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:
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
270
mkdocs/docs/docs/getting-started/control-panel.md
Normal file
270
mkdocs/docs/docs/getting-started/control-panel.md
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
280
mkdocs/docs/docs/getting-started/services.md
Normal file
280
mkdocs/docs/docs/getting-started/services.md
Normal 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
|
||||
285
mkdocs/docs/docs/getting-started/upgrades.md
Normal file
285
mkdocs/docs/docs/getting-started/upgrades.md
Normal 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
|
||||
```
|
||||
@@ -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 %}
|
||||
|
||||
7
mkdocs/docs/overrides/test-page.html
Normal file
7
mkdocs/docs/overrides/test-page.html
Normal 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
7
mkdocs/docs/test-page.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
template: test-page.html
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
title: "Test Page"
|
||||
---
|
||||
Reference in New Issue
Block a user