DEPLOY_SERVER

Server Deployment (Self-Hosted)

What This Is

The server deployment runs SourceQuote as a multi-user web application on your own infrastructure — a VPS, a dedicated server, or any machine you control. It uses:

  • Flask as the web application server (via Gunicorn on Linux/macOS, Waitress on Windows)
  • PostgreSQL for the database
  • Firebase Authentication for user login
  • Local filesystem for file storage
  • Caddy as the reverse proxy and TLS termination layer

Key characteristics:

  • Multi-user — each user signs in with a Firebase account
  • Self-hosted — you own and manage the server and all data
  • No cloud dependencies — everything runs on your machine (transcription can optionally use Modal.com)
  • HTTPS via Caddy — Caddy automatically obtains and renews TLS certificates
  • Optional payments — Stripe can be wired up but is not required

Prerequisites: Assumes the development environment is set up as described in ENVIRONMENT_SETUP.md, including PostgreSQL and Firebase.


1. Configure the Server Environment

Copy the example config files if you haven't already:

cp config/server/.env.server.example config/server/.env.server
cp config/server/.secrets.server.example config/server/.secrets.server

Edit config/server/.env.server

Fill in the required values:

FLASK_DEBUG=0
DEPLOY_MODE=server

# Firebase
FIREBASE_PROJECT_ID=your-firebase-project-id
FIREBASE_API_KEY=AIzaSy...
FIREBASE_AUTH_DOMAIN=your-project-id.firebaseapp.com
AUTH_PROVIDER=firebase

# User registration
# true = any Firebase user can self-register on first login
# false = users must be manually activated in the database
ALLOW_OPEN_REGISTRATION=true

# Admin
ADMIN_UIDS=uid1,uid2
ADMIN_EMAIL=you@yourdomain.com

# CORS — list your domain(s) here
ALLOWED_ORIGINS=https://yourdomain.com

# Optional email
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=you@gmail.com
SMTP_FROM=you@gmail.com
SMTP_FROM_NAME=SourceQuote

Edit config/server/.secrets.server

# Generate with: python -c "import secrets; print(secrets.token_hex(32))"
SECRET_KEY=your_generated_secret_key

DATABASE_URL=postgresql://postgres:yourpassword@localhost:5432/transcriber

# Path to the Firebase service account JSON you downloaded
FIREBASE_SERVICE_ACCOUNT=/path/to/firebase-service-account.json

# HuggingFace token (for local transcription)
HF_TOKEN=hf_your_token_here

# Optional: Modal.com (for cloud transcription)
MODAL_TOKEN_SECRET=

# Optional: SMTP password
SMTP_PASS=your_app_password

# Optional: Stripe
STRIPE_SECRET_KEY=
STRIPE_PUBLISHABLE_KEY=
STRIPE_WEBHOOK_SECRET=

# Optional: GitLab bug reporting
GITLAB_TOKEN=

2. Set Up the PostgreSQL Database

If you haven't already created the database and built the schema:

Create the database:

createdb transcriber

Build the schema (Windows):

database\build.bat -h localhost -p 5432 -U postgres

Build the schema (macOS/Linux):

./database/build.sh -h localhost -p 5432 -U postgres

Pass -P yourpassword if your PostgreSQL user has a password:

database\build.bat -h localhost -p 5432 -U postgres -P yourpassword

3. Create Storage Directories

The app stores uploaded files on the filesystem. Create the data directories if they don't exist:

mkdir -p data/projects data data/embeds

These paths correspond to the PROJECTS_DIR, STATS_DIR, and EMBEDS_DIR settings in .env.server. Defaults (relative to project root): PROJECTS_DIR=data/projects, STATS_DIR=data, EMBEDS_DIR=data/embeds.


4. Start the Server

Set the deploy mode and start:

Windows PowerShell:

$env:DEPLOY_MODE = "server"
python serve.py --host 127.0.0.1 --port 5000

macOS/Linux:

export DEPLOY_MODE=server
python serve.py --host 127.0.0.1 --port 5000

The server listens on 127.0.0.1:5000 by default. Caddy (configured in step 5) handles external traffic and TLS.

Options:

--host HOST       Bind address (default: 127.0.0.1)
--port PORT       Bind port (default: 5000)
--workers N       Gunicorn worker count, Linux/macOS only (default: 4)

On Linux/macOS the server uses Gunicorn. On Windows it uses Waitress. Both are included in requirements-cpu.txt.


5. Set Up Caddy (Reverse Proxy + HTTPS)

Caddy handles HTTPS certificate provisioning and reverse-proxies requests to Flask.

Install Caddy

Edit the Caddyfile

Open Caddyfile in the project root and replace the example domains with your own:

yourdomain.com {
    reverse_proxy 127.0.0.1:5000
}

For LAN access with a self-signed certificate (browsers will warn unless you install Caddy's root CA):

# Optional — replace with your server's LAN IP
192.168.1.x {
    tls internal
    reverse_proxy 127.0.0.1:5000
}

Run Caddy

From the project root:

caddy run

Or as a background service:

caddy start

Caddy automatically obtains a Let's Encrypt certificate for your domain on the first request. Your domain must already point to the server's public IP in DNS.


6. Make Your First Admin User

After the first user registers through Firebase, grant them admin privileges.

Find their Firebase UID from either:

  • The Firebase Console → Authentication → Users
  • The users table in PostgreSQL: SELECT uid, email FROM users;

Then either:

Option A — Edit .env.server:

ADMIN_UIDS=uid_here

Restart the server for the change to take effect.

Option B — Use the helper script:

python scripts/add_admin_user.py --email user@example.com --firebase-uid <uid>

7. Running as a System Service (Linux)

To keep the server running after SSH logout, create a systemd service.

Create /etc/systemd/system/sourcequote.service:

[Unit]
Description=SourceQuote
After=network.target postgresql.service

[Service]
User=youruser
WorkingDirectory=/path/to/transcriber
Environment=DEPLOY_MODE=server
ExecStart=/path/to/transcriber/.venv/bin/python serve.py --host 127.0.0.1 --port 5000
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Enable and start it:

sudo systemctl daemon-reload
sudo systemctl enable sourcequote
sudo systemctl start sourcequote

8. Optional: Enable Rate Limiting with Redis

By default, rate limiting uses in-process memory, which does not persist across restarts and is not safe for multi-process deployments. For production use with multiple workers, configure Redis:

  1. Install Redis: sudo apt install redis-server
  2. Set in .env.server:
    RATELIMIT_STORAGE_URI=redis://localhost:6379/0
    

9. Optional: Stripe Payments

To enable subscription billing:

  1. Create a Stripe account and obtain API keys (see ENVIRONMENT_SETUP.md § 9)
  2. Set STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY, and STRIPE_WEBHOOK_SECRET in .secrets.server
  3. Set STRIPE_BETA_MODE=0 in .env.server when ready to accept real charges
  4. Create a webhook endpoint in the Stripe Dashboard pointing to https://yourdomain.com/api/stripe/webhook

10. Verify the Deployment

  1. Navigate to https://yourdomain.com — the landing page should load over HTTPS
  2. Sign in with a Firebase account — the app should open after login
  3. Check the admin panel at https://yourdomain.com/admin (admin users only)

Configuration Reference

FilePurpose
config/server/.env.serverNon-secret settings (committed OK if no credentials)
config/server/.secrets.serverCredentials and API keys (never commit)
CaddyfileCaddy reverse proxy config
serve.pyWSGI launcher (Gunicorn/Waitress)
database/build.bat / build.shSchema initialiser