bzl

self-hosted ephemeral community engine
Log | Files | Refs | README | LICENSE

commit b692a17ba440b0dd06ba4a7495fbf36bc621379f
parent cdb30c2657d8c3ab69fb0aa9fde56b99fc53892d
Author: SageAzakaela <106701693+SageAzakaela@users.noreply.github.com>
Date:   Sat, 21 Feb 2026 17:19:21 -0700

update docs

Diffstat:
MREADME.md | 1+
Adocs/DIGITALOCEAN_DEPLOYMENT_AND_COST.md | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdocs/INSTANCE_FLEET_AUTOMATION.md | 87++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mdocs/MULTI_INSTANCE_DOCKER.md | 93++++++++++++++++++++++++++++++++-----------------------------------------------
Mdocs/SERVER_UPDATE.md | 161++++++++++++++++---------------------------------------------------------------
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`