created: 20260527000000000
modified: 20260801000000000
tags: 
title: Deployment
type: text/vnd.tiddlywiki

!! Docker Compose Deployment

The published image is `linonetwo/tiddlywiki-cpl`. Clone the repo, fill in `.env`, then start:

```bash
git clone https://github.com/tiddly-gittly/TiddlyWiki-CPL.git
cd TiddlyWiki-CPL
cp .env.example .env   # fill in secrets — see Environment Variables below

# Pull latest image and start
docker compose pull
docker compose up -d
```

Architecture:

```
frpc → 127.0.0.1:28080 → cpl-nginx (:80)
                              ├─ /tmp/cpl-maintenance-active 存在? → maintenance (:8081)
                              └─ 否则 → tiddlywiki-cpl (:8080)
```

Two containers run together:
* `tiddlywiki-cpl` — TiddlyWiki + CPL server. Attaches to port `28081` for local debugging (no gzip). Does not expose port 8080 publicly.
* `cpl-nginx` — nginx reverse proxy. Handles gzip (homepage ~10MB → ~2MB), static asset caching, and maintenance-page fallback during startup.

Both containers share a `cpl-runtime` Docker volume at `/tmp`. The entrypoint writes `/tmp/cpl-maintenance-active` during startup; nginx checks this file per-request and proxies to the maintenance page on 8081 when present. Once the TiddlyWiki server is ready, the flag file is deleted and nginx switches to 8080.

!!! Volume mounts (from docker-compose.yml)

|!Host path|!Container path|!Purpose|
|`./cache`|`/app/cache`|Runtime plugin cache (rebuilt on startup)|
|`./wiki/files/plugin-fetched`|`/app/wiki/files/plugin-fetched`|Latest plugin JSONs|
|`./wiki/files/plugin-fetched-history`|`/app/wiki/files/plugin-fetched-history`|Per-version plugin archives|
|`./wiki/tiddlers/comments/approved`|`/app/wiki/tiddlers/comments/approved`|Approved comments|
|`./wiki/tiddlers/comments/pending`|`/app/wiki/tiddlers/comments/pending`|Pending comments|
|`./wiki/tiddlers/ratings`|`/app/wiki/tiddlers/ratings`|Plugin ratings data|
|`./wiki/tiddlers/compatibility`|`/app/wiki/tiddlers/compatibility`|Compatibility reports|
|`./wiki/tiddlers/download-stats`|`/app/wiki/tiddlers/download-stats`|Per-plugin download counts|
|`./docker/IfEditorMode.tid`|`/app/wiki/tiddlers/IfEditorMode.tid`|Read-only editor mode toggle|

!!! Container lifecycle (`docker-entrypoint.ts`)

# Start maintenance server on `:8081` (prevents 502 while building).
# `git clone` (first run) or `git pull` into `/app/repo-cache` — non-fatal if GitHub unreachable.
# Build `cache/plugins` static repo artifacts.
# Stop maintenance server, start TiddlyWiki on `:8080`.
# `fetch-plugins` runs in background; server restarts when new plugin files are ready.
# Repeats every `SYNC_INTERVAL_SECONDS` (default `3600`).

!! Environment Variables

Copy `.env.example` to `.env`:

|!Variable|!Default|!Description|
|`HOST`|`0.0.0.0`|Bind address|
|`PORT`|`8080`|Listen port|
|`CPL_JWT_SECRET`|—|Random 64-byte hex for JWT signing. Generate: `node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"`|
|`CPL_GITHUB_CLIENT_ID`|—|GitHub OAuth App Client ID|
|`CPL_GITHUB_CLIENT_SECRET`|—|GitHub OAuth App Client Secret|
|`CPL_ADMIN_GITHUB_IDS`|—|Comma-separated GitHub user IDs for comment moderators|
|`CPL_BLOCKED_GITHUB_IDS`|—|Optional comma-separated GitHub user IDs to block|
|`CPL_SERVER_ID`|—|Mirror identifier (e.g. `china`, `us`, `eu`). Leave empty for single-server.|
|`SYNC_INTERVAL_SECONDS`|`3600`|Seconds between git pull + plugin re-fetch. Set `0` to disable.|

Never commit `.env` — it is in `.gitignore`.

!!! Creating a GitHub OAuth App

# Go to https://github.com/settings/applications/new
# Set //Homepage URL// to `https://cpl.tidgi.fun/`
# Set //Authorization callback URL// to `https://cpl.tidgi.fun/cpl/auth/github/callback`
# Copy Client ID and Client Secret into `.env`

!!! GitHub User IDs

`CPL_ADMIN_GITHUB_IDS` and `CPL_BLOCKED_GITHUB_IDS` use numeric IDs, not usernames:

```bash
gh api users/<login> --jq .id
```

!! Multi-Server Deployment

Each mirror writes runtime Tiddlers with its own `CPL_SERVER_ID` suffix or identifier. Runtime data remains under `wiki/tiddlers/comments`, `wiki/tiddlers/ratings`, `wiki/tiddlers/compatibility`, and `wiki/tiddlers/download-stats`. The server aggregates these Tiddlers at read time, so independent mirror writes can be merged through Git without sharing a writable filesystem.

Set a unique `CPL_SERVER_ID` per mirror, for example `china` and `us`. Each mirror periodically pushes its runtime paths to a stable `data-sync/{serverId}` branch. GitHub Actions opens one Pull Request for that branch and later pushes refresh the same Pull Request.

After a data Pull Request is reviewed and merged, each mirror's normal Git sync pulls `master`. Before reset/clean, the deployment stashes the four runtime paths and restores them afterward. This preserves local writes while also receiving merged data from the other mirrors. Distinct server identifiers keep independently-created records from colliding.

To audit across mirrors:

```bash
pnpm run reconcile-data             # dry run
pnpm run reconcile-data -- --fix    # apply fixes
pnpm run reconcile-data -- --clean-stale   # remove files from mirrors inactive 30+ days
```

Comment IDs use format `{serverId}-{timestamp}-{random}` to prevent collisions.

!! Syncing runtime Tiddlers back to GitHub

The sync process requires repository-scoped Git push credentials. In Kubernetes, a dedicated sidecar mounts a writable GitHub deploy key and runs the sync script periodically. For a manual or bare-metal deployment, provide equivalent Git credentials and run:

```bash
CPL_SERVER_ID=china pnpm run sync-data
```

`scripts/sync-data.sh` clones the latest `master` into a temporary worktree, overlays only the four runtime-data paths, commits changes to the stable `data-sync/{serverId}` branch, and force-updates that branch. It never switches or resets the live wiki worktree. `.github/workflows/data-sync-pr.yml` creates the Pull Request when one does not already exist.

Example cron (weekly, Monday 03:00):

```
0 3 * * 1  cd /path/to/TiddlyWiki-CPL && CPL_SERVER_ID=china pnpm run sync-data >> /var/log/cpl-sync.log 2>&1
```

!! Comment System Administration

* Users authenticate via GitHub OAuth
* New comments held `pending` until moderator approval
* Moderators: GitHub user IDs in `CPL_ADMIN_GITHUB_IDS`
* Block abusers: numeric GitHub IDs in `CPL_BLOCKED_GITHUB_IDS`
* Rate limit: `CPL_COMMENT_RATE_LIMIT` comments per hour per user (default `10`)

!! Bare-Metal Deployment (without Docker)

```bash
pnpm install
pnpm run fetch-plugins
pnpm server:prod
```

Schedule plugin sync (Docker does this automatically):

```
0 3 * * *  cd /path/to/TiddlyWiki-CPL && pnpm run fetch-plugins >> /var/log/cpl-fetch.log 2>&1
```

!! Building the Docker Image

```bash
docker build -t linonetwo/tiddlywiki-cpl:latest .
```

Behind a proxy on Linux:

```bash
socat TCP-LISTEN:1081,bind=172.17.0.1,fork,reuseaddr TCP:127.0.0.1:1080 &
docker build \
  --build-arg HTTP_PROXY=http://172.17.0.1:1081 \
  --build-arg HTTPS_PROXY=http://172.17.0.1:1081 \
  -t linonetwo/tiddlywiki-cpl:latest \
  .
```
