Spaces:
Runtime error
Runtime error
Commit ·
0f62e9d
1
Parent(s): a2234fc
docker file fixed
Browse files- deploy/huggingface/Dockerfile → Dockerfile +3 -3
- deploy/.gitattributes +0 -4
- deploy/Caddyfile +0 -21
- deploy/README.md +0 -61
- deploy/docker-compose.prod.yml +0 -66
- deploy/env.prod.example +0 -80
- deploy/nginx-notestack.conf +0 -31
- deploy/setup.sh +0 -39
- deploy/update.sh +0 -46
deploy/huggingface/Dockerfile → Dockerfile
RENAMED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
-
# Hugging Face Spaces image
|
| 2 |
-
#
|
| 3 |
-
#
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|