把原本散落在 /root/zikai 的 FastAPI 服务整理到 server/ 子目录, 开启独立 git 与 venv。运行时数据(config.yaml / keys / uploads) 按 .gitignore 留在工作目录但不入仓。
5.5 KiB
zikai file service
A Python web service (FastAPI) for f.zikai.wang that provides host
monitoring and large-file upload over both HTTP and SFTP. It follows a
Spring-style layered architecture (controllers → services → dao, plus
models and schemas), ships with auto-generated API docs, and runs entirely
from a self-contained .venv.
Features
GET /api/system/status— CPU, memory, and per-disk usage (viapsutil).POST /api/files/upload— streamed multipart upload (flat memory, multi-GB friendly), with SHA-256.GET /api/files,GET /api/files/{id},GET /api/files/{id}/download.- Embedded SFTP server (asyncssh) supporting password + public-key auth, sharing the same storage as HTTP.
/docs(Swagger UI) and/redoc— interactive, auto-lists all APIs.- Metadata persisted in an independent MySQL database (
zikai_filesvc). start.sh/stop.shlifecycle;setup.shfor one-time provisioning.
Architecture (Spring-style layers)
app/
├── controllers/ # FastAPI routers — HTTP boundary (like @RestController)
├── services/ # business logic (SystemService, UploadService, SFTP server)
├── dao/ # data access objects — the only layer that issues SQL/ORM
├── models/ # SQLAlchemy ORM entities
├── schemas/ # pydantic DTOs (request/response validation)
├── database.py # engine, session, Base, get_db() dependency
├── config.py # typed Settings loaded from config.yaml
└── scripts/ # init_db.py — DB provisioning
Request flow: controller → service → dao → ORM model → MySQL.
The DB session is injected by FastAPI's get_db dependency and passed down.
Quick start
cd /root/zikai
./setup.sh # one-time: venv, deps, provision DB + user, SFTP host key
./start.sh # start HTTP (127.0.0.1:6867) + SFTP (0.0.0.0:2022)
./stop.sh # stop both
setup.sh is re-runnable. It creates the .venv, installs requirements.txt,
copies config.example.yaml → config.yaml (if absent), provisions a new
independent MySQL database and app user via the local root socket, and
generates the SFTP host key.
Access
| Where | URL |
|---|---|
| Status page (HTML) | https://f.zikai.wang/api/system/status |
| Status page (JSON) | https://f.zikai.wang/api/system/status?format=json (or Accept: application/json) |
| API docs (Swagger) | https://f.zikai.wang/docs (HTTP Basic Auth — see docs: in config.yaml) |
| API docs (ReDoc) | https://f.zikai.wang/redoc (same auth) |
| Upload | curl -F file=@big.iso https://f.zikai.wang/api/files/upload |
| SFTP | sftp -P 2022 uploader@f.zikai.wang |
/api/system/status content-negotiates: browsers (Accept: text/html) get a
human-readable page with progress bars; API clients get JSON. Force one with
?format=html or ?format=json.
/docs, /redoc, and /openapi.json require HTTP Basic Auth — the browser
will prompt you. Username + plaintext password live in config.yaml under
docs:. /health and / remain public.
Apache (/etc/apache2/sites-available/f.zikai.wang-le-ssl.conf) proxies
f.zikai.wang → 127.0.0.1:6867 with ProxyPreserveHost On, so the service
only binds the loopback.
Large/slow HTTP uploads: Apache's proxy leg inherits the global
Timeout 300. For multi-GB transfers over a slow link, prefer SFTP (it bypasses the HTTP proxy entirely). To raise the HTTP ceiling you can addProxyTimeout/Timeoutin the Apache vhost.
Configuration
All runtime config lives in config.yaml (git-ignored). See
config.example.yaml for the full schema. Notable keys:
server— bind host/port (keep127.0.0.1:6867to match Apache).database— host/port/user/password/database. The password is auto-generated and written here bysetup.sh/init_db.py.storage.upload_dir,storage.chunk_bytes(default 1 MiB streaming chunk).sftp— enabled, host/port, host key + authorized_keys paths, andusers.
Setting the /docs admin password
Edit config.yaml directly — no hashing required:
docs:
enabled: true
username: admin
password: "your-plaintext-password"
realm: "zikai docs"
Then ./stop.sh && ./start.sh. The file is root-owned and stays on this
host; comparison is constant-time (secrets.compare_digest).
Setting SFTP credentials
Password auth — generate a bcrypt hash and put it in config.yaml:
.venv/bin/python -c "import bcrypt;print(bcrypt.hashpw(b'yourpass',bcrypt.gensalt()).decode())"
# paste the output into sftp.users[].password_hash, then ./stop.sh && ./start.sh
Public-key auth — append each client's public key (OpenSSH format) to
keys/authorized_keys (one per line). Clients in sftp.users[] may then log
in with either method.
Regenerating the DB password
.venv/bin/python -m app.scripts.init_db # new random password
KEEP_DB_PASSWORD=1 .venv/bin/python -m app.scripts.init_db # keep current
SFTP notes
- The SFTP server can't traverse Apache's HTTP proxy, so it binds
0.0.0.0:2022directly. Open port 2022 in your firewall for external clients (FileZilla/WinSCP/scp). - Sessions are chrooted to the upload root (
uploads/), shared with HTTP. - Only users listed in
sftp.usersmay connect; only SFTP (no shell/exec) is allowed.
Logs & pidfiles
- HTTP logs →
logs/app.log; SFTP logs →logs/sftp.log. - Pidfiles:
app.pid,sftp.pid(used bystop.sh).