Tonne of updates to things like social systems, calendars, and the documentation system (making it mobile friendly and fixing up navigation)

This commit is contained in:
2026-03-07 13:10:08 -07:00
parent 08d8066157
commit 1cca51e518
188 changed files with 39689 additions and 2615 deletions

View File

@@ -7,10 +7,10 @@
"stars_count": 0,
"forks_count": 0,
"open_issues_count": 23,
"updated_at": "2026-03-03T14:22:46-07:00",
"updated_at": "2026-03-05T12:20:58-07:00",
"created_at": "2025-05-28T14:54:59-06:00",
"clone_url": "https://gitea.bnkops.com/admin/changemaker.lite.git",
"ssh_url": "git@gitea.bnkops.com:admin/changemaker.lite.git",
"default_branch": "main",
"last_build_update": "2026-03-03T14:22:46-07:00"
"last_build_update": "2026-03-05T12:20:58-07:00"
}

View File

@@ -4,13 +4,13 @@
"description": "Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.",
"html_url": "https://github.com/anthropics/claude-code",
"language": "Shell",
"stars_count": 73218,
"forks_count": 5806,
"open_issues_count": 5500,
"updated_at": "2026-03-03T21:40:58Z",
"stars_count": 74943,
"forks_count": 6013,
"open_issues_count": 5785,
"updated_at": "2026-03-07T19:48:10Z",
"created_at": "2025-02-22T17:41:21Z",
"clone_url": "https://github.com/anthropics/claude-code.git",
"ssh_url": "git@github.com:anthropics/claude-code.git",
"default_branch": "main",
"last_build_update": "2026-03-02T16:38:30Z"
"last_build_update": "2026-03-07T00:12:45Z"
}

View File

@@ -4,13 +4,13 @@
"description": "VS Code in the browser",
"html_url": "https://github.com/coder/code-server",
"language": "TypeScript",
"stars_count": 76454,
"forks_count": 6532,
"open_issues_count": 174,
"updated_at": "2026-03-03T21:35:43Z",
"stars_count": 76519,
"forks_count": 6539,
"open_issues_count": 169,
"updated_at": "2026-03-07T18:20:51Z",
"created_at": "2019-02-27T16:50:41Z",
"clone_url": "https://github.com/coder/code-server.git",
"ssh_url": "git@github.com:coder/code-server.git",
"default_branch": "main",
"last_build_update": "2026-03-03T21:35:38Z"
"last_build_update": "2026-03-06T12:59:10Z"
}

View File

@@ -4,13 +4,13 @@
"description": "A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.",
"html_url": "https://github.com/gethomepage/homepage",
"language": "JavaScript",
"stars_count": 28705,
"stars_count": 28761,
"forks_count": 1808,
"open_issues_count": 6,
"updated_at": "2026-03-03T21:17:50Z",
"open_issues_count": 1,
"updated_at": "2026-03-07T19:52:28Z",
"created_at": "2022-08-24T07:29:42Z",
"clone_url": "https://github.com/gethomepage/homepage.git",
"ssh_url": "git@github.com:gethomepage/homepage.git",
"default_branch": "dev",
"last_build_update": "2026-03-03T12:22:06Z"
"last_build_update": "2026-03-07T15:45:18Z"
}

View File

@@ -4,13 +4,13 @@
"description": "Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD",
"html_url": "https://github.com/go-gitea/gitea",
"language": "Go",
"stars_count": 54043,
"forks_count": 6420,
"open_issues_count": 2841,
"updated_at": "2026-03-03T21:25:00Z",
"stars_count": 54164,
"forks_count": 6434,
"open_issues_count": 2847,
"updated_at": "2026-03-07T18:54:25Z",
"created_at": "2016-11-01T02:13:26Z",
"clone_url": "https://github.com/go-gitea/gitea.git",
"ssh_url": "git@github.com:go-gitea/gitea.git",
"default_branch": "main",
"last_build_update": "2026-03-03T19:24:00Z"
"last_build_update": "2026-03-07T05:30:59Z"
}

View File

@@ -4,13 +4,13 @@
"description": "High performance, self-hosted, newsletter and mailing list manager with a modern dashboard. Single binary app.",
"html_url": "https://github.com/knadh/listmonk",
"language": "Go",
"stars_count": 19177,
"forks_count": 1945,
"open_issues_count": 115,
"updated_at": "2026-03-03T21:29:08Z",
"stars_count": 19208,
"forks_count": 1946,
"open_issues_count": 99,
"updated_at": "2026-03-07T18:41:21Z",
"created_at": "2019-06-26T05:08:39Z",
"clone_url": "https://github.com/knadh/listmonk.git",
"ssh_url": "git@github.com:knadh/listmonk.git",
"default_branch": "master",
"last_build_update": "2026-03-03T03:44:33Z"
"last_build_update": "2026-03-07T18:41:17Z"
}

View File

@@ -4,13 +4,13 @@
"description": "Create & scan cute qr codes easily \ud83d\udc7e",
"html_url": "https://github.com/lyqht/mini-qr",
"language": "Vue",
"stars_count": 1883,
"forks_count": 240,
"stars_count": 1896,
"forks_count": 238,
"open_issues_count": 21,
"updated_at": "2026-03-03T15:21:16Z",
"updated_at": "2026-03-07T17:03:03Z",
"created_at": "2023-04-21T14:20:14Z",
"clone_url": "https://github.com/lyqht/mini-qr.git",
"ssh_url": "git@github.com:lyqht/mini-qr.git",
"default_branch": "main",
"last_build_update": "2026-03-02T11:52:10Z"
"last_build_update": "2026-03-05T13:18:42Z"
}

View File

@@ -4,13 +4,13 @@
"description": "Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.",
"html_url": "https://github.com/n8n-io/n8n",
"language": "TypeScript",
"stars_count": 177388,
"forks_count": 55378,
"open_issues_count": 1397,
"updated_at": "2026-03-03T21:42:29Z",
"stars_count": 178014,
"forks_count": 55521,
"open_issues_count": 1413,
"updated_at": "2026-03-07T19:43:47Z",
"created_at": "2019-06-22T09:24:21Z",
"clone_url": "https://github.com/n8n-io/n8n.git",
"ssh_url": "git@github.com:n8n-io/n8n.git",
"default_branch": "master",
"last_build_update": "2026-03-03T21:30:53Z"
"last_build_update": "2026-03-07T18:51:26Z"
}

View File

@@ -4,13 +4,13 @@
"description": "\ud83d\udd25 \ud83d\udd25 \ud83d\udd25 A Free & Self-hostable Airtable Alternative",
"html_url": "https://github.com/nocodb/nocodb",
"language": "TypeScript",
"stars_count": 62290,
"forks_count": 4650,
"open_issues_count": 621,
"updated_at": "2026-03-03T21:35:23Z",
"stars_count": 62371,
"forks_count": 4655,
"open_issues_count": 627,
"updated_at": "2026-03-07T19:49:07Z",
"created_at": "2017-10-29T18:51:48Z",
"clone_url": "https://github.com/nocodb/nocodb.git",
"ssh_url": "git@github.com:nocodb/nocodb.git",
"default_branch": "develop",
"last_build_update": "2026-03-03T15:07:07Z"
"last_build_update": "2026-03-07T10:48:26Z"
}

View File

@@ -4,13 +4,13 @@
"description": "Get up and running with Kimi-K2.5, GLM-5, MiniMax, DeepSeek, gpt-oss, Qwen, Gemma and other models.",
"html_url": "https://github.com/ollama/ollama",
"language": "Go",
"stars_count": 163957,
"forks_count": 14745,
"open_issues_count": 2551,
"updated_at": "2026-03-03T21:37:48Z",
"stars_count": 164358,
"forks_count": 14823,
"open_issues_count": 2590,
"updated_at": "2026-03-07T19:18:40Z",
"created_at": "2023-06-26T19:39:32Z",
"clone_url": "https://github.com/ollama/ollama.git",
"ssh_url": "git@github.com:ollama/ollama.git",
"default_branch": "main",
"last_build_update": "2026-03-03T21:23:42Z"
"last_build_update": "2026-03-07T03:18:54Z"
}

View File

@@ -4,13 +4,13 @@
"description": "Documentation that simply works",
"html_url": "https://github.com/squidfunk/mkdocs-material",
"language": "Python",
"stars_count": 26161,
"forks_count": 4048,
"stars_count": 26199,
"forks_count": 4047,
"open_issues_count": 2,
"updated_at": "2026-03-03T19:59:27Z",
"updated_at": "2026-03-07T18:01:45Z",
"created_at": "2016-01-28T22:09:23Z",
"clone_url": "https://github.com/squidfunk/mkdocs-material.git",
"ssh_url": "git@github.com:squidfunk/mkdocs-material.git",
"default_branch": "master",
"last_build_update": "2026-03-03T19:59:22Z"
"last_build_update": "2026-03-05T13:44:14Z"
}

View File

@@ -0,0 +1,270 @@
---
title: Control Panel (CCP)
description: Multi-tenant management for provisioning and operating multiple Changemaker Lite instances.
icon: material/console
---
# Changemaker Control Panel (CCP)
The Changemaker Control Panel is a **multi-tenant management layer** for operators who run multiple Changemaker Lite instances from a single server. It provides a web UI to provision, monitor, and maintain a fleet of instances without manual configuration.
!!! info "Single instance?"
If you're running a single Changemaker Lite instance, you don't need CCP. Skip this page and continue with [First Steps](first-steps.md).
---
## When to Use CCP
CCP is designed for:
- **Campaign organizations** managing instances for multiple chapters or regions
- **Hosting providers** offering Changemaker Lite as a managed service
- **Development teams** spinning up isolated test instances
CCP handles the entire instance lifecycle: provisioning, configuration, health monitoring, backups, and upgrades — all from a single dashboard.
---
## Architecture
CCP runs as 4 Docker containers alongside (but independent from) your CML instances:
```
┌──────────────────────────┐
│ CCP Admin GUI (5100) │ React + Vite + Ant Design
│ Dark theme, SPA │ Zustand auth store
└────────────┬─────────────┘
┌────────────▼─────────────┐
│ CCP API (5000) │ Express + TypeScript
│ JWT auth, RBAC │ Prisma ORM → PostgreSQL
│ Docker socket access │ Winston logger
└────────────┬─────────────┘
┌────────┼────────┐
▼ ▼ ▼
ccp-postgres ccp-redis Docker Socket
(port 5480) (port 6399)
```
| Service | Container | Port | Description |
|---------|-----------|------|-------------|
| CCP API | `ccp-api` | 5000 | Express API with Docker CLI access |
| CCP Admin | `ccp-admin` | 5100 | React admin GUI |
| CCP PostgreSQL | `ccp-postgres` | 5480 | CCP metadata database |
| CCP Redis | `ccp-redis` | 6399 | Rate limiting, caching |
Each managed CML instance gets its own isolated set of containers and PostgreSQL database, with ports allocated from non-overlapping ranges.
---
## Setup
### 1. Run the Setup Script
```bash
cd changemaker-control-panel
chmod +x setup.sh
./setup.sh
```
The setup script:
- Detects the installation directory and resolves absolute paths
- Creates `instances/` and `backups/` directories
- Copies `.env.example` to `.env` if not present
- Sets `INSTANCES_BASE_PATH`, `BACKUP_STORAGE_PATH`, and `CML_SOURCE_PATH`
- Generates random secrets for any placeholder values
### 2. Review Environment
Edit `.env` and verify the key settings:
| Variable | Default | Description |
|----------|---------|-------------|
| `JWT_ACCESS_SECRET` | Auto-generated | JWT signing key |
| `JWT_REFRESH_SECRET` | Auto-generated | Refresh token signing key |
| `ENCRYPTION_KEY` | Auto-generated | AES-256 key for instance secrets at rest |
| `INITIAL_ADMIN_EMAIL` | `admin@example.com` | Bootstrap admin email |
| `INITIAL_ADMIN_PASSWORD` | `ChangeMe2025!!` | Bootstrap admin password |
| `INSTANCES_BASE_PATH` | `./instances` | Where instance directories are created |
| `CML_SOURCE_PATH` | Auto-detected | Path to CML source repo for provisioning |
| `BACKUP_STORAGE_PATH` | `./backups` | Backup archive storage |
| `PANGOLIN_API_URL` | — | Pangolin API for tunnel management |
| `PANGOLIN_API_KEY` | — | Pangolin authentication |
| `PANGOLIN_ORG_ID` | — | Pangolin organization |
### 3. Start CCP
```bash
docker compose up -d
# Run database migrations and seed the admin user
docker compose exec ccp-api npx prisma migrate deploy
docker compose exec ccp-api npx prisma db seed
```
### 4. Log In
Open **http://localhost:5100** and sign in with the admin credentials from `.env`.
---
## Creating an Instance
The Create Instance wizard walks through 5 steps:
### Step 1: Basic Information
- **Instance name** — human-readable label (e.g., "Edmonton Chapter")
- **Slug** — URL-safe identifier (e.g., `edmonton`), used for directory names and compose project
- **Domain** — the domain this instance will serve (e.g., `edmonton.example.org`)
### Step 2: Features
Toggle which platform features to enable for this instance:
- Media Manager
- Listmonk newsletter sync
- Payments
- Rocket.Chat
- Gancio events
- Jitsi Meet
- SMS Campaigns
### Step 3: Email
Configure SMTP for the instance, or use MailHog for testing.
### Step 4: Tunnel
Optionally configure Pangolin tunnel credentials for public access.
### Step 5: Review
Review all settings, then click **Create** to start provisioning.
---
## Provisioning Flow
When you create an instance, CCP runs a **13-step async provisioning process**:
| Step | What Happens |
|------|-------------|
| 1 | Validate uniqueness (slug + domain) |
| 2 | Allocate 4 ports from ranges |
| 3 | Generate 14 secrets (passwords, JWT keys, encryption keys) |
| 4 | Create Instance record (status: PROVISIONING) |
| 5 | Create instance directory |
| 6 | Copy CML source code (rsync, excluding node_modules/.git/.env) |
| 7 | Decrypt secrets and build template context |
| 8 | Render 7 config files from Handlebars templates (docker-compose.yml, .env, nginx configs, Pangolin, Prometheus) |
| 9 | Copy static files (nginx.conf) |
| 10 | `docker compose pull` (non-fatal if images are cached) |
| 11 | `docker compose build` |
| 12 | Start infrastructure (PostgreSQL + Redis), wait for healthy |
| 13 | Start API (runs migrations + seed), then start all remaining services |
The admin GUI polls every 3 seconds during provisioning to show progress. When complete, the instance status changes to **RUNNING**.
---
## Port Allocation
CCP allocates ports from 4 non-overlapping ranges to prevent conflicts between instances:
| Range | Start | End | Purpose |
|-------|-------|-----|---------|
| API | 14000 | 14999 | Express API server |
| Admin | 13000 | 13999 | React admin GUI |
| PostgreSQL | 15400 | 15499 | Database |
| Nginx | 10000 | 10999 | Reverse proxy |
Each new instance receives one port from each range. Ports are tracked in the database and released when instances are deleted.
---
## Pages Overview
### Dashboard
At-a-glance fleet status:
- Total instances, running, healthy, degraded, stopped, error counts
- Instance cards with status indicators and quick actions
### Instance List
Searchable, filterable table of all instances with status, domain, health, and creation date.
### Instance Detail
5-tab view for each instance:
| Tab | Content |
|-----|---------|
| **Overview** | Status, domain, ports, features, health summary |
| **Services** | Per-container status grid with restart and log-view actions |
| **Logs** | Real-time log viewer with service filter, tail count, and time range |
| **Backups** | Backup list with create, download, and delete actions |
| **Tunnel** | Pangolin tunnel status and configuration |
### Backups
Cross-instance backup management:
- All backups in one table with instance filter
- Stats: total count, total size, last backup time
- "Backup All Running" bulk action
- Download and delete individual archives
### Audit Log
Filterable activity trail with 18 action types:
- Instance lifecycle: CREATE, UPDATE, DELETE, START, STOP, RESTART, UPGRADE
- Backups: CREATE, DELETE
- Tunnel: PANGOLIN_SETUP, PANGOLIN_SYNC
- Users: LOGIN, CREATE, UPDATE, DELETE
- Settings: UPDATE
Each entry includes timestamp, user, action, instance, IP address, and details (expandable JSON).
### Settings
CCP-level configuration:
- Port ranges
- Pangolin credentials
- Default feature flags for new instances
- Health check interval
- Backup retention period
---
## Roles
| Role | Capabilities |
|------|-------------|
| **SUPER_ADMIN** | Full access: create/delete instances, manage users, view secrets, delete backups |
| **OPERATOR** | Manage instances: create, start/stop/restart, backups, health checks |
| **VIEWER** | Read-only: view instances, logs, health, backups, audit log |
---
## Security
- **JWT authentication** with 15-minute access tokens and 7-day refresh tokens (atomic rotation)
- **AES-256-GCM encryption** for instance secrets stored in the database
- **Audit logging** on all operations with IP address capture
- **Role-based access control** on all API endpoints
- **Docker socket access** restricted to the CCP API container only
---
## Next Steps
- [Services Overview](services.md) — learn about the services CCP provisions for each instance
- [Updates & Upgrades](upgrades.md) — upgrading CML instances
- [Deployment](../deployment/index.md) — production setup with tunneling and SSL

View File

@@ -33,6 +33,7 @@ Visit **Settings** (`/app/settings`) to:
- Choose theme colors for admin and public interfaces
- Enable feature modules (campaigns, map, media, payments, etc.)
- Configure email delivery (MailHog for testing, production SMTP for live use)
- Check the **System** tab to verify your installation and check for updates
---
@@ -75,6 +76,8 @@ Share the shifts page link or generate QR codes for in-person events. Volunteers
## Next Steps
- [Services Overview](services.md) — complete catalog of all 30+ Docker services
- [Updates & Upgrades](upgrades.md) — keep your installation current
- [Features at a Glance](features.md) — visual overview of every module
- [Admin Guide](../admin/index.md) — full administration reference
- [Deployment](../deployment/index.md) — production setup with tunneling and SSL

View File

@@ -16,132 +16,73 @@ This guide walks you through installing Changemaker Lite, running your first dep
- At least 2 GB RAM and 10 GB disk space
- A domain name (optional, but recommended for production)
## Installation
### 1. Clone the Repository
## Quick Start
```bash
git clone https://gitea.bnkops.com/admin/changemaker.lite
cd changemaker.lite
git checkout v2
```
### 2. Run the Configuration Wizard
The fastest way to get a working `.env` file is the interactive configuration wizard:
```bash
bash config.sh
docker compose up -d
```
The wizard walks you through each step:
| Step | What it does |
|------|-------------|
| **Prerequisites check** | Verifies Docker, Docker Compose, and OpenSSL are installed |
| **Domain** | Sets your root domain and updates all subdomain references (nginx, Gitea, n8n, MkDocs, etc.) |
| **Admin credentials** | Prompts for the initial super-admin email and password (enforces 12+ chars, uppercase, lowercase, digit) |
| **Secret generation** | Auto-generates 16 unique secrets — JWT keys, encryption key, database passwords, Redis password, API tokens |
| **SMTP** | Optionally configures production SMTP (defaults to MailHog for development) |
| **Feature flags** | Enable/disable Media Manager and Listmonk newsletter sync |
| **Pangolin tunnel** | Optionally configures tunnel credentials for public access |
| **CORS** | Auto-sets allowed origins based on your domain |
| **Homepage** | Generates `configs/homepage/services.yaml` with all service links for your domain |
| **Permissions** | Creates required directories and sets container-friendly permissions |
After completion you'll have a fully populated `.env` with no placeholder passwords remaining.
!!! tip "Already have a `.env`?"
If a `.env` file exists, the wizard offers to back it up before creating a fresh one, or update values in place.
??? example "What the wizard looks like"
```
██████╗██╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ███████╗
██╔════╝██║ ██║██╔══██╗████╗ ██║██╔════╝ ██╔════╝
██║ ███████║███████║██╔██╗ ██║██║ ███╗█████╗
██║ ██╔══██║██╔══██║██║╚██╗██║██║ ██║██╔══╝
╚██████╗██║ ██║██║ ██║██║ ╚████║╚██████╔╝███████╗
╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝
███╗ ███╗ █████╗ ██╗ ██╗███████╗██████╗
████╗ ████║██╔══██╗██║ ██╔╝██╔════╝██╔══██╗
██╔████╔██║███████║█████╔╝ █████╗ ██████╔╝
██║╚██╔╝██║██╔══██║██╔═██╗ ██╔══╝ ██╔══██╗
██║ ╚═╝ ██║██║ ██║██║ ██╗███████╗██║ ██║
╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
V2 Configuration Wizard
[INFO] This wizard will create your .env file, generate secure secrets,
[INFO] and prepare your system to run the full Changemaker Lite stack.
```
### 3. Manual Setup (Alternative)
If you prefer to configure things by hand:
```bash
cp .env.example .env
```
Then edit `.env` and at minimum set these values:
```bash
V2_POSTGRES_PASSWORD=<strong password>
REDIS_PASSWORD=<strong password>
JWT_ACCESS_SECRET=<openssl rand -hex 32>
JWT_REFRESH_SECRET=<openssl rand -hex 32>
ENCRYPTION_KEY=<openssl rand -hex 32>
INITIAL_ADMIN_PASSWORD=<12+ chars, mixed case + digit>
```
See [Environment Variables](environment-variables.md) for every available option.
### 4. Start Services
```bash
# Start core services
docker compose up -d v2-postgres redis api admin
# Run database migrations and seed the initial admin account
docker compose exec api npx prisma migrate deploy
docker compose exec api npx prisma db seed
```
### 5. Log In
Open **http://localhost:3000** and sign in with the admin email and password you configured.
Open **http://localhost:3000** and sign in with the admin email and password you configured. The API container automatically runs database migrations and seeding on first startup — no manual steps needed.
!!! warning "Change your password"
If you used the wizard's generated password, change it immediately from the admin dashboard.
## Optional Services
For the full setup walkthrough, see [Installation](installation.md).
Once the core is running, add more services as needed:
## Configuration Wizard
```bash
# Reverse proxy (required for subdomain routing)
docker compose up -d nginx
The `config.sh` wizard produces a fully populated `.env` file in **14 steps**:
# Video library
docker compose up -d media-api
| Step | What It Does |
|------|-------------|
| **1. Prerequisites** | Verifies Docker, Docker Compose, and OpenSSL |
| **2. Environment file** | Creates `.env` from `.env.example` (backs up existing) |
| **3. Domain** | Sets root domain + 14 derived variables, updates mkdocs.yml |
| **4. Admin credentials** | Email + password (enforces 12+ chars, mixed case, digit) |
| **5. Secrets** | Auto-generates 21 unique secrets (JWT, encryption, database, service passwords) |
| **6. Email** | MailHog (dev) or production SMTP, optionally shared with Listmonk |
| **7. Feature flags** | 9 toggles: Media, Listmonk, Payments, Chat, Events, Meet, SMS, Docs Comments, Bunker Ops |
| **8. Tunnel** | Pangolin credentials for secure public access |
| **9. CORS** | Auto-calculated allowed origins from domain |
| **10. Nginx** | Renders `.conf.template` files with domain substitution |
| **11. Homepage** | Generates `services.yaml` with 27 service entries |
| **12. Permissions** | Creates 12 directories with container-friendly permissions |
| **13. Upgrade watcher** | Installs systemd units for GUI-triggered upgrades (optional, requires sudo) |
| **14. Summary** | Displays configuration summary + next steps |
# Newsletters
docker compose up -d listmonk-app
See [Installation](installation.md) for detailed documentation of each step.
# Service dashboard
docker compose up -d homepage
## Services
# All services at once
docker compose up -d
Changemaker Lite includes **30+ Docker services** organized into 8 categories:
# Monitoring stack (Prometheus, Grafana, Alertmanager)
docker compose --profile monitoring up -d
```
| Category | Services | Startup |
|----------|----------|---------|
| **Core** | API, Admin, PostgreSQL, Redis, Nginx | `docker compose up -d v2-postgres redis api admin nginx` |
| **Media** | Fastify media API | `docker compose up -d media-api` |
| **Communication** | Rocket.Chat, Gancio, Jitsi Meet | Individual `docker compose up -d` commands |
| **Newsletter & Email** | Listmonk, MailHog | `docker compose up -d listmonk-app` |
| **Developer Tools** | Code Server, MkDocs, Gitea, NocoDB, n8n | Individual `docker compose up -d` commands |
| **Utilities** | Mini QR, Excalidraw, Vaultwarden, Homepage | `docker compose up -d mini-qr excalidraw vaultwarden homepage` |
| **Monitoring** | Prometheus, Grafana, Alertmanager, exporters | `docker compose --profile monitoring up -d` |
| **Infrastructure** | Newt tunnel, Docker socket proxy | Auto-starts with tunnel configuration |
See [Services Overview](services.md) for the complete catalog with ports, feature flags, and detailed descriptions.
## Next Steps
- [Installation](installation.md) — detailed setup walkthrough and manual configuration
- [Services Overview](services.md) — complete service catalog (30+ containers)
- [Environment Variables](environment-variables.md) — complete `.env` reference
- [First Steps](first-steps.md) — create your first campaign and add locations
- [Updates & Upgrades](upgrades.md) — keep your installation current
- [Control Panel (CCP)](control-panel.md) — multi-instance management
- [Features at a Glance](features.md) — visual overview of every module
- [Admin Guide](../admin/index.md) — full administration reference
- [Deployment](../deployment/index.md) — production setup with SSL and tunneling

View File

@@ -1,12 +1,12 @@
---
title: Installation
description: System requirements, installation methods, and initial service startup.
description: System requirements, configuration wizard walkthrough, and initial service startup.
icon: material/download
---
# Installation
Changemaker Lite runs as a set of Docker containers orchestrated by Docker Compose.
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.
---
@@ -15,8 +15,8 @@ Changemaker Lite runs as a set of Docker containers orchestrated by Docker Compo
- **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 and 10 GB disk space
- A domain name (optional, but recommended for production)
- At least **2 GB RAM** for core services, **4 GB** for the full stack
- A domain name (optional for development, recommended for production)
---
@@ -31,31 +31,294 @@ git checkout v2
# Run the configuration wizard
bash config.sh
# Start core services
docker compose up -d v2-postgres redis api admin
# Run database migrations and seed
docker compose exec api npx prisma migrate deploy
docker compose exec api npx prisma db seed
# Start all services
docker compose up -d
```
Open **http://localhost:3000** and sign in with the admin credentials you configured.
Open **http://localhost:3000** and sign in with the admin credentials you configured. Database migrations and seeding run automatically on first startup.
!!! warning "Change your password"
If you used the wizard's generated password, change it immediately from the admin dashboard.
---
## Configuration Wizard
## Configuration Wizard (`config.sh`)
The `config.sh` wizard walks you through domain setup, admin credentials, secret generation, SMTP config, feature flags, and Pangolin tunnel setup. After completion you'll have a fully populated `.env` with no placeholder passwords.
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
### Step 5: Secret Generation
Auto-generates **21 unique secrets** — no placeholder passwords remain after this step:
| Category | Count | Secrets |
|----------|-------|---------|
| JWT & Encryption | 3 | `JWT_ACCESS_SECRET`, `JWT_REFRESH_SECRET`, `ENCRYPTION_KEY` (64-char hex) |
| 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.
---
## Manual Setup
## What Gets Modified
If you prefer to configure by hand, copy `.env.example` to `.env` and set the required values. See [Environment Variables](environment-variables.md) for every option.
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:
```bash
cp .env.example .env
```
At minimum, set these required secrets:
```bash
# 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)
ENCRYPTION_KEY=$(openssl rand -hex 32)
```
Set your admin credentials (password must meet the 12+ char complexity requirement):
```bash
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](environment-variables.md) for every available option.
---
## Full Stack Startup
After configuration, start the entire platform:
```bash
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:
```bash
docker compose logs -f api --tail 20
```
Once you see `Starting server on port 4000`, open **http://localhost:3000** and log in.
### Include Monitoring
The monitoring stack (Prometheus, Grafana, Alertmanager) uses a Docker Compose profile and isn't included by default:
```bash
docker compose --profile monitoring up -d
```
### Start Only Core Services
If you prefer a minimal startup (lower resource usage):
```bash
docker compose up -d v2-postgres redis api admin nginx
```
!!! note "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:
```bash
cd api && npx prisma migrate deploy && npx prisma db seed
```
See [Services Overview](services.md) for the complete service catalog.
---
## Verifying Installation
After starting services, verify everything is healthy:
```bash
# Check running containers
docker compose ps
# API health check
curl -s http://localhost:4000/api/health | python3 -m json.tool
# View API logs
docker compose logs api --tail 20
# Check for containers in restart loops
docker compose ps | grep -i restarting
```
You should see the API return `{"status":"ok"}` and all started containers in a "running" state.
---
## Next Steps
- [First Steps](first-steps.md) — explore the dashboard and create your first campaign
- [Environment Variables](environment-variables.md) — complete configuration reference
- [Services Overview](services.md) — complete service catalog with ports and startup commands
- [Environment Variables](environment-variables.md) — complete `.env` reference
- [First Steps](first-steps.md) — create your first campaign and add locations
- [Updates & Upgrades](upgrades.md) — keep your installation up to date

View File

@@ -0,0 +1,280 @@
---
title: Services Overview
description: Complete catalog of all Docker services, ports, and startup commands.
icon: material/docker
---
# Services Overview
Changemaker Lite runs as **30+ Docker containers** orchestrated by Docker Compose. This page catalogs every service, organized by category.
!!! tip "Quick reference"
Use `docker compose ps` to see which services are currently running, or `docker compose ps -a` to include stopped containers.
---
## Core (Required)
These services form the minimum viable platform. Start them first.
| Container | Port | Description |
|-----------|------|-------------|
| `changemaker-v2-api` | 4000 | Express.js REST API (Prisma ORM) |
| `changemaker-v2-admin` | 3000 | React admin GUI (Vite + Ant Design) |
| `changemaker-v2-postgres` | 5433 | PostgreSQL 16 — primary database |
| `redis-changemaker` | 6379 | Redis 7 — cache, rate limiting, job queues |
| `changemaker-v2-nginx` | 80 | Nginx reverse proxy — subdomain routing |
```bash
# Start core services only (minimal)
docker compose up -d v2-postgres redis api admin nginx
# Or start everything at once
docker compose up -d
```
The API container automatically runs database migrations and seeding on startup via its entrypoint script.
!!! note
Nginx is technically optional for local development (you can access services directly by port), but required for production subdomain routing.
---
## Media
| Container | Port | Description | Feature Flag |
|-----------|------|-------------|-------------|
| `changemaker-media-api` | 4100 | Fastify media API — video library, analytics, scheduling | `ENABLE_MEDIA_FEATURES=true` |
```bash
docker compose up -d media-api
```
The media API runs as a separate Fastify server sharing the same PostgreSQL database. It handles video upload (FFprobe metadata extraction), scheduled publishing via BullMQ, and GDPR-compliant view analytics.
---
## Communication
### Rocket.Chat (Team Chat)
| Container | Port | Description | Feature Flag |
|-----------|------|-------------|-------------|
| `rocketchat-changemaker` | 8891 | Rocket.Chat server | `ENABLE_CHAT=true` |
| `mongodb-changemaker` | — | MongoDB (Rocket.Chat data store) | — |
| `nats-changemaker` | — | NATS (Rocket.Chat message bus) | — |
```bash
docker compose up -d rocketchat mongodb nats
```
### Gancio (Events)
| Container | Port | Description | Feature Flag |
|-----------|------|-------------|-------------|
| `gancio-changemaker` | 8092 | Gancio event platform | `GANCIO_SYNC_ENABLED=true` |
| `gancio-init` | — | Init container — creates Gancio database | — |
```bash
docker compose up -d gancio
```
!!! info "Init containers"
`gancio-init` runs once on first start to create the Gancio database in PostgreSQL, then exits. This is normal — don't worry about seeing it in a "stopped" state.
### Jitsi Meet (Video Conferencing)
| Container | Port | Description | Feature Flag |
|-----------|------|-------------|-------------|
| `jitsi-web-changemaker` | 8893 | Jitsi web interface | `ENABLE_MEET=true` |
| `jitsi-prosody-changemaker` | — | XMPP server (Prosody) | — |
| `jitsi-jicofo-changemaker` | — | Jitsi conference focus | — |
| `jitsi-jvb-changemaker` | 10000/udp | Jitsi video bridge | — |
```bash
docker compose up -d jitsi-web jitsi-prosody jitsi-jicofo jitsi-jvb
```
!!! warning "Firewall requirement"
Jitsi requires **UDP port 10000** open in your firewall for video/audio media traffic. Set `JVB_ADVERTISE_IP` in `.env` to your server's public IP address.
---
## Newsletter & Email
| Container | Port | Description | Feature Flag |
|-----------|------|-------------|-------------|
| `listmonk-app` | 9001 | Listmonk newsletter platform | `LISTMONK_SYNC_ENABLED=true` |
| `listmonk-db` | 5432 | PostgreSQL (Listmonk's own database) | — |
| `listmonk-init` | — | Init container — creates API user | — |
| `mailhog-changemaker` | 8025 | MailHog email capture (development) | `EMAIL_TEST_MODE=true` |
```bash
# Newsletter platform
docker compose up -d listmonk-app
# Email testing (captures all outgoing emails)
docker compose up -d mailhog
```
Listmonk has its own PostgreSQL instance separate from the main database. The `listmonk-init` container auto-creates the API user for platform integration.
---
## Developer Tools
| Container | Port | Description |
|-----------|------|-------------|
| `code-server-changemaker` | 8888 | VS Code in the browser |
| `mkdocs-changemaker` | 4003 | MkDocs live preview (hot reload) |
| `mkdocs-site-server-changemaker` | 4004 | MkDocs static site server |
| `gitea-changemaker` | 3030 | Gitea — self-hosted Git repository |
| `gitea-db` | — | PostgreSQL (Gitea's database) |
| `changemaker-v2-nocodb` | 8091 | NocoDB — read-only database browser |
| `nocodb-init` | — | Init container — registers database |
| `n8n-changemaker` | 5678 | n8n — workflow automation |
```bash
# Start individual tools
docker compose up -d code-server
docker compose up -d mkdocs mkdocs-site-server
docker compose up -d gitea
docker compose up -d nocodb
docker compose up -d n8n
```
!!! tip
`mkdocs` (port 4003) provides live editing with hot reload for documentation authors. `mkdocs-site-server` (port 4004) serves the built static site for production visitors.
---
## Utilities
| Container | Port | Description |
|-----------|------|-------------|
| `mini-qr` | 8089 | QR code PNG generator |
| `excalidraw-changemaker` | 8090 | Collaborative whiteboard |
| `vaultwarden-changemaker` | 8445 | Vaultwarden — Bitwarden-compatible password manager |
| `vaultwarden-init` | — | Init container — configures admin settings |
| `homepage-changemaker` | 3010 | Homepage — service dashboard |
```bash
docker compose up -d mini-qr excalidraw vaultwarden homepage
```
Mini QR is used internally by walk sheets and cut export pages to generate printable QR codes.
---
## Monitoring (Docker Profile)
Monitoring services are behind a Docker Compose profile and are **not started by default**.
| Container | Port | Description |
|-----------|------|-------------|
| `prometheus-changemaker` | 9090 | Prometheus — metrics collection |
| `grafana-changemaker` | 3005 | Grafana — monitoring dashboards |
| `alertmanager-changemaker` | 9093 | Alertmanager — alert routing |
| `cadvisor-changemaker` | 8086 | cAdvisor — container metrics |
| `node-exporter-changemaker` | 9100 | Node Exporter — host system metrics |
| `redis-exporter-changemaker` | 9121 | Redis Exporter — Redis metrics |
| `gotify-changemaker` | 8889 | Gotify — push notifications |
```bash
# Start the entire monitoring stack
docker compose --profile monitoring up -d
```
The monitoring stack includes 3 pre-configured Grafana dashboards and 12 custom `cm_*` Prometheus metrics. See [Monitoring](../admin/services/monitoring.md) for details.
---
## Infrastructure
| Container | Port | Description |
|-----------|------|-------------|
| `newt` | — | Pangolin tunnel connector (Newt) |
| `docker-socket-proxy` | — | Docker socket proxy for secure container access |
```bash
# Newt starts automatically if PANGOLIN_NEWT_ID and PANGOLIN_NEWT_SECRET are set
docker compose up -d newt
```
The Newt container connects to a Pangolin tunnel server for secure public access without opening inbound ports. See [Tunnel](../admin/services/tunnel.md) for setup.
---
## Subdomain Routing
When Nginx is running, services are accessible via subdomains. The root domain serves documentation only; all application routes are at `app.DOMAIN`.
| Subdomain | Target | Purpose |
|-----------|--------|---------|
| `app.DOMAIN` | Admin (3000) | All application routes (admin, public pages, campaigns, map, shifts, media gallery) |
| `api.DOMAIN` | Express API (4000) | REST API |
| `media.DOMAIN` | Fastify Media API (4100) | Media API |
| `DOMAIN` | MkDocs Static (4004) | Documentation / marketing site |
| `db.DOMAIN` | NocoDB (8091) | Database browser |
| `docs.DOMAIN` | MkDocs Live (4003) | Live documentation preview |
| `code.DOMAIN` | Code Server (8888) | Web IDE |
| `n8n.DOMAIN` | n8n (5678) | Workflow automation |
| `git.DOMAIN` | Gitea (3030) | Git hosting |
| `home.DOMAIN` | Homepage (3010) | Service dashboard |
| `grafana.DOMAIN` | Grafana (3005) | Metrics visualization |
| `listmonk.DOMAIN` | Listmonk (9001) | Newsletter platform |
| `qr.DOMAIN` | Mini QR (8089) | QR code generator |
| `draw.DOMAIN` | Excalidraw (8090) | Collaborative whiteboard |
| `vault.DOMAIN` | Vaultwarden (8445) | Password manager |
| `events.DOMAIN` | Gancio (8092) | Event platform |
| `chat.DOMAIN` | Rocket.Chat (8891) | Team chat |
| `meet.DOMAIN` | Jitsi Meet (8893) | Video conferencing |
| `mail.DOMAIN` | MailHog (8025) | Email capture (dev) |
---
## Init Containers
Several services use **init containers** — lightweight containers that run once on first startup to bootstrap databases or configuration, then exit with code 0. This pattern is borrowed from Kubernetes.
| Init Container | Purpose |
|----------------|---------|
| `listmonk-init` | Creates the Listmonk API user for platform integration |
| `gancio-init` | Creates the Gancio database in the shared PostgreSQL instance |
| `vaultwarden-init` | Configures Vaultwarden admin settings |
| `nocodb-init` | Registers the main database with NocoDB for browsing |
Seeing these containers in a "stopped" or "exited (0)" state is completely normal.
---
## Starting Everything
To start all services at once (excluding monitoring):
```bash
docker compose up -d
```
To start everything including monitoring:
```bash
docker compose up -d && docker compose --profile monitoring up -d
```
To see what's running:
```bash
docker compose ps
```
!!! warning
Starting all services at once requires at least **4 GB RAM**. For resource-constrained environments, start only the services you need.
---
## Next Steps
- [Installation](installation.md) — setup walkthrough and configuration wizard details
- [Environment Variables](environment-variables.md) — complete `.env` reference
- [First Steps](first-steps.md) — create your first campaign and volunteer shift

View File

@@ -0,0 +1,285 @@
---
title: Updates & Upgrades
description: Keep Changemaker Lite up to date via the admin GUI or command line.
icon: material/update
---
# Updates & Upgrades
Changemaker Lite includes a built-in upgrade system that pulls code updates, rebuilds containers, runs database migrations, and restarts services — all while preserving your customizations.
There are two ways to upgrade:
1. **Admin GUI** — Check for updates and run upgrades from **Settings > System**
2. **CLI** — Run `./scripts/upgrade.sh` directly from the command line
Both methods execute the same 6-phase upgrade process.
---
## Prerequisites
### Upgrade Watcher (Required for GUI Method)
The admin GUI triggers upgrades via a **systemd path watcher** that monitors for trigger files. This must be installed on the host system.
**Install during initial setup:**
The `config.sh` wizard offers to install the watcher automatically (Step 13). If you skipped it, install manually:
```bash
# Edit the systemd units to set your project path and user
sed -e "s|__PROJECT_DIR__|$(pwd)|g" scripts/systemd/changemaker-upgrade.path > /tmp/changemaker-upgrade.path
sed -e "s|__PROJECT_DIR__|$(pwd)|g" -e "s|__USER__|$(whoami)|g" scripts/systemd/changemaker-upgrade.service > /tmp/changemaker-upgrade.service
# Install and enable
sudo cp /tmp/changemaker-upgrade.path /tmp/changemaker-upgrade.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now changemaker-upgrade.path
```
**Verify it's running:**
```bash
sudo systemctl status changemaker-upgrade.path
```
!!! note "How the watcher works"
The API container writes a `trigger.json` file to a shared `data/upgrade/` volume. The systemd path watcher detects the file and runs `scripts/upgrade-watcher.sh` on the host, which dispatches to the appropriate script (check or upgrade). Progress and results are communicated back via JSON files that the API reads.
---
## Method 1: Admin GUI
### Checking for Updates
1. Navigate to **Settings** (`/app/settings`)
2. Click the **System** tab
3. Click **Check for Updates**
The system fetches from the git remote and shows:
- Current commit hash and message
- Remote commit hash (if different)
- Number of commits behind
- Changelog of incoming changes
### Starting an Upgrade
1. Review the changelog to understand what's changing
2. Click **Start Upgrade**
3. Optionally configure:
- **Skip backup** — skip the database backup phase (not recommended)
- **Pull images** — also update third-party Docker images (PostgreSQL, Redis, etc.)
- **Dry run** — preview what would happen without making changes
4. Monitor the 6-phase progress indicator
The GUI polls for progress updates and displays the current phase, percentage, and status message in real time.
---
## The 6 Upgrade Phases
Both the GUI and CLI methods execute the same 6-phase process:
| Phase | % | Name | What Happens |
|-------|---|------|-------------|
| **1** | 5% | Pre-flight Checks | Verifies Docker, git, disk space (2 GB minimum), remote reachability, and clean working directory |
| **2** | 15% | Backup | Runs `scripts/backup.sh` (pg_dump + archive), backs up user-modifiable content, saves pre-upgrade commit hash |
| **3** | 30% | Code Update | Saves user paths, stashes local changes, `git pull`, pops stash with auto-conflict resolution, detects new `.env` variables |
| **4** | 50% | Container Rebuild | Rebuilds `api`, `admin`, `media-api`; conditionally rebuilds `nginx` and `code-server` if their configs changed; optionally pulls third-party images |
| **5** | 70% | Service Restart | Stops app containers, force-recreates LSIO containers, verifies Gancio config, starts infrastructure, waits for PostgreSQL, starts API (runs migrations), starts everything else, restarts Newt tunnel and monitoring if they were running |
| **6** | 90% | Verification | Health checks for API, Admin, Media API, Gancio, MkDocs; detects containers in restart loops |
---
## What Gets Preserved
The upgrade script automatically preserves **user-modifiable paths** that you may have customized:
| Path | What It Contains |
|------|-----------------|
| `mkdocs/docs/` | Your documentation content |
| `mkdocs/mkdocs.yml` | MkDocs configuration |
| `mkdocs/site/` | Built documentation site |
| `configs/` | Prometheus, Grafana, Alertmanager, Homepage configs |
| `nginx/conf.d/services.conf` | Custom nginx service proxies |
These files are saved before `git pull` and unconditionally restored afterward, even if the pull introduces changes to them. Your versions always win.
!!! tip
The `.env` file is never touched by `git pull` (it's in `.gitignore`). However, if new environment variables are added in `.env.example`, the upgrade script automatically appends them to your `.env` with their default values and warns you to review them.
---
## Method 2: CLI
Run the upgrade script directly:
```bash
./scripts/upgrade.sh
```
### Options
| Flag | Description |
|------|-------------|
| `--skip-backup` | Skip the backup phase (requires `--force`) |
| `--pull-services` | Also pull new third-party Docker images |
| `--dry-run` | Show what would happen without executing |
| `--force` | Continue past non-critical warnings |
| `--branch BRANCH` | Git branch to pull (default: current branch) |
| `--rollback` | Rollback to pre-upgrade commit |
| `--api-mode` | Write progress/result JSON for admin GUI (used internally) |
### Examples
```bash
# Standard upgrade
./scripts/upgrade.sh
# Preview changes without executing
./scripts/upgrade.sh --dry-run
# Full upgrade including third-party image updates
./scripts/upgrade.sh --pull-services
# Rollback to the last pre-upgrade state
./scripts/upgrade.sh --rollback
```
---
## Rollback
### Automatic Rollback
If the upgrade fails at any phase, the script prints detailed rollback instructions including the pre-upgrade commit hash. Use the `--rollback` flag:
```bash
./scripts/upgrade.sh --rollback
```
This:
1. Finds the latest backup archive
2. Extracts the pre-upgrade commit hash from `git-commit.txt` inside the archive
3. Checks out that commit
4. Rebuilds and restarts all containers
!!! warning
`--rollback` restores the **code** to the pre-upgrade state but does **not** automatically restore the database. If database migrations were applied during the failed upgrade, you may need to manually restore from the backup archive.
### Manual Rollback
```bash
# 1. Restore code
cd /path/to/changemaker.lite
git checkout <pre-upgrade-commit-hash>
# 2. Rebuild and restart
docker compose build api admin media-api
docker compose up -d
# 3. Database restore (if needed — destructive!)
ls -lt backups/changemaker-v2-backup-*.tar.gz | head -5
tar xzf backups/<backup>.tar.gz -C /tmp
gunzip -c /tmp/<backup>/v2-postgres.sql.gz | \
docker exec -i changemaker-v2-postgres psql -U changemaker -d changemaker_v2
```
---
## New Environment Variables
When upstream code adds new environment variables to `.env.example`, the upgrade script automatically:
1. Compares `.env.example` against your `.env`
2. Appends any missing variables with their default values
3. Warns you to review the new additions
```
[WARN] New env vars added to .env (review defaults):
NEW_FEATURE_FLAG
NEW_API_KEY
```
Always review new variables after an upgrade — some may need manual configuration.
---
## Update Checker
A separate lightweight script checks for available updates without performing any changes:
```bash
./scripts/upgrade-check.sh
```
This writes `data/upgrade/status.json` with:
- Current and remote commit hashes
- Number of commits behind
- Changelog (last 30 commits)
- Timestamp of last check
The admin GUI reads this file to display update availability.
---
## Troubleshooting
### Stale Progress Indicator
If the GUI shows an upgrade "in progress" but nothing is happening, the upgrade script may have crashed. The system automatically detects stale progress (no update for 10+ minutes) and treats it as not running.
To manually clear:
```bash
rm -f data/upgrade/progress.json
```
### Merge Conflicts
If `git pull` encounters merge conflicts in **user-modifiable paths** (docs, configs), the upgrade script auto-resolves by keeping your version. If conflicts occur in **project-owned files** (api/, admin/), the upgrade fails and asks you to resolve manually.
### Lock File
The upgrade script uses `.upgrade.lock` to prevent concurrent upgrades. If a previous upgrade crashed without cleaning up:
```bash
# Verify no upgrade is actually running
ps aux | grep upgrade.sh
# Remove stale lock
rm -f .upgrade.lock
```
### Health Check Failures
If Phase 6 health checks fail, services may still be starting. Wait 1-2 minutes and check manually:
```bash
# API health
curl -s http://localhost:4000/api/health
# Container status
docker compose ps
# Recent logs
docker compose logs api --tail 50
docker compose logs admin --tail 50
```
### Systemd Watcher Not Triggering
```bash
# Check watcher status
sudo systemctl status changemaker-upgrade.path
# Check service logs
sudo journalctl -u changemaker-upgrade.service --tail 20
# Re-enable if stopped
sudo systemctl enable --now changemaker-upgrade.path
```

View File

@@ -11,19 +11,57 @@
</div>
<div class="cm-header-nav__links">
<div class="cm-header-nav__links-inner">
<a href="#" data-path="/" class="cm-header-nav__link" data-nav-id="home" target="_blank" rel="noopener noreferrer"><span class="material-icons-outlined">home</span><span class="cm-header-nav__label">Home</span></a>
<a href="#" data-path="/home" class="cm-header-nav__link" data-nav-id="home"><span class="material-icons-outlined">home</span><span class="cm-header-nav__label">Home</span></a>
<a href="#" data-path="/campaigns" class="cm-header-nav__link" data-nav-id="campaigns"><span class="material-icons-outlined">send</span><span class="cm-header-nav__label">Campaigns</span></a>
<a href="#" data-path="/shifts" class="cm-header-nav__link" data-nav-id="shifts"><span class="material-icons-outlined">schedule</span><span class="cm-header-nav__label">Shifts</span></a>
<a href="#" data-path="/events" class="cm-header-nav__link" data-nav-id="events" target="_blank" rel="noopener noreferrer"><span class="material-icons-outlined">event</span><span class="cm-header-nav__label">Events</span></a>
<a href="#" data-path="/map" class="cm-header-nav__link" data-nav-id="map"><span class="material-icons-outlined">place</span><span class="cm-header-nav__label">Map</span></a>
<div class="cm-header-nav__dropdown">
<span class="cm-header-nav__link cm-header-nav__dropdown-trigger">
<span class="material-icons-outlined">apps</span>
<span class="cm-header-nav__label">Scheduling</span>
<span class="material-icons-outlined cm-header-nav__chevron">expand_more</span>
</span>
<div class="cm-header-nav__dropdown-menu">
<a href="#" data-path="/shifts" class="cm-header-nav__dropdown-item" data-nav-id="shifts"><span class="material-icons-outlined">schedule</span><span>Shifts</span></a>
<a href="#" data-path="/events" class="cm-header-nav__dropdown-item" data-nav-id="events"><span class="material-icons-outlined">event</span><span>Calendar</span></a>
<a href="#" data-path="/polls" class="cm-header-nav__dropdown-item" data-nav-id="polls"><span class="material-icons-outlined">bar_chart</span><span>Polls</span></a>
<a href="#" data-path="/events/tickets" class="cm-header-nav__dropdown-item" data-nav-id="tickets"><span class="material-icons-outlined">sell</span><span>Tickets</span></a>
<a href="#" data-path="/meet" class="cm-header-nav__dropdown-item" data-nav-id="meet"><span class="material-icons-outlined">videocam</span><span>Meet</span></a>
</div>
</div>
<a href="#" data-path="/gallery" class="cm-header-nav__link" data-nav-id="gallery"><span class="material-icons-outlined">play_circle</span><span class="cm-header-nav__label">Gallery</span></a>
<a href="#" data-path="/shop" class="cm-header-nav__link" data-nav-id="shop"><span class="material-icons-outlined">shopping_bag</span><span class="cm-header-nav__label">Shop</span></a>
<a href="#" data-path="/donate" class="cm-header-nav__link" data-nav-id="donate"><span class="material-icons-outlined">favorite_border</span><span class="cm-header-nav__label">Donate</span></a>
<div class="cm-header-nav__dropdown">
<span class="cm-header-nav__link cm-header-nav__dropdown-trigger">
<span class="material-icons-outlined">account_balance_wallet</span>
<span class="cm-header-nav__label">Commerce</span>
<span class="material-icons-outlined cm-header-nav__chevron">expand_more</span>
</span>
<div class="cm-header-nav__dropdown-menu">
<a href="#" data-path="/pricing" class="cm-header-nav__dropdown-item" data-nav-id="pricing"><span class="material-icons-outlined">attach_money</span><span>Pricing</span></a>
<a href="#" data-path="/shop" class="cm-header-nav__dropdown-item" data-nav-id="shop"><span class="material-icons-outlined">shopping_bag</span><span>Shop</span></a>
<a href="#" data-path="/donate" class="cm-header-nav__dropdown-item" data-nav-id="donate"><span class="material-icons-outlined">favorite_border</span><span>Donate</span></a>
</div>
</div>
<a href="#" data-path="/wall-of-fame" class="cm-header-nav__link" data-nav-id="wall-of-fame"><span class="material-icons-outlined">emoji_events</span><span class="cm-header-nav__label">Wall of Fame</span></a>
<a href="#" data-path="/pages" class="cm-header-nav__link" data-nav-id="pages"><span class="material-icons-outlined">description</span><span class="cm-header-nav__label">Pages</span></a>
<a href="/" class="cm-header-nav__link" data-nav-id="landing"><span class="material-icons-outlined">language</span><span class="cm-header-nav__label">Website</span></a>
<a href="/docs/" class="cm-header-nav__link" data-nav-id="docs"><span class="material-icons-outlined">menu_book</span><span class="cm-header-nav__label">Docs</span></a>
<a href="#" data-path="/app" class="cm-header-nav__link">
<span class="material-icons-outlined">dashboard</span>
<span class="cm-header-nav__label">Admin</span>
<a href="#" data-path="/login" class="cm-header-nav__link" id="cm-signin-link">
<span class="material-icons-outlined">login</span>
<span class="cm-header-nav__label">Sign In</span>
</a>
<div class="cm-header-nav__dropdown" id="cm-admin-dropdown" style="display:none">
<span class="cm-header-nav__link cm-header-nav__dropdown-trigger">
<span class="material-icons-outlined">person</span>
<span class="cm-header-nav__label">Admin</span>
<span class="material-icons-outlined cm-header-nav__chevron">expand_more</span>
</span>
<div class="cm-header-nav__dropdown-menu cm-header-nav__dropdown-menu--right">
<a href="#" data-path="/app" class="cm-header-nav__dropdown-item"><span class="material-icons-outlined">dashboard</span><span>Admin Panel</span></a>
<a href="#" data-path="/volunteer" class="cm-header-nav__dropdown-item"><span class="material-icons-outlined">volunteer_activism</span><span>Volunteer Portal</span></a>
<a href="#" data-path="/volunteer/profile" class="cm-header-nav__dropdown-item"><span class="material-icons-outlined">account_circle</span><span>My Profile</span></a>
<a href="#" data-path="/logout" class="cm-header-nav__dropdown-item"><span class="material-icons-outlined">logout</span><span>Logout</span></a>
</div>
</div>
</div>
<button class="cm-header-nav__hamburger" aria-label="Open navigation menu">
<span class="material-icons-outlined">menu</span>
@@ -38,19 +76,57 @@
</button>
</div>
<div class="cm-header-nav__mobile-links">
<a href="#" data-path="/" class="cm-header-nav__mobile-link" data-nav-id="home" target="_blank" rel="noopener noreferrer"><span class="material-icons-outlined">home</span><span>Home</span></a>
<a href="#" data-path="/home" class="cm-header-nav__mobile-link" data-nav-id="home"><span class="material-icons-outlined">home</span><span>Home</span></a>
<a href="#" data-path="/campaigns" class="cm-header-nav__mobile-link" data-nav-id="campaigns"><span class="material-icons-outlined">send</span><span>Campaigns</span></a>
<a href="#" data-path="/shifts" class="cm-header-nav__mobile-link" data-nav-id="shifts"><span class="material-icons-outlined">schedule</span><span>Shifts</span></a>
<a href="#" data-path="/events" class="cm-header-nav__mobile-link" data-nav-id="events" target="_blank" rel="noopener noreferrer"><span class="material-icons-outlined">event</span><span>Events</span></a>
<a href="#" data-path="/map" class="cm-header-nav__mobile-link" data-nav-id="map"><span class="material-icons-outlined">place</span><span>Map</span></a>
<div class="cm-header-nav__mobile-group" data-group-id="scheduling">
<span class="cm-header-nav__mobile-link cm-header-nav__mobile-group-trigger" role="button">
<span class="material-icons-outlined">apps</span>
<span style="flex:1">Scheduling</span>
<span class="material-icons-outlined cm-header-nav__mobile-chevron">expand_more</span>
</span>
<div class="cm-header-nav__mobile-group-children">
<a href="#" data-path="/shifts" class="cm-header-nav__mobile-link" data-nav-id="shifts" style="padding-left:48px"><span class="material-icons-outlined">schedule</span><span>Shifts</span></a>
<a href="#" data-path="/events" class="cm-header-nav__mobile-link" data-nav-id="events" style="padding-left:48px"><span class="material-icons-outlined">event</span><span>Calendar</span></a>
<a href="#" data-path="/polls" class="cm-header-nav__mobile-link" data-nav-id="polls" style="padding-left:48px"><span class="material-icons-outlined">bar_chart</span><span>Polls</span></a>
<a href="#" data-path="/events/tickets" class="cm-header-nav__mobile-link" data-nav-id="tickets" style="padding-left:48px"><span class="material-icons-outlined">sell</span><span>Tickets</span></a>
<a href="#" data-path="/meet" class="cm-header-nav__mobile-link" data-nav-id="meet" style="padding-left:48px"><span class="material-icons-outlined">videocam</span><span>Meet</span></a>
</div>
</div>
<a href="#" data-path="/gallery" class="cm-header-nav__mobile-link" data-nav-id="gallery"><span class="material-icons-outlined">play_circle</span><span>Gallery</span></a>
<a href="#" data-path="/shop" class="cm-header-nav__mobile-link" data-nav-id="shop"><span class="material-icons-outlined">shopping_bag</span><span>Shop</span></a>
<a href="#" data-path="/donate" class="cm-header-nav__mobile-link" data-nav-id="donate"><span class="material-icons-outlined">favorite_border</span><span>Donate</span></a>
<div class="cm-header-nav__mobile-group" data-group-id="commerce">
<span class="cm-header-nav__mobile-link cm-header-nav__mobile-group-trigger" role="button">
<span class="material-icons-outlined">account_balance_wallet</span>
<span style="flex:1">Commerce</span>
<span class="material-icons-outlined cm-header-nav__mobile-chevron">expand_more</span>
</span>
<div class="cm-header-nav__mobile-group-children">
<a href="#" data-path="/pricing" class="cm-header-nav__mobile-link" data-nav-id="pricing" style="padding-left:48px"><span class="material-icons-outlined">attach_money</span><span>Pricing</span></a>
<a href="#" data-path="/shop" class="cm-header-nav__mobile-link" data-nav-id="shop" style="padding-left:48px"><span class="material-icons-outlined">shopping_bag</span><span>Shop</span></a>
<a href="#" data-path="/donate" class="cm-header-nav__mobile-link" data-nav-id="donate" style="padding-left:48px"><span class="material-icons-outlined">favorite_border</span><span>Donate</span></a>
</div>
</div>
<a href="#" data-path="/wall-of-fame" class="cm-header-nav__mobile-link" data-nav-id="wall-of-fame"><span class="material-icons-outlined">emoji_events</span><span>Wall of Fame</span></a>
<a href="#" data-path="/pages" class="cm-header-nav__mobile-link" data-nav-id="pages"><span class="material-icons-outlined">description</span><span>Pages</span></a>
<a href="/" class="cm-header-nav__mobile-link" data-nav-id="landing"><span class="material-icons-outlined">language</span><span>Website</span></a>
<a href="/docs/" class="cm-header-nav__mobile-link" data-nav-id="docs"><span class="material-icons-outlined">menu_book</span><span>Docs</span></a>
<a href="#" data-path="/app" class="cm-header-nav__mobile-link">
<span class="material-icons-outlined">dashboard</span>
<span>Admin</span>
<a href="#" data-path="/login" class="cm-header-nav__mobile-link" id="cm-mobile-signin-link">
<span class="material-icons-outlined">login</span>
<span>Sign In</span>
</a>
<div class="cm-header-nav__mobile-group" data-group-id="admin" id="cm-mobile-admin-group" style="display:none">
<span class="cm-header-nav__mobile-link cm-header-nav__mobile-group-trigger" role="button">
<span class="material-icons-outlined">person</span>
<span style="flex:1">Admin</span>
<span class="material-icons-outlined cm-header-nav__mobile-chevron">expand_more</span>
</span>
<div class="cm-header-nav__mobile-group-children">
<a href="#" data-path="/app" class="cm-header-nav__mobile-link" style="padding-left:48px"><span class="material-icons-outlined">dashboard</span><span>Admin Panel</span></a>
<a href="#" data-path="/volunteer" class="cm-header-nav__mobile-link" style="padding-left:48px"><span class="material-icons-outlined">volunteer_activism</span><span>Volunteer Portal</span></a>
<a href="#" data-path="/volunteer/profile" class="cm-header-nav__mobile-link" style="padding-left:48px"><span class="material-icons-outlined">account_circle</span><span>My Profile</span></a>
<a href="#" data-path="/logout" class="cm-header-nav__mobile-link" style="padding-left:48px"><span class="material-icons-outlined">logout</span><span>Logout</span></a>
</div>
</div>
</div>
</div>
<div class="cm-header-nav__mobile-overlay" id="cm-mobile-overlay"></div>
@@ -59,7 +135,7 @@
var h = location.hostname;
var base;
if (h === 'localhost' || h === '127.0.0.1') {
base = location.protocol + '//localhost:' + ({{ config.extra.admin_port }} || 3000);
base = location.protocol + '//localhost:' + ({{ config.extra.admin_port | default(0) }} || 3000);
} else {
var parts = h.split('.');
if (parts.length >= 3) { parts[0] = 'app'; }
@@ -89,26 +165,84 @@
if (hamburger) hamburger.addEventListener('click', openDrawer);
if (closeBtn) closeBtn.addEventListener('click', closeDrawer);
if (overlay) overlay.addEventListener('click', closeDrawer);
// Mobile group expand/collapse toggles
document.querySelectorAll('.cm-header-nav__mobile-group-trigger').forEach(function(trigger) {
trigger.addEventListener('click', function() {
var group = this.closest('.cm-header-nav__mobile-group');
var children = group.querySelector('.cm-header-nav__mobile-group-children');
var isExpanded = group.classList.contains('expanded');
if (isExpanded) {
group.classList.remove('expanded');
children.style.display = 'none';
} else {
group.classList.add('expanded');
children.style.display = 'block';
}
});
});
// Auth-aware: show Admin dropdown for logged-in users, Sign In for guests.
// Uses hidden iframe + postMessage to read auth state from the app's origin.
function showAdminMenu() {
var s1 = document.getElementById('cm-signin-link');
var s2 = document.getElementById('cm-mobile-signin-link');
var a1 = document.getElementById('cm-admin-dropdown');
var a2 = document.getElementById('cm-mobile-admin-group');
if (s1) s1.style.display = 'none';
if (s2) s2.style.display = 'none';
if (a1) a1.style.display = '';
if (a2) a2.style.display = '';
}
// 1. Same-origin check (works when MkDocs served from same origin as app)
try {
var stored = localStorage.getItem('cml-auth');
if (stored) {
var parsed = JSON.parse(stored);
if (parsed && parsed.state && parsed.state.accessToken) {
showAdminMenu();
}
}
} catch(e) {}
// 2. Cross-origin check via hidden iframe + postMessage
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = base + '/auth-check.html';
window.addEventListener('message', function(event) {
if (event.origin !== base) return;
if (event.data && event.data.type === 'cml-auth-status' && event.data.authenticated) {
showAdminMenu();
}
});
document.body.appendChild(iframe);
})();
</script>
<style>
.md-banner {
background: linear-gradient(158deg, rgb(0,31,156) 0%, rgb(0,68,204) 100%) !important;
background: linear-gradient(135deg, #005a9c 0%, #007acc 100%) !important;
color: #ffffff !important;
padding: 0 !important;
overflow: visible !important;
border: none !important;
box-shadow: none !important;
}
.md-banner__inner {
overflow: visible !important;
margin: 0 !important;
padding: 0 !important;
max-width: 100% !important;
}
.md-banner__button {
display: none !important;
}
.cm-header-nav {
background: linear-gradient(158deg, rgb(0,31,156) 0%, rgb(0,68,204) 100%);
background: linear-gradient(135deg, #005a9c 0%, #007acc 100%);
height: 56px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
z-index: 10;
position: relative;
z-index: 100;
box-sizing: border-box;
}
.cm-header-nav a {
@@ -171,6 +305,62 @@
.cm-header-nav__hamburger .material-icons-outlined {
font-size: 24px;
}
/* Desktop dropdown menus */
.cm-header-nav__dropdown {
position: relative;
display: inline-flex;
align-items: center;
}
.cm-header-nav__dropdown-trigger {
cursor: pointer;
user-select: none;
}
.cm-header-nav__dropdown-trigger .cm-header-nav__chevron {
font-size: 14px;
transition: transform 0.2s;
}
.cm-header-nav__dropdown:hover .cm-header-nav__chevron {
transform: rotate(180deg);
}
.cm-header-nav__dropdown-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
min-width: 180px;
background: #1b2838;
border-radius: 8px;
padding: 6px 0;
box-shadow: 0 6px 16px rgba(0,0,0,0.3);
z-index: 100;
margin-top: 4px;
}
.cm-header-nav__dropdown:hover .cm-header-nav__dropdown-menu {
display: block;
}
.cm-header-nav__dropdown-menu--right {
left: auto;
right: 0;
}
.cm-header-nav__dropdown-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
color: rgba(255, 255, 255, 0.85) !important;
text-decoration: none !important;
font-size: 14px;
white-space: nowrap;
transition: background 0.15s;
}
.cm-header-nav__dropdown-item:hover {
background: rgba(255,255,255,0.1);
color: #fff !important;
text-decoration: none !important;
}
.cm-header-nav__dropdown-item .material-icons-outlined {
font-size: 16px;
}
/* Mobile drawer */
.cm-header-nav__mobile-drawer {
position: fixed;
@@ -231,6 +421,21 @@
.cm-header-nav__mobile-link .material-icons-outlined {
font-size: 18px;
}
/* Mobile group expand/collapse */
.cm-header-nav__mobile-group-trigger {
cursor: pointer;
user-select: none;
}
.cm-header-nav__mobile-chevron {
font-size: 14px !important;
transition: transform 0.2s;
}
.cm-header-nav__mobile-group.expanded .cm-header-nav__mobile-chevron {
transform: rotate(180deg);
}
.cm-header-nav__mobile-group-children {
display: none;
}
.cm-header-nav__mobile-overlay {
display: none;
position: fixed;
@@ -248,6 +453,7 @@
.cm-header-nav { padding: 0 16px; }
.cm-header-nav__links-inner { display: none; }
.cm-header-nav__hamburger { display: block; }
.cm-header-nav__dropdown-menu { display: none !important; }
}
</style>
{% endblock %}

View File

@@ -0,0 +1,7 @@
{% extends "main.html" %}
{% block content %}
<style>
* { box-sizing: border-box; } body {margin: 0;}#i25w{padding:10px;}
</style>
<body id="i7af"><div id="i25w">Insert your text here</div></body>
{% endblock %}

7
mkdocs/docs/test-page.md Normal file
View File

@@ -0,0 +1,7 @@
---
template: test-page.html
hide:
- navigation
- toc
title: "Test Page"
---