diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..24fc6fb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,44 @@ +# ── Docker build context — loại bỏ mã nguồn/bí mật không cần vào image ── +.git +.gitignore +.github + +# Mã nguồn client (JSX) — image CHỈ chứa bản precompiled (minified) +app/static/js/app.jsx +app/static/js/app.jsx.new +*.jsx + +# Bí mật +.env +.env.* +!.env.example + +# Dependencies / build cache +node_modules +__pycache__ +*.pyc +.cache +.pytest_cache + +# Tài liệu / kế hoạch / backup nội bộ +wiki.md +PLAN.md +PLAN* +plans/ +md/ +*.patch +*.recovered +index.html.recovered +celerybeat-schedule +celerybeat.pid + +# Dữ liệu runtime (mount volume riêng — KHÔNG chép vào image) +app/storage/uploads +app/storage/processed +app/storage/sonicforge.db +celerybeat-schedule + +# Test +tests/ +tools/ +samples/ diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..bfa25bb --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +# ── SonicForge Studio — môi trường (SAO CHÉP sang .env — KHÔNG commit .env) ── + +# Redis / Celery +REDIS_URL=redis://redis:6379/0 +CELERY_BROKER_URL=redis://redis:6379/0 +CELERY_RESULT_BACKEND=redis://redis:6379/0 + +# LLM (server-side proxy — client không thấy key) +OPENAI_API_BASE=http://localhost:11434/v1 +OPENAI_API_KEY=ollama + +# Auth +SECRET_KEY=thay-bang-chuoi-ngau-nhien-dai +DEFAULT_ADMIN_PASSWORD=thay-mat-khau-admin + +# Storage (đường dẫn trong container) +STORAGE_DIR=/app/app/storage diff --git a/DESKTOP_INSTALL_PLAN.md b/DESKTOP_INSTALL_PLAN.md new file mode 100644 index 0000000..7a65624 --- /dev/null +++ b/DESKTOP_INSTALL_PLAN.md @@ -0,0 +1,87 @@ +# DESKTOP INSTALL PLAN — SonicForge Studio (bản cài trực tiếp trên OS) + +Mô hình: **server chạy nền trên máy người dùng (localhost), client mở bằng browser**. +Không cần Electron — tận dụng stack hiện có (FastAPI + static JS precompiled). +Soundfonts do **người dùng tự tải vào thư mục riêng** và **khai báo** cho ứng dụng. + +--- + +## 1. KIẾN TRÚC + +``` +┌─────────────────────────── NGƯỜI DÙNG (1 máy) ───────────────────────────┐ +│ Browser ──http://127.0.0.1:8000──▶ FastAPI (uvicorn, chạy NỀN) │ +│ │ │ +│ ~/SonicForgeStudio/ ├─ data/ (DB, uploads, cache) │ +│ ├─ data/sonicforge.db ├─ soundfonts/ (user tự bỏ .sf2) │ +│ ├─ soundfonts/*.sf2/.sf3 └─ quét nền 30s (scanner có sẵn) │ +│ └─ config.json ← khai báo folder soundfont │ +└──────────────────────────────────────────────────────────────────────────┘ +``` + +## 2. ĐÓNG GÓI CÀI ĐẶT (per-OS) — server nền + browser client + +### Thành phần đóng gói +| Thành phần | Vai trò | +|---|---| +| Python backend (`app/`, `requirements.txt`) | Server — **PyInstaller `--onedir`** → binary (không cần Python trên máy user; bảo vệ source tốt hơn so với chạy .py) | +| `app/static/` (precompiled.js + css + vendor) | Client — đóng vào package, server phục vụ | +| VST3 plugins (`vst_plugins/`) | Copy theo platform (win .dll / mac .vst3 / linux .so) | +| Service wrapper | Tự khởi động server nền + mở browser khi login | + +### Cài đặt + service nền theo OS +| OS | Installer | Service nền | Auto-open browser | +|---|---|---|---| +| **Windows** | Inno Setup (`.exe`) | Windows Service qua `NSSM`/`WinSW` hoặc Task Scheduler (logon) | `start http://127.0.0.1:8000` | +| **macOS** | `.dmg` + `.app` (PyInstaller) | `launchd` LaunchAgent (`~/Library/LaunchAgents`) | `open http://127.0.0.1:8000` | +| **Linux** | `.deb` / `.AppImage` | systemd **user** service (`~/.config/systemd/user/`) | `xdg-open http://127.0.0.1:8000` | + +- Server bind **`127.0.0.1`** (không lộ mạng), port mặc định 8000 (config được). +- App gồm 2 tiến trình nhỏ: `web` (uvicorn) + `worker` (celery) — hoặc gộp worker vào web ở chế độ desktop (đơn giản: `--pool=solo`, chạy celery trong tiến trình riêng nếu cần render nặng). + +## 3. THƯ MỤC DỮ LIỆU NGƯỜI DÙNG +``` +~/SonicForgeStudio/ +├── data/ # DB, uploads, processed (thay app/storage khi chạy desktop) +├── soundfonts/ # USER tự tải .sf2/.sf3 vào đây (mặc định được quét) +└── config.json # cấu hình: soundfont_dirs, port, autostart... +``` +- `config.py`: thêm `DATA_DIR` (env `SFDATA_DIR`, mặc định `~/SonicForgeStudio/data`), `SOUNDFONT_DIRS`. + +## 4. SOUNDFONT DO NGƯỜI DÙNG QUẢN LÝ (tải + khai báo) +**Cơ chế hiện có (tận dụng):** `app/core/soundfont_scanner.py` — `SoundFontAutoScanner` quét nền 30s +`/opt/daw_engine/soundfonts` + `storage/soundfonts` → catalog → API list qua `app/api/v1/plugins.py`. + +**Việc cần làm (mở rộng):** +1. **Scanner nhận folder người dùng:** constructor nhận thêm `user_dirs` (từ `SOUNDFONT_DIRS` env + `config.json`) — merge vào catalog (ưu tiên: user > system). +2. **Khai báo folder — 2 cách:** + - **UI** (chính): Settings → "Soundfonts" → nút **Add folder…** (chọn thư mục chứa .sf2) → lưu vào `config.json` → gọi scanner rescan. + - **config.json** (thủ công): `{ "soundfont_dirs": ["D:/SF", "/Users/me/sf"] }`. +3. **API:** thêm endpoint `POST /api/v1/plugins/soundfonts/dirs` (đăng ký folder) + `GET .../dirs` (liệt kê) — scanner reload. +4. **UI danh sách:** hiển thị catalog (tên SF, kích thước, folder nguồn) — chọn = load vào FluidSynth (luồng có sẵn qua `SonicSF.selectInstrument`). +5. **Số hóa:** không upload file — server đọc trực tiếp từ đường dẫn user khai báo (không nhân đôi dữ liệu). + +## 5. BUILD + PHÁT HÀNH +``` +code → build.mjs (precompiled + ?v=) → PyInstaller (server binary) → đóng installer theo OS +→ ký số (tùy chọn) → phát hành (GitHub Releases / trang riêng) +``` +- **GitHub Actions matrix** (windows-latest / macos-latest / ubuntu-latest): test → build → installer artifact. +- Installer gồm: binary server, static/, VST plugins nền tảng, script tạo service + mở browser, mặc định tạo `~/SonicForgeStudio/` lần chạy đầu. + +## 6. CẬP NHẬT +- **Version check**: khi mở app, gọi endpoint version (file `version.json` đóng kèm + so sánh remote) → thông báo bản mới + link tải installer. +- Cập nhật = chạy installer mới (ghi đè, GIỮ NGUYÊN `~/SonicForgeStudio/` — data + soundfonts không đụng). +- `?v=` cache-bust JS mỗi bản (cơ chế đã có) — browser không dính cache cũ. + +## 7. CHECKLIST CODE CẦN LÀM +- [ ] `config.py`: `DATA_DIR`, `SOUNDFONT_DIRS` (env + config.json) +- [ ] `soundfont_scanner.py`: nhận `user_dirs`, merge catalog, ưu tiên user +- [ ] API: `POST/GET .../soundfonts/dirs` (đăng ký/liệt kê folder) + rescan +- [ ] UI Settings → Soundfonts (Add folder, Browse, list, load) +- [ ] `soundfontStorage.js`: chuyển hướng sang catalog folder-scan (giữ upload path cho dự án cũ) +- [ ] PyInstaller spec: bundle static/ + vendor (libfluidsynth wasm, vst), bind 127.0.0.1 +- [ ] Service wrappers: Windows (NSSM/WinSW), macOS (launchd), Linux (systemd user) + auto-open browser +- [ ] Installer scripts: Inno Setup (.exe), dmg (macOS), deb/AppImage (Linux) +- [ ] CI matrix build 3 OS + release artifacts +- [ ] `version.json` + in-app update check diff --git a/DISTRIBUTION_PLAN.md b/DISTRIBUTION_PLAN.md new file mode 100644 index 0000000..fd9bad4 --- /dev/null +++ b/DISTRIBUTION_PLAN.md @@ -0,0 +1,85 @@ +# DISTRIBUTION PLAN — SonicForge Studio (Alpha → Beta → Production) + +Mục tiêu: phân phối ứng dụng cho người dùng **không chia sẻ mã nguồn**, có pipeline +cập nhật liên tục. Áp dụng cho backend Python/FastAPI + frontend JS (Babel precompiled). + +--- + +## 1. TÁCH BÍ MẬT — FILE .env + +| Biến | Dùng cho | Bắt buộc | +|---|---|---| +| `REDIS_URL` | Redis/Celery broker | ✓ | +| `CELERY_BROKER_URL` / `CELERY_RESULT_BACKEND` | Celery | ✓ | +| `OPENAI_API_BASE` / `OPENAI_API_KEY` | LLM server-side (client KHÔNG thấy key — `aiGateway.js` chỉ gọi proxy server) | ✓ | +| `SECRET_KEY` | Auth token (app/core/auth.py) | ✓ (mặc định trống → phải set) | +| `DEFAULT_ADMIN_PASSWORD` | Admin mặc định | thay đổi ở production | + +- File `.env.example` đã tạo (commit được). `.env` thật KHÔNG commit (đã vào `.dockerignore`). +- **Nguyên tắc:** client JS không bao giờ chứa secret — mọi API key nằm server (env) hoặc proxy. + +## 2. BUILD IMAGE — KHÔNG CHIA SẺ MÃ NGUỒN + +### 2.1 Chặn source khỏi image (`.dockerignore` — đã tạo) +Loại khỏi build context: `.git`, `app/static/js/app.jsx` (source JSX), `wiki.md`, `plans/`, `*.patch`, `.env`, `tests/`, `node_modules`, backup... + +### 2.2 Frontend — chỉ ship bản precompiled +- Build JS: `node build.mjs` (Babel → `app.precompiled.js` minified) — **CHỈ bản minified vào image**, source `.jsx` bị `.dockerignore` chặn. +- Bump `?v=` (cache-bust) mỗi bản phát hành — user cập nhật không dính cache cũ. + +### 2.3 Backend Python — giới hạn đọc source +- Python không biên dịch native mặc định → `.py` vẫn đọc được trong image. 3 lớp bảo vệ: + 1. **Registry riêng tư** (GHCR / Docker Hub private / Harbor) — image KHÔNG public — người dùng chỉ nhận qua pull có auth. + 2. **Secret chỉ qua env** — không hardcode gì trong image. + 3. *(Tùy chọn, giai đoạn sau)* compile Python bằng **Nuitka** → `.so` cho `app/core`, `app/api` (giữ `main.py`/`config.py` đọc được — không chứa bí mật). + +### 2.4 Cách build + push +```bash +# 1. Precompile JS + bump ?v= +node build.mjs # hoặc: NODE=... babel ... (xem wiki) +# 2. Build image (tag theo version) +docker build -t ghcr.io//sonicforge-studio:v1.0.0-alpha.1 . +docker push ghcr.io//sonicforge-studio:v1.0.0-alpha.1 +# 3. Production chạy image đã push (KHÔNG mount source): +docker compose -f docker-compose.prod.yml up -d +``` +- Compose production (`docker-compose.prod.yml` — đã tạo): image từ registry, `env_file: .env`, + volumes bền (uploads/processed/db), asset dirs qua env (`VST3_DIR`, `SOUNDFONTS_DIR`, `PIANOBOOK_DIR`), + KHÔNG có `.:/app` (bỏ mount dev của compose cũ). + +## 3. KẾ HOẠCH PHÁT HÀNH + +### Giai đoạn 1 — ALPHA (nội bộ dev/QA) +- Tag: `v0.x.x-alpha.N` — chạy `docker compose up --build` (dev mount OK). +- Log verbose; chưa quan tâm bảo mật; test tính năng + thu hồi nhanh. +- Mỗi thay đổi: bump `?v=` → build → tag → ghi wiki.md. + +### Giai đoạn 2 — BETA (nhóm người dùng mời) +- Tag: `v0.x.x-beta.N` — image push **registry riêng tư**. +- Người dùng: `docker compose -f docker-compose.prod.yml pull && up -d` (chỉ cần `.env` + image). +- Thu log lỗi: thêm endpoint `/health` + (tùy chọn) Sentry/self-host error tracking. +- **Feature flags** (biến env `FEATURE_*`) để tắt tính năng rủi ro từ xa. + +### Giai đoạn 3 — PRODUCTION +- Tag: `vX.Y.Z` (semver) + `latest`. +- **Trước khi release:** backup volumes (`docker run --rm -v sf_db:/data -v $PWD:/backup alpine tar czf /backup/db-.tgz /data`), migrate dữ liệu nếu schema đổi. +- Triển khai: pull image mới → recreate (rolling — web trước, worker sau) → healthcheck. +- `SECRET_KEY`, `DEFAULT_ADMIN_PASSWORD`, API keys: quản lý qua secret manager (Docker secrets / vault) — không ở compose file. + +## 4. PIPELINE CẬP NHẬT (lặp lại mỗi release) +``` +code mới → build.mjs (precompiled + ?v=) → docker build (tag mới) +→ push registry → [beta/prod] pull + recreate → backup trước nếu prod → kiểm tra /health +``` +- Phiên bản: `git tag vX.Y.Z` — build tag tự động từ git (`git describe --tags`). +- Rollback: giữ tag cũ — `docker compose -f docker-compose.prod.yml up -d` với `IMAGE=...:`. + +## 5. VIỆC CẦN LÀM (checklist) +- [x] `.env.example` — liệt kê đủ 7 biến +- [x] `.dockerignore` — chặn source/secret/docs +- [x] `docker-compose.prod.yml` — production (registry + env_file + volumes, bỏ mount source) +- [ ] Kiểm tra endpoint `/health` (tạo nếu chưa có — healthcheck compose đang trỏ tới) +- [ ] Chọn registry (GHCR/Docker Hub private/Harbor) + tạo token CI +- [ ] Nuitka compile `app/core`+`app/api` (tùy chọn — nếu cần bảo vệ backend chặt hơn) +- [ ] CI (GitHub Actions): test → build → push tag → deploy beta tự động +- [ ] Backup script volumes trước mỗi production release diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..25327a3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,40 @@ +MIT License + +Copyright (c) 2026 SonicForge Studio contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + +## Third-party components + +Code viết riêng của dự án (app/static/js/services/*, components/*, app.jsx) +thuộc license MIT trên. + +Các thành phần bên thứ ba giữ license gốc: + +- **React / react-dom** — MIT +- **Babel** (@babel/cli, core, preset-react) — MIT +- **jsdom** — MIT +- **Tailwind CSS** (app/static/css/tailwind.min.css) — MIT +- **Lucide icons** — ISC +- **FluidSynth 2.3.0** (app/static/js/vendor/libfluidsynth-2.3.0-sf3.js/.wasm) — + GNU LGPL v2.1+ — xem https://www.gnu.org/licenses/lgpl-2.1.html + +Chi tiết + attribution từng thành phần: xem THIRD_PARTY_LICENSES.md. diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md new file mode 100644 index 0000000..a72730d --- /dev/null +++ b/THIRD_PARTY_LICENSES.md @@ -0,0 +1,37 @@ +# THIRD-PARTY LICENSES — SonicForgeStudio + +Kiểm tra ngày 2026-08-07. Danh sách thành phần bên thứ ba + license. + +## 1. Dependencies (npm — package.json) +| Thành phần | License | Ghi chú | +|---|---|---| +| react / react-dom 19.x | MIT | OK | +| @babel/cli, @babel/core, @babel/preset-react | MIT | Chỉ build-time | +| jsdom | MIT | Chỉ build-time/test | + +## 2. Thư viện nhúng (app/static) +| File | Nguồn | License | Trạng thái | +|---|---|---|---| +| `css/tailwind.min.css` | Tailwind CSS | MIT | OK — nên giữ attribution | +| `js/services/spessasynth_processor.min.js` | SpessaSynth (KHÔNG còn dùng — không được include trong index.html — chỉ FluidSynth) | MIT | ✅ KHÔNG có code nào gọi (chỉ còn comment cũ app.jsx:14276 + file chết) — có thể xóa file | +| `js/vendor/libfluidsynth-2.3.0-sf3.js` + `.wasm` | FluidSynth 2.3.0 (Emscripten) | **LGPL-2.1+** | ⚠️ Bắt buộc giữ license notice + attribution + (nếu phân phối bản build) cung cấp link/tài liệu LGPL | +| `js/services/fluidsynthLoader.js`, `worklets/fluidsynth-bridge.js` | Bản tự viết (bọc FluidSynth) | Dự án | OK | +| Lucide icons (inline `data-lucide`) | Lucide | ISC (MIT-compatible) | OK | + +## 3. Nội dung âm thanh (app/storage) +| File | Nguồn | License | Trạng thái | +|---|---|---|---| +| `soundfonts/518e850f-...sf2` | "General MIDI SoundFont v3.0" — © 2006-2010 Rich "Weeds" Nagel — "Some rights reserved" | Không có text đầy đủ trong file (chỉ ICOP ngắn). Tuyên bố: "created from various **commercial**, custom, and freeware soundfonts and samples" | ⚠️ RỦI RO: (a) điều kiện "some rights reserved" không rõ ràng (thường là CC-BY — cần ghi attribution; có thể hạn chế thương mại); (b) samples gốc có nguồn commercial — quyền tái phân phối phụ thuộc tuyên bố tác giả. **Khuyến nghị: thay bằng SF2 license rõ (FluidR3_GM — MIT/GPL-2; GeneralUser GS — CC-BY-SA; Arachno — public domain) HOẶC giữ + ghi attribution đầy đủ.** | +| `uploads/user_anonymous_*.mp3` | File người dùng upload | Thuộc người dùng | OK — không phải thành phần phân phối | +| SGM-V2.01 (xuất hiện trong log `sfId: SGM-V2.01`) | SGM-V2.01 | Freeware — tác giả cho phép dùng nhưng **hạn chế redistribution** (cần permission) | ⚠️ Nếu vẫn dùng để phân phối bản build — cần permission từ tác giả; không nhúng vào sản phẩm | + +## 4. Assets +- `templates/favicon.svg`, `images/SonicForgeUI.png` — nội bộ (tự tạo) — OK. +- Code trong `app/static/js/services/*`, `app/static/js/components/*`, `app.jsx` — tự viết — thuộc dự án. + +## KẾT LUẬN +- **Không phát hiện vi phạm bản quyền rõ ràng** (không có code GPL bị nhúng vào project MIT; LGPL của FluidSynth tương thích nếu giữ notice). +- **3 điểm cần xử lý trước khi phân phối công khai:** + 1. Bổ sung `LICENSE` text (MIT + LGPL-2.1) + attribution cho SpessaSynth (MIT notice) và Tailwind. + 2. Ghi attribution SF2 Rich Nagel ("General MIDI SoundFont v3.0 — © 2006-2010 Rich 'Weeds' Nagel — Some rights reserved") hoặc thay soundfont khác license rõ. + 3. Không nhúng SGM-V2.01 vào bản phân phối (hạn chế redistribution). diff --git a/app/main.py b/app/main.py index a4a022e..591cf85 100644 --- a/app/main.py +++ b/app/main.py @@ -75,6 +75,10 @@ app.include_router(plugins_router, prefix="/api/v1/plugins", tags=["plugins"]) app.include_router(media_router, prefix="/api/v1/media", tags=["media"]) +@app.get("/health") +async def health(): + return {"status": "ok"} + @app.get("/", response_class=HTMLResponse) async def get_index(): index_path = os.path.join(settings.TEMPLATES_DIR, "index.html") diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index d491b3f..e8fb048 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -14273,7 +14273,7 @@ const App = () => { setInstrumentDropdownTrackId(null); setInstrumentDropdownBtnRect(null); setSynthCategory(null); - // Trigger SpessaSynth load + program change when soundfont instrument selected + // Trigger FluidSynth load + program change when soundfont instrument selected if (window.SonicSF && window.SonicSF.selectInstrument && instrumentId && isSfInstrument) { const sfId = instrumentId.replace('sf_', ''); window.SonicSF.selectInstrument(mch, sfBank || 0, sfProg || 0, sfId); diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..e1ddff1 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,58 @@ +# ── Production compose — KHÔNG mount source, image từ registry, bí mật qua .env ── +# Dùng: docker compose -f docker-compose.prod.yml up -d +services: + redis: + image: redis:7-alpine + restart: unless-stopped + + web: + image: ${IMAGE:-sonicforge-studio:latest} + restart: unless-stopped + ports: + - "${WEB_PORT:-8000}:8000" + env_file: .env + volumes: + - sf_uploads:/app/app/storage/uploads + - sf_processed:/app/app/storage/processed + - sf_db:/app/app/storage + - ${VST3_DIR:-./vst_plugins}:/opt/daw_engine/vst3:ro + - ${SOUNDFONTS_DIR:-./soundfonts}:/opt/daw_engine/soundfonts:ro + - ${PIANOBOOK_DIR:-./samples/pianobook}:/opt/daw_engine/samples/pianobook:ro + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request;urllib.request.urlopen('http://localhost:8000/health')"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 15s + depends_on: + - redis + + worker: + image: ${IMAGE:-sonicforge-studio:latest} + restart: unless-stopped + command: celery -A app.tasks.worker.celery_app worker --loglevel=info + env_file: .env + volumes: + - sf_uploads:/app/app/storage/uploads + - sf_processed:/app/app/storage/processed + - sf_db:/app/app/storage + - ${VST3_DIR:-./vst_plugins}:/opt/daw_engine/vst3:ro + - ${SOUNDFONTS_DIR:-./soundfonts}:/opt/daw_engine/soundfonts:ro + - ${PIANOBOOK_DIR:-./samples/pianobook}:/opt/daw_engine/samples/pianobook:ro + depends_on: + - redis + + beat: + image: ${IMAGE:-sonicforge-studio:latest} + restart: unless-stopped + command: celery -A app.tasks.worker.celery_app beat --loglevel=info + env_file: .env + volumes: + - sf_db:/app/app/storage + depends_on: + - redis + +volumes: + sf_uploads: + sf_processed: + sf_db: