commit b692a17ba440b0dd06ba4a7495fbf36bc621379f
parent cdb30c2657d8c3ab69fb0aa9fde56b99fc53892d
Author: SageAzakaela <106701693+SageAzakaela@users.noreply.github.com>
Date: Sat, 21 Feb 2026 17:19:21 -0700
update docs
Diffstat:
5 files changed, 317 insertions(+), 220 deletions(-)
diff --git a/README.md b/README.md
@@ -38,6 +38,7 @@ Media uploads:
- Self-hosted installer plan: `docs/SELF_HOSTED_INSTALLER_PLAN.md`
- Multi-instance docker stack: `docs/MULTI_INSTANCE_DOCKER.md`
- Instance fleet automation (discovery + bulk update): `docs/INSTANCE_FLEET_AUTOMATION.md`
+- DigitalOcean + domain deployment and monthly cost guide: `docs/DIGITALOCEAN_DEPLOYMENT_AND_COST.md`
- Issue tracker guide: `docs/ISSUE_TRACKER.md`
- Updating a live server (git + docker): `docs/SERVER_UPDATE.md`
diff --git a/docs/DIGITALOCEAN_DEPLOYMENT_AND_COST.md b/docs/DIGITALOCEAN_DEPLOYMENT_AND_COST.md
@@ -0,0 +1,195 @@
+# Bzl on DigitalOcean with a Domain (Setup + Cost Guide)
+
+Last updated: February 22, 2026
+
+This guide covers:
+- starting Bzl on a DigitalOcean droplet
+- attaching a domain with Caddy HTTPS
+- running multiple Bzl instances on one droplet
+- estimating monthly cost based on usage
+
+---
+
+## 1) Provision the droplet
+
+Recommended baseline for production:
+- Ubuntu 24.04 LTS
+- 2 vCPU / 2 GB RAM (or higher if you run multiple active instances)
+- Docker + Docker Compose installed
+
+On first login:
+
+```bash
+apt update && apt upgrade -y
+apt install -y git curl
+```
+
+If Docker is not installed yet:
+
+```bash
+curl -fsSL https://get.docker.com | sh
+```
+
+---
+
+## 2) Deploy a single Bzl instance
+
+```bash
+cd /root
+git clone https://github.com/bzlapp/Bzl.git
+cd /root/Bzl
+cp .env.example .env
+```
+
+Edit `.env` and set at least:
+- `PORT=3000`
+- `HOST=0.0.0.0`
+- `REGISTRATION_CODE=<your-code>`
+
+Then start:
+
+```bash
+docker compose up -d --build --remove-orphans
+curl -fsS http://127.0.0.1:3000/api/health
+```
+
+---
+
+## 3) Point your domain to the droplet
+
+Create DNS `A` records:
+- `chat.example.com -> <droplet_public_ip>`
+- (optional stream hostname) `stream.chat.example.com -> <droplet_public_ip>`
+
+If you use Cloudflare:
+- Keep normal web hostnames proxied if you want Cloudflare proxy features.
+- For TURN/real-time UDP endpoints, use **DNS only**.
+
+---
+
+## 4) Configure Caddy HTTPS reverse proxy
+
+Install Caddy if needed:
+
+```bash
+apt install -y debian-keyring debian-archive-keyring apt-transport-https
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
+curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
+apt update && apt install -y caddy
+```
+
+Example `/etc/caddy/Caddyfile`:
+
+```caddy
+chat.example.com {
+ reverse_proxy 127.0.0.1:3000
+}
+```
+
+Apply:
+
+```bash
+caddy fmt --overwrite /etc/caddy/Caddyfile
+systemctl reload caddy
+```
+
+---
+
+## 5) Add more Bzl instances on the same droplet
+
+Create each instance:
+
+```bash
+cd /root/Bzl
+npm run instance:create -- --path=/opt/bzl-community1 --port=3002 --registration-code='community1' --hostname=community1.example.com
+npm run instance:create -- --path=/opt/bzl-community2 --port=3003 --registration-code='community2' --hostname=community2.example.com
+```
+
+Then add Caddy routes:
+
+```caddy
+community1.example.com {
+ reverse_proxy 127.0.0.1:3002
+}
+
+community2.example.com {
+ reverse_proxy 127.0.0.1:3003
+}
+```
+
+Reload Caddy and verify each `/api/health`.
+
+---
+
+## 6) Update all instance folders at once
+
+```bash
+cd /root/Bzl
+npm ci --omit=dev
+npm run instances:update -- --roots=/root,/srv,/opt,/home --max-depth=7 --dry-run
+npm run instances:update -- --roots=/root,/srv,/opt,/home --max-depth=7
+```
+
+Fast restart without pull/build:
+
+```bash
+npm run instances:update -- --roots=/root,/srv,/opt,/home --max-depth=7 --skip-git --skip-build
+```
+
+---
+
+## 7) Monthly cost model (DigitalOcean)
+
+Use this formula:
+
+`monthly_total = droplet + backups(optional) + block_storage(optional) + transfer_overage(optional) + domain`
+
+### Droplet baseline pricing inputs
+
+DigitalOcean Basic Droplets are shown at:
+- `$4/mo` (1 vCPU / 512 MiB / 10 GiB SSD / 500 GiB transfer)
+- `$6/mo` (1 vCPU / 1 GiB / 25 GiB SSD / 1,000 GiB transfer)
+- `$12/mo` (1 vCPU / 2 GiB / 50 GiB SSD / 2,000 GiB transfer)
+
+Bandwidth overage is listed as `$0.01/GiB` after included transfer.
+
+Optional services:
+- Droplet backups: `20%` of droplet price
+- Block Storage: starts at `$10/month`
+
+### Example monthly scenarios
+
+1. Small private community (single instance)
+- Droplet: `$6`
+- Backups: `$1.20`
+- Total infra (before domain): **`$7.20/month`**
+
+2. Multiple communities on one droplet (3-4 low/medium traffic instances)
+- Droplet: `$12`
+- Backups: `$2.40`
+- Total infra (before domain): **`$14.40/month`**
+
+3. Heavier usage with extra storage
+- Droplet: `$24` (or higher tier)
+- Backups: `$4.80`
+- Block storage: `$10`
+- Total infra (before domain/overage): **`$38.80/month`**
+
+Domain cost depends on registrar + TLD and is billed separately.
+
+---
+
+## 8) Cost control tips
+
+- Start at one droplet tier up from your minimum acceptable RAM.
+- Enable backups only for instances you cannot quickly recreate.
+- Keep uploads bounded with Bzl upload limits and retention policies.
+- Watch transfer and disk usage monthly; upgrade before CPU/RAM saturation.
+
+---
+
+## References
+
+- DigitalOcean Droplet pricing: https://www.digitalocean.com/pricing/droplets
+- DigitalOcean pricing calculator: https://www.digitalocean.com/pricing/calculator
+- DigitalOcean bandwidth overage + backups + storage examples: https://docs.digitalocean.com/products/droplets/details/pricing/
diff --git a/docs/INSTANCE_FLEET_AUTOMATION.md b/docs/INSTANCE_FLEET_AUTOMATION.md
@@ -1,65 +1,80 @@
-# Instance Fleet Automation (Detect + Update + Create)
+# Instance Fleet Automation (Detect, Create, Update, Restart)
-Use this when you run multiple Bzl clones in separate folders on one server (for example `/Bzl`, `/srv/bzl-staging`, `/opt/community/Bzl`).
+Use this when you run several Bzl repos on one server and want one workflow for all of them.
-## 1) Detect instances from root paths
-
-List discovered Bzl instances:
+## 1) Detect Bzl instances
```bash
-npm run instances:scan -- --roots=/ --max-depth=4
+cd /root/Bzl
+npm run instances:scan -- --roots=/root,/srv,/opt,/home --max-depth=7
```
-By default, detection combines:
-- filesystem scan for compose projects with Bzl-like signals (`bzl` in path/name or Bzl source markers)
-- Docker label scan (`com.docker.compose.project.working_dir`) so running instances are found even if the folder is compose-only
+Detection sources:
+- Filesystem scan for compose projects and Bzl source markers
+- Docker labels (`com.docker.compose.project.working_dir`) so compose-only folders can still be found
-Supported compose filenames include:
+Supported compose filenames:
- `compose.yaml`
- `compose.yml`
- `docker-compose.yml`
- `docker-compose.yaml`
-## 2) Update all discovered instances
-
-Bulk update:
+## 2) Create a new instance folder
```bash
-npm run instances:update -- --roots=/ --max-depth=4
+cd /root/Bzl
+npm run instance:create -- --path=/opt/bzl-new --port=3405 --registration-code='replace-me' --hostname=new.example.com
```
-This performs, per instance:
-1. `git fetch`
-2. `git checkout main`
-3. `git pull --ff-only origin main`
-4. `docker compose -f <compose-file> up -d --build --remove-orphans`
+What this does:
+- Clones `https://github.com/bzlapp/Bzl.git` (`main`) into `--path`
+- Writes `.env` with `PORT`, `HOST`, and `REGISTRATION_CODE`
+- Starts Docker Compose unless `--no-start` is used
+
+Important:
+- `--path` must be empty
+- `--port` must be unique per instance
+
+Useful flags:
+- `--repo=...`
+- `--branch=...`
+- `--no-start`
+- `--dry-run`
+
+## 3) Update all instances to latest `main`
+
+```bash
+cd /root/Bzl
+npm run instances:update -- --roots=/root,/srv,/opt,/home --max-depth=7 --dry-run
+npm run instances:update -- --roots=/root,/srv,/opt,/home --max-depth=7
+```
Useful flags:
-- `--skip-git`
-- `--skip-build`
+- `--skip-git` (restart/recreate only)
+- `--skip-build` (skip image build)
- `--branch=main`
- `--remote=origin`
- `--dry-run`
-## 3) Create a new instance in a new folder
-
-Provision a fresh clone + `.env` + docker startup:
+## 4) Restart all instances (fast path)
```bash
-npm run instance:create -- --path=/srv/bzl-new --port=3405 --registration-code='replace-me' --hostname=new.example.com
+cd /root/Bzl
+npm run instances:update -- --roots=/root,/srv,/opt,/home --max-depth=7 --skip-git --skip-build
```
-Default repo source is `https://github.com/bzlapp/Bzl.git` on `main`.
+## 5) Verify each instance
-Useful flags:
-- `--repo=...`
-- `--branch=...`
-- `--no-start`
-- `--dry-run`
+```bash
+docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}'
+curl -fsS http://127.0.0.1:<port>/api/health
+```
+
+## DNS + reverse proxy reminder
-## Caddy + DNS reminders
+For each hostname:
+1. DNS `A` record points to the server IP
+2. Caddy routes hostname to the instance port
+3. Public HTTPS URL returns app + `/api/health`
-After creating/updating instances:
-- ensure each hostname reverse proxies to the correct local port
-- verify `curl http://127.0.0.1:<port>/api/health`
-- confirm public hostname routes to the expected instance
+See also: `docs/DIGITALOCEAN_DEPLOYMENT_AND_COST.md`
diff --git a/docs/MULTI_INSTANCE_DOCKER.md b/docs/MULTI_INSTANCE_DOCKER.md
@@ -1,77 +1,55 @@
-# Multi-instance Docker Setup (Single Server)
+# Multi-Instance Docker Stack (Single Compose Project)
-This workflow lets you run multiple Bzl instances on one host, each with its own:
-- persistent data volume
-- hostname
-- registration code
+This workflow runs many Bzl instances from one generated compose file (`multi_instance/docker-compose.yml`).
-It also supports generating Cloudflare tunnel ingress config and optional automated `cloudflared tunnel route dns` calls.
+Use this when you want:
+- one config file for all instances
+- one command to start all instances
+- one command to update all instances
----
-
-## 1) Create/edit config
-
-Run once:
+## 1) Create or edit the config
```bash
+cd /root/Bzl
npm run multi:init
```
-If `multi_instance/instances.json` does not exist, the script creates a template and exits.
-
-Edit:
-- `multi_instance/instances.json`
+If `multi_instance/instances.json` does not exist, a template is created.
Key fields:
-- `instances[].id` - stable identifier (used for service/env filenames)
-- `instances[].hostname` - public hostname for that instance
-- `instances[].hostPort` - unique localhost port each instance maps to
-- `instances[].registrationCode` - per-instance registration code
-- `cloudflared.*` - tunnel config + optional DNS route automation
+- `instances[].id`: stable ID for env/service naming
+- `instances[].hostname`: public hostname
+- `instances[].hostPort`: unique local port
+- `instances[].registrationCode`: per-instance registration code
----
-
-## 2) Generate compose + env + DNS checklist
+## 2) Generate compose + env outputs
```bash
+cd /root/Bzl
npm run multi:init
```
-Generated files:
+Generated:
- `multi_instance/docker-compose.yml`
-- `multi_instance/env/<id>.env` (one per instance)
+- `multi_instance/env/<id>.env`
- `multi_instance/DNS_CHECKLIST.md`
-- Cloudflared config (path from `cloudflared.configPath`, default `~/.cloudflared/config.yml`)
-Optional DNS automation:
+Optional (Cloudflare tunnel DNS automation):
```bash
npm run multi:init -- --route-dns
```
-This runs `cloudflared tunnel route dns ...` for each configured hostname.
-
----
-
-## 3) Start all instances
+## 3) Start all configured instances
```bash
docker compose -f multi_instance/docker-compose.yml up -d --build --remove-orphans
```
-Then verify local health endpoints from the host:
-
-```bash
-curl -fsS http://127.0.0.1:<hostPort>/api/health
-```
-
----
-
-## 4) Update all instances to latest source-of-truth
-
-Use the updater script from the repo root:
+## 4) Update all configured instances
```bash
+cd /root/Bzl
npm run multi:update
```
@@ -79,22 +57,25 @@ Default behavior:
1. `git fetch origin`
2. `git checkout main`
3. `git pull --ff-only origin main`
-4. regenerate multi-instance config outputs
+4. regenerate compose/env outputs
5. `docker compose -f multi_instance/docker-compose.yml up -d --build --remove-orphans`
-Options:
-- `--skip-git` (skip fetch/pull)
-- `--skip-build` (restart/update without image rebuild)
-- `--route-dns` (also rerun DNS route commands during regeneration)
-- `--dry-run` (show the docker command without executing it)
+Useful flags:
+- `--skip-git`
+- `--skip-build`
+- `--route-dns`
+- `--dry-run`
- `--config=/path/to/instances.json`
----
+## 5) Validate routing
+
+```bash
+curl -fsS http://127.0.0.1:<hostPort>/api/health
+```
+
+Then verify each public hostname resolves to the expected instance.
-## DNS reminders
+## When to use this vs fleet automation
-After generating or changing hostnames:
-- ensure each hostname is routed to the intended tunnel (`cloudflared tunnel route dns ...`)
-- confirm Cloudflare SSL/TLS mode is compatible (Full / Full strict recommended)
-- restart tunnel process/service if ingress config changed
-- verify each hostname reaches the expected instance (`/api/health`)
+- Use this doc (`multi:*`) if you intentionally keep all instances in one managed stack.
+- Use `docs/INSTANCE_FLEET_AUTOMATION.md` if your instances are spread across independent folders/projects.
diff --git a/docs/SERVER_UPDATE.md b/docs/SERVER_UPDATE.md
@@ -1,161 +1,66 @@
-# Updating a Live Bzl Server (Git + Docker)
+# Updating a Live Bzl Server
-This doc is for a typical droplet setup where you:
-- have the `bzlapp/Bzl` repo checked out on the server (example: `~/Bzl`)
-- run Bzl via Docker / Docker Compose using that checkout (builds the image from the local Dockerfile)
+This is the standard update flow when Bzl is running from a git checkout + Docker Compose on your host.
-If you’re not sure which setup you have, run `docker ps` and see whether Bzl is running as a container.
-
----
-
-## The “golden” update flow (Compose + local build)
-
-From the droplet host (not inside the container):
+## Single instance update (quick path)
```bash
cd ~/Bzl
-
-# 1) make sure you're on the right branch and clean
git status
git checkout main
-
-# 2) pull latest code
git fetch origin
git pull --ff-only origin main
-
-# 3) rebuild + recreate container(s)
-docker compose up -d --build
-```
-
-That’s it.
-
-### Why this order matters
-- If you rebuild before pulling, you rebuild the *old* code.
-- A `git pull` does **not** update a running container unless you rebuild/recreate it (or you’re bind-mounting the code into the container).
-
----
-
-## Restart only (no code update)
-
-```bash
-docker compose restart
-```
-
-Or, if you run a single container named `bzl`:
-
-```bash
-docker restart bzl
-```
-
----
-
-## Multi-instance stack update (single host, many Bzl instances)
-
-If you run multiple Bzl instances from `multi_instance/docker-compose.yml`, use:
-
-```bash
-npm run multi:update
-```
-
-This script pulls latest `main`, regenerates per-instance env/compose from `multi_instance/instances.json`, and runs:
-
-```bash
-docker compose -f multi_instance/docker-compose.yml up -d --build --remove-orphans
-```
-
-See: `docs/MULTI_INSTANCE_DOCKER.md`
-
----
-
-## Auto-detect + update all Bzl clones across server folders
-
-If your instances live in separate folders (for example one in `/Bzl` and another elsewhere), use fleet automation:
-
-```bash
-# detect instances first
-npm run instances:scan -- --roots=/ --max-depth=4
-
-# then update all detected instances
-npm run instances:update -- --roots=/ --max-depth=4
+docker compose up -d --build --remove-orphans
```
-To create a brand-new instance folder:
+## Update every detected Bzl instance on the host
-```bash
-npm run instance:create -- --path=/srv/bzl-new --port=3405 --registration-code='replace-me' --hostname=new.example.com
-```
-
-See: `docs/INSTANCE_FLEET_AUTOMATION.md`
-
----
-
-## Common git mistake (your screenshot)
-
-If you run:
+Use this when you have multiple instance folders (for example `/root/Bzl`, `/opt/bzl-tawky`, `/opt/bzl-unianetwork`).
```bash
-git pull main
-```
-
-Git interprets `main` as a *remote name* (not a branch), and you’ll get:
-`fatal: 'main' does not appear to be a git repository`
+cd /root/Bzl
+npm ci --omit=dev
-Use this instead:
+# preview only
+npm run instances:update -- --roots=/root,/srv,/opt,/home --max-depth=7 --dry-run
-```bash
-git pull --ff-only origin main
+# execute
+npm run instances:update -- --roots=/root,/srv,/opt,/home --max-depth=7
```
-Quick sanity checks:
-
-```bash
-git remote -v
-git branch --show-current
-git log -1 --oneline
-```
-
----
-
-## If you have local changes on the droplet
-
-If `git status` shows modified files, decide whether you want to keep them.
+The updater runs per instance:
+1. `git fetch`
+2. `git checkout main`
+3. `git pull --ff-only origin main`
+4. `docker compose up -d --build --remove-orphans`
-To throw away local changes and match GitHub exactly:
+## Restart all detected instances (no pull, no rebuild)
-```bash
-git fetch origin
-git reset --hard origin/main
-```
-
-Then rebuild:
+Use this if you only changed env values or need a service restart.
```bash
-docker compose up -d --build
+cd /root/Bzl
+npm run instances:update -- --roots=/root,/srv,/opt,/home --max-depth=7 --skip-git --skip-build
```
----
+## Common failures
-## Confirm you’re actually updated
+- `fatal: not a git repository`: you are in the wrong folder. `cd` into the repo root first.
+- `npm: command not found`: install Node.js/npm on the host, then retry.
+- `Target path is not empty` during `instance:create`: use an empty folder, or create manually in an existing clone.
-After updating/restarting:
+## Validation checklist
```bash
docker ps
-docker logs --tail 80 bzl
+curl -fsS http://127.0.0.1:<PORT>/api/health
+docker logs --tail 80 <container-name>
```
-In the UI, the Moderation → Server panel should reflect the latest version string.
-
-If the UI still looks unchanged, hard-refresh your browser:
-- Chrome/Edge: `Ctrl+Shift+R`
-
----
-
-## Next step (recommended): in-app core updates
+Replace `<PORT>` and `<container-name>` per instance.
-For “Clean Install” desktop setups, the Launcher UI can already do opt-in updates via GitHub Releases.
+## Related docs
-For live servers, we can add a similar admin-only “Update core” flow (pull + rebuild + restart) later, but it needs extra safety:
-- confirmation prompts + backup
-- clear logs of what changed
-- no “surprise” updates
+- `docs/INSTANCE_FLEET_AUTOMATION.md`
+- `docs/MULTI_INSTANCE_DOCKER.md`
+- `docs/DIGITALOCEAN_DEPLOYMENT_AND_COST.md`