Skip to content

# Installation

Need help getting set up?

Bunker Operations provides managed infrastructure and hands-on setup assistance for organizations running Changemaker Lite. We handle domains, tunnels, SMTP, and servers so you can focus on your campaign. Get in touch: bnkops.com | `admin@bnkops.ca`

Changemaker Lite runs as a set of Docker containers orchestrated by Docker Compose. The `config.sh` wizard handles all configuration — or you can set things up manually.

Have your external services ready?

For a production deployment, you'll need a domain name, SMTP email provider, and a reverse tunnel (like Pangolin) or public IP with SSL. Gather these before running the wizard — it makes the process much smoother.

Prerequisites & External Services — full checklist with provider recommendations

For local development/evaluation, you can skip this — Docker and MailHog handle everything out of the box.

## Prerequisites

- Docker 24+ and Docker Compose v2
- OpenSSL (for secret generation)
- A Linux server (Ubuntu 22.04+ recommended) or macOS for development
- At least 2 GB RAM for core services, 4 GB for the full stack
- A domain name (optional for development, recommended for production)
- An SMTP provider for production email delivery (see Prerequisites)
- A reverse tunnel or public IP for internet access (see Prerequisites)

## Quick Start

Clone the repository:

    git clone https://gitea.bnkops.com/admin/changemaker.lite
    cd changemaker.lite

Run the configuration wizard:

    bash config.sh

Start all services:

    docker compose up -d

Open http://localhost:3000 and sign in with the admin credentials you configured. Database migrations and seeding run automatically on first startup.

Change your password

If you used the wizard's generated password, change it immediately from the admin dashboard.

## Pre-built Image Installation

For production deployments, you can skip cloning the source repository entirely. Pre-built Docker images are pulled from the Gitea container registry.

### One-Line Install

    curl -fsSL https://gitea.bnkops.com/admin/changemaker.lite/raw/branch/main/scripts/install.sh | bash

This script:

1. Checks prerequisites (Docker, Docker Compose, OpenSSL)
2. Checks host port availability — aborts early if any of the ~14 required ports (3000, 4000, 9090, 3030, etc.) are already in use. Prints culprit-specific hints (e.g. cockpit.socket on :9090)
3. Verifies at least 10 GB free disk
4. Downloads the latest release package from Gitea
5. Extracts to `~/changemaker.lite/`
6. Launches the configuration wizard (`config.sh`)

After the wizard completes, start everything with `docker compose up -d`, then verify with `bash scripts/test-deployment.sh --wait 60`.

#### Installer Flags

The installer accepts a few options for non-default setups. Download the script first, inspect it, then run with flags:

    curl -fsSLO https://gitea.bnkops.com/admin/changemaker.lite/raw/branch/main/scripts/install.sh
    bash install.sh --help

Flag | Purpose
--- | ---
`--dir DIR` | Install into a directory other than `~/changemaker.lite`
`--version TAG` | Pin a specific release (e.g. `v2.9.10`) instead of latest
`--tarball FILE` | Use a local tarball — skip the Gitea download entirely

### Manual Download

If you prefer not to pipe to bash:

    # Download latest release
    curl -LO https://gitea.bnkops.com/admin/changemaker.lite/releases/latest/download/changemaker-lite-latest.tar.gz
    tar xzf changemaker-lite-latest.tar.gz
    cd changemaker-lite
    bash config.sh
    docker compose up -d

### What's Different from Source Install

| Source Install | Pre-built Install
--- | --- | ---
Download size | ~200 MB (full repo) | ~15 MB (config, compose, MkDocs content)
First startup | 10+ min (TypeScript compile + Docker build) | ~2 min (image pull only)
Requires | Git, full repo | Docker only
Upgrades | `git pull` + rebuild | Download new release tarball
Development | Edit source, hot-reload | Not for development

When to use which

Use pre-built install for production deployments and quick evaluation. Use source install when you want to modify the platform code or contribute to development.

## Configuration Wizard (`config.sh`)

The wizard performs 14 steps to produce a fully configured `.env` file and prepare the system for startup. Each step is interactive with sensible defaults.

### Step 1: Prerequisites Check

Verifies that Docker, Docker Compose v2, and OpenSSL are installed. Exits immediately if any are missing, with links to installation guides.

### Step 2: Environment File Setup

- If no `.env` exists, copies `.env.example` as the starting point
- If `.env` already exists, offers to back it up (timestamped copy) and create a fresh one, or update values in place

### Step 3: Domain Configuration

Prompts for your root domain (default: `cmlite.org`) and derives 14 environment variables from it:

Variable | Example Value
--- | ---
`DOMAIN` | `example.org`
`BASE_DOMAIN` | `https://example.org`
`GITEA_ROOT_URL` | `https://git.example.org`
`GITEA_DOMAIN` | `git.example.org`
`N8N_HOST` | `n8n.example.org`
`SMTP_FROM` | `noreply@example.org`
`INITIAL_ADMIN_EMAIL` | `admin@example.org`
`NC_ADMIN_EMAIL` | `admin@example.org`
`EXCALIDRAW_WS_URL` | `wss://draw.example.org`
`LISTMONK_SMTP_FROM` | `Changemaker Lite <noreply@example.org>`
`HOMEPAGE_VAR_BASE_URL` | `https://example.org`
`VAULTWARDEN_DOMAIN` | `https://vault.example.org`
`GANCIO_BASE_URL` | `https://events.example.org`
`TEST_EMAIL_RECIPIENT` | `admin@example.org`

Also updates `mkdocs/mkdocs.yml` with the new `site_url` and `repo_url`, and asks whether this is a production deployment (sets `NODE_ENV=production`).

### Step 4: Admin Credentials

Prompts for the initial super-admin email and password. The password is validated against the security policy:

- Minimum 12 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one digit
- Requires password confirmation

Non-interactive password

If you run with `-y` and omit `--admin-password`, the wizard generates a strong password and also writes it to `data/admin-credentials.txt` with mode 0600. Retrieve it with `cat ~/changemaker.lite/data/admin-credentials.txt`, then delete the file once saved elsewhere (e.g. Vaultwarden). Explicit `--admin-password` is never persisted to disk by `config.sh`.

### Step 5: Secret Generation

Auto-generates 21 unique secrets — no placeholder passwords remain after this step:

Category | Count | Secrets
--- | --- | ---
JWT & Encryption | 4 | `JWT_ACCESS_SECRET`, `JWT_REFRESH_SECRET`, `JWT_INVITE_SECRET` (each 64-char hex), `ENCRYPTION_KEY` (64-char hex, must differ from JWT secrets)
Database | 2 | `V2_POSTGRES_PASSWORD`, `REDIS_PASSWORD` (24-char alphanumeric)
Listmonk | 3 | `LISTMONK_DB_PASSWORD`, `LISTMONK_WEB_ADMIN_PASSWORD`, `LISTMONK_API_TOKEN`
NocoDB | 1 | `NC_ADMIN_PASSWORD`
Gitea | 2 | `GITEA_DB_PASSWD`, `GITEA_DB_ROOT_PASSWORD`
n8n | 2 | `N8N_ENCRYPTION_KEY`, `N8N_USER_PASSWORD`
Monitoring | 2 | `GRAFANA_ADMIN_PASSWORD`, `GOTIFY_ADMIN_PASSWORD`
Vaultwarden | 1 | `VAULTWARDEN_ADMIN_TOKEN` (64-char hex)
Rocket.Chat | 1 | `ROCKETCHAT_ADMIN_PASSWORD`
Gancio | 1 | `GANCIO_ADMIN_PASSWORD`
Jitsi Meet | 3 | `JITSI_APP_SECRET` (64-char hex), `JITSI_JICOFO_AUTH_PASSWORD`, `JITSI_JVB_AUTH_PASSWORD`

### Step 6: Email Configuration

Choose between:

- MailHog (default) — captures all outgoing emails at `http://localhost:8025` for development
- Production SMTP — configures host, port, user, and password. Optionally shares credentials with Listmonk for newsletter delivery

### Step 7: Feature Flags

Enable or disable 9 optional platform features:

Flag | Environment Variable | What It Enables
--- | --- | ---
Media Manager | `ENABLE_MEDIA_FEATURES=true` | Video library, analytics, scheduled publishing
Listmonk Sync | `LISTMONK_SYNC_ENABLED=true` | Newsletter subscriber sync from platform participants
Payments | `ENABLE_PAYMENTS=true` | Stripe-based products, donations, and plans
Rocket.Chat | `ENABLE_CHAT=true` | Team communication platform
Gancio Events | `GANCIO_SYNC_ENABLED=true` | Shift-to-event sync with Gancio
Jitsi Meet | `ENABLE_MEET=true` | Video conferencing (also prompts for server public IP)
SMS Campaigns | `ENABLE_SMS=true` | Termux Android bridge for SMS (also prompts for API URL)
Docs Comments | `GITEA_COMMENTS_ENABLED=true` | Gitea-backed page comments on documentation
Bunker Ops | `BUNKER_OPS_ENABLED=true` | Fleet metrics push to central server (also prompts for remote write URL)

### Step 8: Tunnel Configuration (Pangolin)

Optionally configures Pangolin tunnel credentials for secure public access:

- `PANGOLIN_API_URL` — API endpoint (default: `https://api.bnkserve.org/v1`)
- `PANGOLIN_API_KEY` — Authentication key
- `PANGOLIN_ORG_ID` — Organization identifier

Complete tunnel setup is done from the admin GUI at Settings > Tunnel after services are running.

### Step 9: CORS Origins

Automatically calculates allowed origins from your domain:

    http://app.DOMAIN,https://app.DOMAIN,http://DOMAIN,https://DOMAIN,http://localhost:3000,http://localhost,http://localhost:4003

### Step 10: Nginx Config Generation

Renders all `.conf.template` files in `nginx/conf.d/` by substituting `${DOMAIN}` with your configured domain. This produces the nginx configuration files that handle subdomain routing.

### Step 11: Homepage Services YAML

Generates `configs/homepage/services.yaml` with 27 service entries (both production and local development URLs) for the Homepage service dashboard.

### Step 12: Container Directory Permissions

Creates and sets permissions (775) on 12 directories needed by containers:

Directory | Purpose
--- | ---
`configs/code-server/.config` | Code Server configuration
`configs/code-server/.local` | Code Server local data
`mkdocs/.cache` | MkDocs build cache
`mkdocs/site` | MkDocs built site output
`assets/uploads` | Listmonk uploads
`assets/images` | Shared images
`assets/icons` | Homepage icons
`media/local/inbox` | Media upload inbox
`media/local/thumbnails` | Video thumbnails
`media/public` | Public media files
`local-files` | n8n local files
`data` | NAR import data

### Step 13: Upgrade Watcher (Optional)

Installs a systemd path watcher that enables the admin GUI's "Check for Updates" and "Start Upgrade" buttons. This step requires `sudo` and is optional — you can install it later or use the CLI upgrade script directly.

The watcher installs two systemd units:

- `changemaker-upgrade.path` — watches for `data/upgrade/trigger.json`
- `changemaker-upgrade.service` — runs `scripts/upgrade-watcher.sh` when triggered

### Step 14: Summary & Next Steps

Displays a configuration summary showing all choices made, then prints startup commands.

## What Gets Modified

After the wizard completes, the following files have been created or modified:

File | Action
--- | ---
`.env` | Created (or updated) with all configuration values
`mkdocs/mkdocs.yml` | Updated `site_url` and `repo_url` with domain
`nginx/conf.d/*.conf` | Generated from `.conf.template` files
`configs/homepage/services.yaml` | Generated with all service URLs
12 directories | Created with container-friendly permissions
systemd units (optional) | Installed to `/etc/systemd/system/`

## Manual Setup (Alternative)

If you prefer to configure by hand instead of using the wizard:

    cp .env.example .env

At minimum, set these required secrets:

    # Generate cryptographic secrets
    V2_POSTGRES_PASSWORD=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 24)
    REDIS_PASSWORD=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 24)
    JWT_ACCESS_SECRET=$(openssl rand -hex 32)
    JWT_REFRESH_SECRET=$(openssl rand -hex 32)
    JWT_INVITE_SECRET=$(openssl rand -hex 32)
    ENCRYPTION_KEY=$(openssl rand -hex 32)   # must differ from all JWT secrets

Set your admin credentials (password must meet the 12+ char complexity requirement):

    INITIAL_ADMIN_EMAIL=admin@yourdomain.org
    INITIAL_ADMIN_PASSWORD=YourStrongPassword1

Then configure optional sections:

- Domain: Set `DOMAIN` and all derived variables (see Step 3 table above)
- SMTP: Set `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `EMAIL_TEST_MODE=false`
- Feature flags: Enable features as needed (see Step 7 table above)
- Tunnel: Set `PANGOLIN_API_URL`, `PANGOLIN_API_KEY`, `PANGOLIN_ORG_ID`

See Environment Variables for every available option.

## Full Stack Startup

After configuration, start the entire platform:

    docker compose up -d

That's it. Docker handles the startup order automatically:

1. PostgreSQL and Redis start first (with healthchecks)
2. API waits for both to be healthy, then auto-runs database migrations and seeding
3. Admin GUI waits for the API
4. Nginx, media, communication, and all other services start in parallel
5. Init containers (nocodb-init, listmonk-init, etc.) run once and exit

Watch the startup progress:

    docker compose logs -f api --tail 20

Once you see `Starting server on port 4000`, open http://localhost:3000 and log in.

Quick sanity check

Three commands confirm a healthy install — run them in order:

    docker compose ps                              # all core containers healthy?
    curl -s http://localhost:4000/api/health       # should return {"status":"healthy",...}
    bash scripts/test-deployment.sh --wait 60      # end-to-end verifier

If all three pass, you're done. See Verifying Installation below for manual checks if anything fails.

### Include Monitoring

The monitoring stack (Prometheus, Grafana, Alertmanager) uses a Docker Compose profile and isn't included by default:

    docker compose --profile monitoring up -d

### Start Only Core Services

If you prefer a minimal startup (lower resource usage):

    docker compose up -d v2-postgres redis api admin nginx

Manual migrations

The API container runs migrations and seeding automatically on startup via its entrypoint script. You only need to run them manually if you're developing locally without Docker:

    cd api && npx prisma migrate deploy && npx prisma db seed

See Services Overview for the complete service catalog.

## Verifying Installation

Changemaker Lite ships a one-shot deployment verifier. Run it after `docker compose up -d`:

    bash scripts/test-deployment.sh --wait 60

`--wait 60` gives services time to pass their healthchecks on a cold start (first-run image pulls take ~3 min and health stabilization ~90s — brief unhealthy states during this window are expected). The script checks:

- All containers running with healthy healthchecks
- API `/api/health` returns `"healthy"` with database + Redis OK
- Admin and media API endpoints respond
- (If `--domain yourdomain.org` passed) tunnel subdomains return 200/302/403

Any failure is printed with the failing component. If everything passes, your install is ready.

### Manual checks

    docker compose ps                                         # container status
    curl -s http://localhost:4000/api/health | python3 -m json.tool  # API health
    docker compose logs api --tail 20                         # API startup logs
    bash scripts/validate-env.sh                              # re-check .env + host ports

### Clean reset before reinstall

If you need to wipe and start over:

    docker compose --profile monitoring down -v --remove-orphans
    bash scripts/pangolin-teardown.sh --yes   # wipes the tunnel org (dry-run by default)
    sudo rm -rf ~/changemaker.lite

For CCP-registered instances, also run `bash scripts/ccp-deregister.sh --yes` before removing the project directory — otherwise the CCP retains a stale Instance row that blocks re-registration of the same slug.

## Next Steps

- Services Overview — complete service catalog with ports and startup commands
- Environment Variables — complete `.env` reference
- First Steps — create your first campaign and add locations
- Updates & Upgrades — keep your installation up to date