T-HUB/infra/occupymedia/DEPLOY.md
glass beadian aa5e262f3a Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
2026-10-10 11:56:42 +00:00

6 KiB

Deploy — occupymedia.cc

Storage phase-one addendum: the current deployment uses separate app/chat/ PeerTube CTs. New files/docs work is an independent, undeployed scaffold; see files runbook and storage contracts. The older one-box instructions below do not deploy that stack. Live conduwuit v0.5.0 still has local media; its volumes require backup. R2 durability alone does not back up media or the metadata needed to resolve it.

One box, everything behind the Cloudflare tunnel (already provisioned: zone occupymedia.cc, tunnel occupymedia with ingress app/api/tube/chat, R2 buckets occupymedia-media-encrypted, -peertube-videos, -peertube-playlists, -matrix-media). No inbound ports. All local ports bind to 127.0.0.1.

WP3 release on the app CT (150)

Use docker compose -f docker-compose.app.yml in this directory. Before syncing source, create and verify an owner pg_dump in /opt/tactical-media-broadcast-hub/backups/. Preserve the previous source for rollback.

Both DATABASE_URL and SCHEDULER_DATABASE_URL in the CT .env must connect as sp_app, without superuser or BYPASSRLS privileges. Provision its password with owner access and keep real values only in the CT .env. Keep the existing SUPERPOSTER_KEK; scheduler inherits it and TEMPORAL_ADDRESS.

Stop the old scheduler, then run the new API migration CLI offline with SUPER_DATABASE_URL set to the owner URL: pnpm --filter @tactical-media-broadcast-hub/api exec tsx src/db/migrate.cli.ts --dir migrations. The CLI skips recorded migrations; 019 tracks resumable transactional, batch, and concurrent-index phases. Confirm 018 and 019 in schema_migrations and check assertPublishingSchema using the runtime URL before starting new code.

Build api scheduler web, then run up -d --no-deps api, followed by scheduler and web. Verify API health/readiness, invite regressions, an existing username/password login, scheduler readiness and Temporal connectivity, and the public web app. Defer production publish tests until explicitly requested. For rollback, restore source from the previous commit, re-sync, rebuild and restart the three services; do not undo migrations automatically.

0. Prerequisites on the box

  • Debian 12 / Ubuntu 24.04, 4+ vCPU, 8 GB RAM, 100 GB disk
  • Docker Engine + compose plugin (curl -fsSL https://get.docker.com | sh)
  • git

1. Get the code + secrets

git clone git@gitlab.example.com:mediatools/tacticalmediahub.git
cd tactical-media-broadcast-hub/infra/occupymedia

cp .env.example .env
# Fill: CLOUDFLARE_TUNNEL_TOKEN, R2_ACCESS_KEY, R2_SECRET_KEY (dashboard → R2 →
# Manage API Tokens → Object Read & Write, scoped to the occupymedia-* buckets),
# POSTGRES_PASSWORD, PEERTUBE_DB_PASSWORD, S3_KEK (openssl rand -hex 32 — ONE TIME,
# it wraps every stored blob; lose it = archive unreadable)

# peertube.env: set both PEERTUBE_DB_PASSWORD / POSTGRES_PASSWORD lines to the
# value from .env, and set a registration/SMTP sender if you have them.

2. Build + start the core

docker compose build api scheduler web media-worker
docker compose up -d postgres redis temporal peertube-postgres

3. Apply API migrations (ordered, once)

cd ../..
for f in apps/api/migrations/*.sql; do
  docker compose -f infra/occupymedia/docker-compose.yml exec -T postgres \
    psql -U "$(grep -oP '(?<=POSTGRES_USER:)\S+' infra/occupymedia/docker-compose.yml | head -1 | tr -d '${:-occupymedia}')" \
    -d occupymedia -v ON_ERROR_STOP=1 < "$f" || { echo "FAILED at $f"; break; }
done

(If psql errors on an already-applied migration — re-runs are idempotent-safe for most files; check the failing file before continuing.)

4. Bring everything up

docker compose -f infra/occupymedia/docker-compose.yml up -d
docker compose ps   # everything Up/healthy

5. First-boot steps

  1. Tunnel: docker compose logs cloudflared — should show 4 connections registered. Then https://app.occupymedia.cc (control room), /healthz on api., tube., chat. should all answer.
  2. PeerTube admin: docker compose logs peertube 2>&1 | grep -i -A3 password — random root password printed ONCE. Log in at tube.occupymedia.cc, change it, then run the brand sync (docs/peertube-setup.md) and create the live channel.
  3. Matrix admin: register the first account at chat.occupymedia.cc with the token from conduwuit.toml, then set allow_registration = false and docker compose restart conduwuit. Configure Element as client (docs/matrix-setup.md).
  4. Telegram bridge (optional now): docker compose --profile bridges run --rm mautrix-telegram → interactive login with the bot/user token; then docker compose --profile bridges up -d mautrix-telegram.

6. Live ingest (deferred)

RTMP/SRT cannot traverse the tunnel. Options documented in the session notes: tiny relay VPS + WireGuard to this box (recommended; keeps the home IP out of DNS), or direct port-forward. Until then the 24/7 channel runs playlists/VOD (media-worker pushes to PeerTube internally over the docker network).

7. Operations

  • Backup: docker compose exec postgres pg_dump -U occupymedia occupymedia | gzip > backup-$(date +%F).sql.gz + tar the named volumes (pgdata, peertube_pgdata, conduwuit_data). Media itself is in R2 (durable) — KEK backup matters more than volume backup.
  • Update: git pull && docker compose build api scheduler web media-worker && docker compose up -d (migrations: repeat step 3 for NEW files only).
  • Logs: docker compose logs -f --tail=100 <service>.

Known simplifications (first-production-pass)

  • observability (otel/prometheus/grafana) not included — add from infra/docker-compose.yml services when needed
  • single app instance per service (no horizontal scaling — intended at this size)
  • file-sharing/collab layer: oCIS S3NG + optional Collabora scaffold in files/compose.yml; deployment and Matrix migration remain separate prerequisites