Muhammad-Mehdi commited on
Commit
0f62e9d
·
1 Parent(s): a2234fc

docker file fixed

Browse files
deploy/huggingface/Dockerfile → Dockerfile RENAMED
@@ -1,6 +1,6 @@
1
- # Hugging Face Spaces image, built from the repo root (the Space holds the whole repo).
2
- # Upload this file to the Space root as `Dockerfile`. One container, non-root uid 1000, port 7860;
3
- # the worker runs inside the API process.
4
  FROM python:3.12-slim
5
 
6
  RUN useradd -m -u 1000 user
 
1
+ # Hugging Face Spaces image (the Space builds this root Dockerfile from the whole repo).
2
+ # One container, non-root uid 1000, port 7860; the worker runs inside the API process.
3
+ # Local docker-compose builds backend/Dockerfile instead.
4
  FROM python:3.12-slim
5
 
6
  RUN useradd -m -u 1000 user
deploy/.gitattributes DELETED
@@ -1,4 +0,0 @@
1
- *.sh text eol=lf
2
- Caddyfile text eol=lf
3
- *.yml text eol=lf
4
- env.prod.example text eol=lf
 
 
 
 
 
deploy/Caddyfile DELETED
@@ -1,21 +0,0 @@
1
- {
2
- email {$ACME_EMAIL}
3
- }
4
-
5
- # HTTPS certificates are issued and renewed automatically for API_DOMAIN (set in .env.prod).
6
- {$API_DOMAIN} {
7
- encode zstd gzip
8
-
9
- reverse_proxy api:8000 {
10
- # Stream job progress and chat (server sent events) instead of buffering them.
11
- flush_interval -1
12
- transport http {
13
- read_timeout 0
14
- }
15
- }
16
-
17
- # Uploads (voice samples, files) can be large.
18
- request_body {
19
- max_size 220MB
20
- }
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
deploy/README.md DELETED
@@ -1,61 +0,0 @@
1
- # Deploying the backend to a DigitalOcean Droplet
2
-
3
- One Droplet runs the API, the job worker and, optionally, the video renderer. HTTPS comes from the reverse
4
- proxy already on the Droplet (Nginx, see nginx-notestack.conf), or from the bundled Caddy (`CADDY=1`) on a
5
- Droplet with nothing else on ports 80 and 443.
6
- The database is Neon. The frontend is on Cloudflare Pages.
7
-
8
- ## First time
9
-
10
- 1. **Droplet**: Ubuntu 24.04, 2 GB RAM for API + worker, 4 GB if you also run the renderer. Add your SSH key.
11
- 2. **DNS**: in Cloudflare, add an `A` record `api` pointing to the Droplet IP, **DNS only (grey cloud)** so long
12
- running progress streams are not cut at 100 seconds.
13
- 3. **Neon**: Dashboard > Connect, turn **Connection pooling off** (host without `-pooler`), copy the string and
14
- change `postgresql://` to `postgresql+psycopg://`.
15
- 4. On the Droplet:
16
- ```bash
17
- git clone https://github.com/FireBird-Technologies/NotestackAI.git /opt/notestack
18
- bash /opt/notestack/deploy/setup.sh # installs Docker, firewall, swap, creates deploy/.env.prod
19
- nano /opt/notestack/deploy/.env.prod # fill in every <...> value
20
- bash /opt/notestack/deploy/update.sh # builds, starts, waits for https://API_DOMAIN/api/health
21
- ```
22
- Add `RENDER=1` before `update.sh` to also run the video renderer.
23
- 5. **Frontend**: in Cloudflare Pages set `VITE_API_URL=https://api.notestack.ai`, then retry the deployment.
24
- 6. **Google OAuth client**: redirect URI `https://api.notestack.ai/api/auth/google/callback`, JavaScript origin
25
- `https://notestackai.pages.dev`.
26
-
27
- ## Sharing the Droplet with other backends (Nginx)
28
-
29
- The API listens on `127.0.0.1:8010` only (`API_PORT`). Point Nginx at it:
30
-
31
- ```bash
32
- sudo cp /opt/notestack/deploy/nginx-notestack.conf /etc/nginx/sites-available/notestack
33
- sudo ln -s /etc/nginx/sites-available/notestack /etc/nginx/sites-enabled/notestack
34
- sudo nginx -t && sudo systemctl reload nginx
35
- sudo certbot --nginx -d api.notestack.ai
36
- ```
37
-
38
- ## Every update
39
-
40
- ```bash
41
- bash /opt/notestack/deploy/update.sh
42
- ```
43
-
44
- ## Useful commands (from /opt/notestack/deploy)
45
-
46
- ```bash
47
- docker compose --env-file .env.prod -f docker-compose.prod.yml ps
48
- docker compose --env-file .env.prod -f docker-compose.prod.yml logs -f api worker
49
- docker compose --env-file .env.prod -f docker-compose.prod.yml restart worker
50
- ```
51
-
52
- ## Notes
53
-
54
- - **Why the direct Neon URL**: the worker uses Postgres advisory locks for its scheduled tasks, which do not
55
- work through Neon's transaction pooler. Two processes need only a handful of connections.
56
- - **Neon compute hours**: the worker polls for jobs every `WORKER_POLL_SECONDS`, so Neon's compute stays awake.
57
- That is roughly 720 compute hours a month, beyond the free tier; use a paid Neon plan in production.
58
- - **Files**: with `STORAGE_BACKEND=local` uploads and generated media live in the `appdata` Docker volume on the
59
- Droplet (covered by Droplet backups). Switch to Cloudflare R2 before real traffic if you want them to survive
60
- losing the Droplet.
61
- - **Migrations** run automatically every time the API container starts.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
deploy/docker-compose.prod.yml DELETED
@@ -1,66 +0,0 @@
1
- # Production stack for a single DigitalOcean Droplet. The database is Neon (DATABASE_URL in .env.prod).
2
- # cd /opt/notestack/deploy && docker compose --env-file .env.prod -f docker-compose.prod.yml up -d --build
3
- # The API listens on 127.0.0.1:API_PORT (default 8010) for the reverse proxy already on this host (Nginx,
4
- # Caddy...). On a Droplet with nothing else on ports 80/443, start the bundled Caddy with --profile caddy.
5
-
6
- services:
7
- api:
8
- build: ../backend
9
- restart: unless-stopped
10
- env_file: .env.prod
11
- # Migrations run against Neon on every start (no-op when up to date). Proxy headers let the API see the
12
- # real client IP and https scheme behind Caddy.
13
- command: >
14
- sh -c "alembic upgrade head &&
15
- uvicorn app.main:app --host 0.0.0.0 --port 8000 --proxy-headers --forwarded-allow-ips='*'"
16
- ports:
17
- - "127.0.0.1:${API_PORT:-8010}:8000" # local only: the host proxy forwards API_DOMAIN here
18
- volumes:
19
- - appdata:/data
20
- healthcheck:
21
- test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/health')"]
22
- interval: 30s
23
- timeout: 5s
24
- retries: 3
25
- start_period: 40s
26
-
27
- worker:
28
- build: ../backend
29
- restart: unless-stopped
30
- env_file: .env.prod
31
- command: python -m app.worker
32
- volumes:
33
- - appdata:/data # same volume as the API, so both see the same stored files and corpus cache
34
- depends_on:
35
- api:
36
- condition: service_healthy # the API has applied migrations by the time it is healthy
37
-
38
- renderer:
39
- build: ../renderer
40
- restart: unless-stopped
41
- environment:
42
- INTERNAL_TOKEN: ${INTERNAL_TOKEN} # must match the API's
43
- profiles: ["render"] # optional: start with --profile render (needs about 2 GB of free RAM)
44
-
45
- caddy: # only on a Droplet with no other web server: --profile caddy
46
- image: caddy:2
47
- restart: unless-stopped
48
- profiles: ["caddy"]
49
- ports:
50
- - "80:80"
51
- - "443:443"
52
- - "443:443/udp"
53
- environment: # only what the Caddyfile needs, not the app secrets
54
- API_DOMAIN: ${API_DOMAIN}
55
- ACME_EMAIL: ${ACME_EMAIL}
56
- volumes:
57
- - ./Caddyfile:/etc/caddy/Caddyfile:ro
58
- - caddy_data:/data
59
- - caddy_config:/config
60
- depends_on:
61
- - api
62
-
63
- volumes:
64
- appdata: {}
65
- caddy_data: {}
66
- caddy_config: {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
deploy/env.prod.example DELETED
@@ -1,80 +0,0 @@
1
- # Copy to deploy/.env.prod on the Droplet (git ignores .env.prod) and fill in every <...> value.
2
- # Generate secrets with: openssl rand -hex 32
3
-
4
- # ---- Domains
5
- API_DOMAIN=api.notestack.ai
6
- # Local port the host reverse proxy (Nginx) forwards API_DOMAIN to. Change it if 8010 is taken.
7
- API_PORT=8010
8
- ACME_EMAIL=arslan@firebird-technologies.com
9
- ENV=production
10
- API_URL=https://api.notestack.ai
11
- FRONTEND_URL=https://notestackai.pages.dev
12
- CORS_ORIGINS=https://notestackai.pages.dev
13
-
14
- # ---- Neon Postgres
15
- # In Neon: Dashboard > Connect. Pick the DIRECT connection (turn "Connection pooling" off: the host must NOT
16
- # contain "-pooler"), then change the scheme from postgresql:// to postgresql+psycopg://
17
- DATABASE_URL=postgresql+psycopg://<user>:<password>@<ep-xxxx>.<region>.aws.neon.tech/<db>?sslmode=require
18
- # The worker checks for jobs every WORKER_POLL_SECONDS, which keeps Neon's compute awake. Raise it to cut
19
- # compute hours (jobs then start a few seconds later).
20
- WORKER_POLL_SECONDS=3
21
- WORKER_CONCURRENCY=3
22
- RUN_WORKER_IN_API=false
23
-
24
- # ---- Secrets (new values for production, never reuse local ones)
25
- JWT_SECRET=<openssl rand -hex 32>
26
- INTERNAL_TOKEN=<openssl rand -hex 32>
27
- UNSUBSCRIBE_SECRET=<openssl rand -hex 32>
28
-
29
- # ---- Google sign in (add https://api.notestack.ai/api/auth/google/callback to the OAuth client)
30
- GOOGLE_CLIENT_ID=<client id>
31
- GOOGLE_CLIENT_SECRET=<client secret>
32
- GOOGLE_REDIRECT_URI=https://api.notestack.ai/api/auth/google/callback
33
-
34
- # ---- Email
35
- EMAIL_PROVIDER=resend
36
- RESEND_API_KEY=<resend key>
37
- FROM_EMAIL=Notestack <hello@notestack.ai>
38
- ALERTS_EMAIL=arslan@firebird-technologies.com
39
-
40
- # ---- Files. Local disk on the Droplet (kept in the appdata Docker volume, backed up with the Droplet).
41
- # For R2 instead: set STORAGE_BACKEND=r2 and fill the R2_* values.
42
- STORAGE_BACKEND=local
43
- LOCAL_STORAGE_DIR=/data/storage
44
- CORPUS_CACHE_DIR=/data/corpus
45
- R2_ACCOUNT_ID=
46
- R2_ACCESS_KEY_ID=
47
- R2_SECRET_ACCESS_KEY=
48
- R2_BUCKET=notestack-assets
49
- # Sharing a bucket with other apps? Keep Notestack in its own folder:
50
- R2_PREFIX=
51
-
52
- # ---- AI
53
- LLM_API_KEY=<z.ai key>
54
- ELEVENLABS_API_KEY=<elevenlabs key>
55
- TYPESAFE_API_KEY=
56
- FIRECRAWL_API_KEY=
57
-
58
- # ---- Renderer (only used when started with --profile render)
59
- RENDERER_URL=http://renderer:3100
60
-
61
- # ---- Social publishing (optional). Redirect URIs: https://api.notestack.ai/api/social/{x,linkedin}/callback
62
- X_CLIENT_ID=
63
- X_CLIENT_SECRET=
64
- LINKEDIN_CLIENT_ID=
65
- LINKEDIN_CLIENT_SECRET=
66
-
67
- BILLING_ENABLED=false
68
- # Stripe. Checkout, the customer portal and webhooks. Prices are found or created by lookup key
69
- # (notestack_<plan>_<cycle>) from backend/app/services/plans.py; set STRIPE_PRICE_* to pin existing ones.
70
- # Webhook endpoint: {API_URL}/api/billing/webhook with events checkout.session.completed,
71
- # checkout.session.async_payment_succeeded, checkout.session.async_payment_failed,
72
- # customer.subscription.created, customer.subscription.updated, customer.subscription.deleted,
73
- # customer.subscription.paused, customer.subscription.resumed, invoice.paid, invoice.payment_failed,
74
- # invoice.payment_action_required, charge.dispute.created.
75
- STRIPE_SECRET_KEY=
76
- STRIPE_WEBHOOK_SECRET=
77
- STRIPE_PRICE_WRITER_MONTHLY=
78
- STRIPE_PRICE_WRITER_ANNUAL=
79
- STRIPE_PRICE_STUDIO_MONTHLY=
80
- STRIPE_PRICE_STUDIO_ANNUAL=
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
deploy/nginx-notestack.conf DELETED
@@ -1,31 +0,0 @@
1
- # Nginx site for the Notestack API on a Droplet that already runs other backends.
2
- # sudo cp /opt/notestack/deploy/nginx-notestack.conf /etc/nginx/sites-available/notestack
3
- # sudo ln -s /etc/nginx/sites-available/notestack /etc/nginx/sites-enabled/notestack
4
- # sudo nginx -t && sudo systemctl reload nginx
5
- # sudo certbot --nginx -d api.notestack.ai # adds HTTPS and the 443 server block
6
- # The API container listens on 127.0.0.1:8010 (API_PORT in deploy/.env.prod).
7
-
8
- server {
9
- listen 80;
10
- listen [::]:80;
11
- server_name api.notestack.ai;
12
-
13
- # Voice samples, PDFs and other uploads.
14
- client_max_body_size 220m;
15
-
16
- location / {
17
- proxy_pass http://127.0.0.1:8010;
18
- proxy_http_version 1.1;
19
- proxy_set_header Host $host;
20
- proxy_set_header X-Real-IP $remote_addr;
21
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
22
- proxy_set_header X-Forwarded-Proto $scheme;
23
- proxy_set_header Connection "";
24
-
25
- # Job progress and chat are server sent events: stream them, do not buffer or time out early.
26
- proxy_buffering off;
27
- proxy_cache off;
28
- proxy_read_timeout 3600s;
29
- proxy_send_timeout 3600s;
30
- }
31
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
deploy/setup.sh DELETED
@@ -1,39 +0,0 @@
1
- #!/usr/bin/env bash
2
- # One time setup for a fresh Ubuntu 24.04 Droplet. Run as root:
3
- # git clone https://github.com/FireBird-Technologies/NotestackAI.git /opt/notestack
4
- # bash /opt/notestack/deploy/setup.sh
5
- set -euo pipefail
6
-
7
- cd "$(dirname "$0")"
8
-
9
- echo "==> Installing Docker"
10
- if ! command -v docker >/dev/null; then
11
- curl -fsSL https://get.docker.com | sh
12
- fi
13
-
14
- echo "==> Firewall: SSH, HTTP, HTTPS only"
15
- ufw allow OpenSSH
16
- ufw allow 80/tcp
17
- ufw allow 443/tcp
18
- ufw allow 443/udp
19
- ufw --force enable
20
-
21
- echo "==> Swap (helps builds and the renderer on small Droplets)"
22
- if [ ! -f /swapfile ]; then
23
- fallocate -l 2G /swapfile
24
- chmod 600 /swapfile
25
- mkswap /swapfile
26
- swapon /swapfile
27
- echo '/swapfile none swap sw 0 0' >> /etc/fstab
28
- fi
29
-
30
- if [ ! -f .env.prod ]; then
31
- cp env.prod.example .env.prod
32
- chmod 600 .env.prod
33
- echo
34
- echo "Created deploy/.env.prod. Fill in every <...> value, then run: bash deploy/update.sh"
35
- exit 0
36
- fi
37
-
38
- echo "==> .env.prod exists; starting the stack"
39
- bash ./update.sh
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
deploy/update.sh DELETED
@@ -1,46 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Deploy the latest code: pull, rebuild, restart, then check health.
3
- # bash /opt/notestack/deploy/update.sh # API, worker, Caddy
4
- # RENDER=1 bash /opt/notestack/deploy/update.sh # also the video renderer
5
- # CADDY=1 bash /opt/notestack/deploy/update.sh # bundled Caddy (only if nothing else uses ports 80/443)
6
- set -euo pipefail
7
-
8
- cd "$(dirname "$0")"
9
- git -C .. pull --ff-only
10
-
11
- # Placeholders look like =<...> or <user>/<password> inside DATABASE_URL (FROM_EMAIL's <address> is fine).
12
- if grep -qE '=<|<(user|password|ep-xxxx|region|db)>' .env.prod; then
13
- echo "deploy/.env.prod still has <placeholders>; fill them in first."
14
- exit 1
15
- fi
16
-
17
- profile=()
18
- [ "${RENDER:-0}" = "1" ] && profile+=(--profile render)
19
- [ "${CADDY:-0}" = "1" ] && profile+=(--profile caddy)
20
-
21
- docker compose --env-file .env.prod -f docker-compose.prod.yml "${profile[@]}" up -d --build --remove-orphans
22
- docker image prune -f >/dev/null
23
-
24
- port=$(grep -E '^API_PORT=' .env.prod | cut -d= -f2)
25
- port=${port:-8010}
26
- echo "==> Waiting for the API on 127.0.0.1:$port"
27
- for _ in $(seq 1 30); do
28
- curl -fsS "http://127.0.0.1:$port/api/health" >/dev/null 2>&1 && break
29
- sleep 4
30
- done
31
- curl -fsS "http://127.0.0.1:$port/api/health" || { echo "API not up. Logs: cd deploy && docker compose --env-file .env.prod -f docker-compose.prod.yml logs -f api"; exit 1; }
32
- echo
33
-
34
- domain=$(grep -E '^API_DOMAIN=' .env.prod | cut -d= -f2)
35
- echo "==> Waiting for https://$domain/api/health"
36
- for _ in $(seq 1 30); do
37
- if curl -fsS "https://$domain/api/health" >/dev/null 2>&1; then
38
- curl -fsS "https://$domain/api/health"
39
- echo
40
- echo "==> Live."
41
- exit 0
42
- fi
43
- sleep 4
44
- done
45
- echo "Health check did not pass yet. Logs: cd deploy && docker compose --env-file .env.prod -f docker-compose.prod.yml logs -f api"
46
- exit 1