docs: update PLAN.md — Nginx→Caddy, SSL_MODE→TLS_MODE, port→IP checks
- Added note that plan describes original architecture with diffs noted - Architecture table: Nginx+Certbot→Caddy+Cloudflare DNS-01 - File structure: nginx-gitea.conf.tpl→Caddyfile.tpl + caddy compose - Variable table: NGINX_*/SSL_MODE/SSL_EMAIL→TLS_MODE/CADDY_*/CLOUDFLARE_* - Preflight checks: port checks→container IP availability, Nginx→Caddy path - Phase 8: complete rewrite from 10-step Nginx flow to 6-step Caddy flow - Template section: replaced nginx template spec with Caddy template spec - Removed stale port variables from "Not checked" list Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
149
PLAN.md
149
PLAN.md
@@ -1,7 +1,9 @@
|
||||
# Gitea Migration Toolkit — Implementation Plan
|
||||
|
||||
> **Note**: This is the original implementation plan. Some sections describe the initial Nginx/SSL_MODE/3-repo architecture that has since been replaced by Caddy/TLS_MODE/dynamic repos with macvlan networking. See README.md and .env.example for the current architecture.
|
||||
|
||||
## Context
|
||||
Migrating 3 GitHub repos to self-hosted Gitea (Unraid primary, Fedora backup mirror, GitHub as push mirror). All automation runs from MacBook, SSHing into remote machines. Scripts must be idempotent, .env-driven, with preflight + post-checks + teardown per phase.
|
||||
Migrating GitHub repos to self-hosted Gitea (Unraid primary, Fedora backup mirror, GitHub as push mirror). All automation runs from MacBook, SSHing into remote machines. Scripts must be idempotent, .env-driven, with preflight + post-checks + teardown per phase.
|
||||
|
||||
---
|
||||
|
||||
@@ -17,7 +19,7 @@ Migrating 3 GitHub repos to self-hosted Gitea (Unraid primary, Fedora backup mir
|
||||
| Template rendering | `.tpl` files + `envsubst` | Keeps config templates separate from script logic |
|
||||
| Error handling | `set -euo pipefail` + trap cleanup | Fail fast, no silent errors |
|
||||
| API interaction | Shared `gitea_api()` curl wrapper with JSON/jq | Consistent auth, error checking, response parsing |
|
||||
| HTTPS proxy | Nginx config template + Certbot (Docker) | Plain Nginx already on Unraid as Docker container — just add a server block + SSL cert |
|
||||
| HTTPS proxy | Caddy reverse proxy with Cloudflare DNS-01 or existing certs | Automatic TLS with zero-touch renewal; each host gets a dedicated macvlan IP |
|
||||
|
||||
---
|
||||
|
||||
@@ -43,7 +45,9 @@ gitea-migration/
|
||||
│ ├── app.ini.tpl
|
||||
│ ├── runner-config.yaml.tpl
|
||||
│ ├── com.gitea.runner.plist.tpl
|
||||
│ ├── nginx-gitea.conf.tpl
|
||||
│ ├── Caddyfile.tpl
|
||||
│ ├── docker-compose-caddy.yml.tpl
|
||||
│ ├── com.gitea.runner.newsyslog.conf.tpl
|
||||
│ └── workflows/
|
||||
│ └── security-scan.yml.tpl
|
||||
├── backup/
|
||||
@@ -418,26 +422,26 @@ gitea-migration/
|
||||
|
||||
---
|
||||
|
||||
### 2.6 — `templates/nginx-gitea.conf.tpl`
|
||||
### 2.6 — `templates/Caddyfile.tpl` + `docker-compose-caddy.yml.tpl`
|
||||
|
||||
**Depends on**: .env vars
|
||||
**Produces**: Nginx server block for reverse-proxying Gitea
|
||||
**Depends on**: .env vars (`GITEA_DOMAIN`, `TLS_BLOCK`, `GITEA_CONTAINER_IP`, `CADDY_DATA_PATH`, `CADDY_CONTAINER_IP`)
|
||||
**Produces**: Caddy reverse proxy config + Docker Compose for Caddy container
|
||||
|
||||
**Must include**:
|
||||
- `server_name $GITEA_DOMAIN`
|
||||
- `listen 443 ssl`
|
||||
- `ssl_certificate` / `ssl_certificate_key` paths (Certbot standard: `/etc/letsencrypt/live/$GITEA_DOMAIN/`)
|
||||
- `proxy_pass http://$UNRAID_IP:$UNRAID_GITEA_PORT`
|
||||
- Proxy headers: `X-Real-IP`, `X-Forwarded-For`, `X-Forwarded-Proto`, `Host`
|
||||
- WebSocket support: `proxy_set_header Upgrade`, `Connection "upgrade"`
|
||||
- HTTP→HTTPS redirect block (listen 80, return 301)
|
||||
- `client_max_body_size 512m` (for large git pushes)
|
||||
**Caddyfile must include**:
|
||||
- `${GITEA_DOMAIN}` as the site address
|
||||
- `${TLS_BLOCK}` placeholder (script sets to `tls { dns cloudflare {env.CF_API_TOKEN} }` or `tls /path/cert /path/key`)
|
||||
- `reverse_proxy ${GITEA_CONTAINER_IP}:3000`
|
||||
|
||||
**Docker Compose must include**:
|
||||
- `slothcroissant/caddy-cloudflaredns:latest` image
|
||||
- Volume mounts for Caddyfile, data, and config
|
||||
- macvlan network with static IP (`CADDY_CONTAINER_IP`)
|
||||
- Conditional `CF_API_TOKEN` env var and cert volume mounts based on TLS mode
|
||||
|
||||
**Done when**:
|
||||
- [ ] `nginx -t` passes against rendered config (can test by running nginx in Docker locally)
|
||||
- [ ] WebSocket headers present (needed for Gitea's live features)
|
||||
- [ ] HTTP→HTTPS redirect included
|
||||
- [ ] SSL cert paths match Certbot convention
|
||||
- [ ] Caddy starts and obtains TLS certificate
|
||||
- [ ] HTTPS proxy works to Gitea
|
||||
- [ ] HTTP redirects to HTTPS
|
||||
|
||||
---
|
||||
|
||||
@@ -526,12 +530,12 @@ gitea-migration/
|
||||
| 34 | `MIGRATE_MILESTONES` | true/false | false |
|
||||
| 35 | `MIGRATE_WIKI` | true/false | false |
|
||||
| 36 | `GITHUB_MIRROR_INTERVAL` | Non-empty | 8h |
|
||||
| 37 | `NGINX_CONTAINER_NAME` | Non-empty | — |
|
||||
| 39 | `NGINX_CONF_PATH` | Absolute path | — |
|
||||
| 40 | `SSL_MODE` | `letsencrypt` or `existing` | letsencrypt |
|
||||
| 41 | `SSL_EMAIL` | Email *(only if SSL_MODE=letsencrypt)* | — |
|
||||
| 42 | `SSL_CERT_PATH` | Absolute path *(only if SSL_MODE=existing)* | — |
|
||||
| 43 | `SSL_KEY_PATH` | Absolute path *(only if SSL_MODE=existing)* | — |
|
||||
| 37 | `TLS_MODE` | `cloudflare` or `existing` | cloudflare |
|
||||
| 38 | `CADDY_DOMAIN` | Non-empty | — |
|
||||
| 39 | `CADDY_DATA_PATH` | Absolute path | — |
|
||||
| 40 | `CLOUDFLARE_API_TOKEN` | Non-empty *(only if TLS_MODE=cloudflare)* | — |
|
||||
| 41 | `SSL_CERT_PATH` | Absolute path *(only if TLS_MODE=existing)* | — |
|
||||
| 42 | `SSL_KEY_PATH` | Absolute path *(only if TLS_MODE=existing)* | — |
|
||||
| 44 | `PROTECTED_BRANCH` | Non-empty | main |
|
||||
| 45 | `REQUIRE_PR_REVIEW` | true/false | false |
|
||||
| 46 | `REQUIRED_APPROVALS` | Integer | 1 |
|
||||
@@ -541,9 +545,10 @@ gitea-migration/
|
||||
| 50 | `SECURITY_FAIL_ON_ERROR` | true/false | true |
|
||||
|
||||
**Done when**:
|
||||
- [ ] Each prompt shows progress: `[N/50]` with section header when entering a new section
|
||||
- [ ] Running with no existing `.env` walks through all 50 prompts and produces a valid `.env`
|
||||
- [ ] SSL prompts are conditional: if `SSL_MODE=letsencrypt`, prompt for `SSL_EMAIL` only; if `SSL_MODE=existing`, prompt for `SSL_CERT_PATH` and `SSL_KEY_PATH` only
|
||||
- [ ] Each prompt shows progress: `[N/~62]` with section header when entering a new section
|
||||
- [ ] Running with no existing `.env` walks through all prompts and produces a valid `.env`
|
||||
- [ ] TLS prompts are conditional: if `TLS_MODE=cloudflare`, prompt for `CLOUDFLARE_API_TOKEN` only; if `TLS_MODE=existing`, prompt for `SSL_CERT_PATH` and `SSL_KEY_PATH` only
|
||||
- [ ] DB prompts are conditional: if `GITEA_DB_TYPE` is not `sqlite3`, prompt for host/port/name/user/password
|
||||
- [ ] Running with an existing `.env` shows current values and only overwrites what user changes
|
||||
- [ ] Invalid input (bad IP, path not starting with `/`, password too short) re-prompts with error message
|
||||
- [ ] Auto-populated vars are skipped entirely (no prompt, no overwrite)
|
||||
@@ -663,25 +668,23 @@ gitea-migration/
|
||||
| Repos | `GITHUB_TOKEN` |
|
||||
| Repos | `REPO_NAMES` |
|
||||
| Mirror | `GITHUB_TOKEN` |
|
||||
| Nginx | `NGINX_CONTAINER_NAME` |
|
||||
| Nginx | `NGINX_CONF_PATH` |
|
||||
| Nginx | `SSL_EMAIL` |
|
||||
| TLS/Caddy | `TLS_MODE` |
|
||||
| TLS/Caddy | `CADDY_DOMAIN` |
|
||||
| TLS/Caddy | `CADDY_DATA_PATH` |
|
||||
|
||||
**Not checked** (have defaults or auto-populated):
|
||||
`UNRAID_SSH_PORT`, `UNRAID_GITEA_PORT`, `UNRAID_GITEA_SSH_PORT`, `FEDORA_SSH_PORT`, `FEDORA_GITEA_PORT`, `FEDORA_GITEA_SSH_PORT`, `GITEA_DB_TYPE`, `GITEA_VERSION`, `ACT_RUNNER_VERSION`, `GITEA_BACKUP_MIRROR_INTERVAL`, `BACKUP_RETENTION_COUNT`, `MIGRATE_*`, `GITHUB_MIRROR_INTERVAL`, `PROTECTED_BRANCH`, `REQUIRE_PR_REVIEW`, `REQUIRED_APPROVALS`, `SEMGREP_VERSION`, `TRIVY_VERSION`, `GITLEAKS_VERSION`, `SECURITY_FAIL_ON_ERROR`, `GITEA_ADMIN_TOKEN`, `GITEA_BACKUP_ADMIN_TOKEN`, `GITEA_RUNNER_REGISTRATION_TOKEN`
|
||||
`UNRAID_SSH_PORT`, `FEDORA_SSH_PORT`, `GITEA_DB_TYPE`, `GITEA_VERSION`, `ACT_RUNNER_VERSION`, `GITEA_BACKUP_MIRROR_INTERVAL`, `BACKUP_RETENTION_COUNT`, `MIGRATE_*`, `GITHUB_MIRROR_INTERVAL`, `PROTECTED_BRANCH`, `REQUIRE_PR_REVIEW`, `REQUIRED_APPROVALS`, `SEMGREP_VERSION`, `TRIVY_VERSION`, `GITLEAKS_VERSION`, `SECURITY_FAIL_ON_ERROR`, `GITEA_ADMIN_TOKEN`, `GITEA_BACKUP_ADMIN_TOKEN`, `GITEA_RUNNER_REGISTRATION_TOKEN`
|
||||
| 4 | SSH to Unraid | `ssh_check UNRAID` returns 0 | "Cannot SSH to Unraid at $UNRAID_IP. Run setup/unraid.sh or check SSH config." |
|
||||
| 5 | SSH to Fedora | `ssh_check FEDORA` returns 0 | Same pattern |
|
||||
| 6 | Docker on Unraid | `ssh_exec UNRAID "docker --version"` exits 0 | "Docker not found on Unraid. Run setup/unraid.sh." |
|
||||
| 7 | Docker on Fedora | Same | Same |
|
||||
| 8 | docker-compose on Unraid | `ssh_exec UNRAID "docker compose version"` or `docker-compose --version` | "docker-compose not found on Unraid. Run setup/unraid.sh." |
|
||||
| 9 | docker-compose on Fedora | Same | Same |
|
||||
| 10 | Port 3000 free on Unraid | `ssh_exec UNRAID "! ss -tlnp \| grep -q ':$UNRAID_GITEA_PORT '"` | "Port $UNRAID_GITEA_PORT already in use on Unraid." |
|
||||
| 11 | Port 3000 free on Fedora | Same | Same |
|
||||
| 10 | Container IPs available | Ping-check `UNRAID_GITEA_IP`, `UNRAID_CADDY_IP`, `FEDORA_GITEA_IP`, `FEDORA_CADDY_IP` — warn if responding | "IP $ip is already responding to ping (may be in use)." |
|
||||
| 12 | DNS resolves | `dig +short $GITEA_DOMAIN` returns `$UNRAID_IP` | "$GITEA_DOMAIN does not resolve to $UNRAID_IP." |
|
||||
| 13 | GitHub token valid | `github_api GET /user` returns 200 | "GitHub token invalid. Check GITHUB_TOKEN in .env." |
|
||||
| 14 | GitHub repos exist | For each REPO_N_NAME: `github_api GET /repos/$GITHUB_USERNAME/$REPO_N_NAME` returns 200 | "GitHub repo $REPO_N_NAME not found under $GITHUB_USERNAME." |
|
||||
| 15 | Nginx running on Unraid | `ssh_exec UNRAID "docker ps --filter name=$NGINX_CONTAINER_NAME --format '{{.Status}}'"` contains "Up" | "Nginx container '$NGINX_CONTAINER_NAME' not running on Unraid." |
|
||||
| 16 | Nginx conf dir writable | `ssh_exec UNRAID "test -w $NGINX_CONF_PATH"` | "Nginx config path $NGINX_CONF_PATH not writable." |
|
||||
| 14 | GitHub repos exist | For each repo in `REPO_NAMES`: `github_api GET /repos/$GITHUB_USERNAME/$repo` returns 200 | "GitHub repo $repo not found under $GITHUB_USERNAME." |
|
||||
| 15 | Caddy data path writable | `ssh_exec UNRAID "test -w $CADDY_DATA_PATH"` or parent dir writable | "Caddy data path $CADDY_DATA_PATH not writable on Unraid." |
|
||||
|
||||
**Exit behavior**: Runs ALL checks (doesn't stop at first failure). Prints summary at end. Exits 0 if all pass, 1 if any fail.
|
||||
|
||||
@@ -1038,70 +1041,50 @@ Teardown: `gitea_api DELETE /repos/$ORG/$REPO/branch_protections/$PROTECTED_BRAN
|
||||
|
||||
### 12.1 — `phase8_cutover.sh`
|
||||
|
||||
**Depends on**: Nginx running on Unraid, all prior phases
|
||||
**Produces**: HTTPS access to Gitea, GitHub repos archived
|
||||
**`require_vars`**: `UNRAID_IP`, `UNRAID_SSH_USER`, `UNRAID_SSH_PORT`, `UNRAID_GITEA_PORT`, `GITEA_INTERNAL_URL`, `GITEA_DOMAIN`, `GITEA_ADMIN_TOKEN` *(auto)*, `GITEA_ORG_NAME`, `NGINX_CONTAINER_NAME`, `NGINX_CONF_PATH`, `SSL_EMAIL`, `GITHUB_USERNAME`, `GITHUB_TOKEN`, `REPO_NAMES`
|
||||
**Depends on**: macvlan network created (Phase 1), all prior phases
|
||||
**Produces**: HTTPS access to Gitea via Caddy, GitHub repos marked as mirrors
|
||||
**`require_vars`**: `UNRAID_IP`, `UNRAID_SSH_USER`, `UNRAID_GITEA_IP`, `UNRAID_CADDY_IP`, `GITEA_INTERNAL_URL`, `GITEA_DOMAIN`, `GITEA_ADMIN_TOKEN` *(auto)*, `GITEA_ORG_NAME`, `TLS_MODE`, `CADDY_DOMAIN`, `CADDY_DATA_PATH`, `GITHUB_USERNAME`, `GITHUB_TOKEN`, `REPO_NAMES`. Conditional: `CLOUDFLARE_API_TOKEN` (if `TLS_MODE=cloudflare`), `SSL_CERT_PATH` + `SSL_KEY_PATH` (if `TLS_MODE=existing`).
|
||||
|
||||
**Steps with idempotency**:
|
||||
|
||||
| # | Action | Detail | Idempotency check (skip if true) |
|
||||
|---|--------|--------|----------------------------------|
|
||||
| 1 | Deploy HTTP-only Nginx config | Render `nginx-gitea.conf.tpl` in **HTTP-only mode** (no SSL directives). This serves: (a) reverse proxy to Gitea on port 80, (b) `/.well-known/acme-challenge/` location for Certbot webroot validation. SCP to `$NGINX_CONF_PATH/gitea.conf`. | `ssh_exec UNRAID "test -f $NGINX_CONF_PATH/gitea.conf"` |
|
||||
| 2 | Test Nginx config | `ssh_exec UNRAID "docker exec $NGINX_CONTAINER_NAME nginx -t"` — if fails, remove config and exit 1 | — (always run) |
|
||||
| 3 | Reload Nginx (HTTP) | `ssh_exec UNRAID "docker exec $NGINX_CONTAINER_NAME nginx -s reload"` | — |
|
||||
| 4 | Verify HTTP proxy works | `curl -sf http://$GITEA_DOMAIN/api/v1/version` returns 200 | — |
|
||||
| 5 | Obtain or verify SSL cert | **If `SSL_MODE=letsencrypt`**: `ssh_exec UNRAID "docker run --rm -v /etc/letsencrypt:/etc/letsencrypt -v /var/www/html:/var/www/html certbot/certbot certonly --webroot -w /var/www/html -d $GITEA_DOMAIN --email $SSL_EMAIL --agree-tos --non-interactive"`. **If `SSL_MODE=existing`**: verify cert files exist at `$SSL_CERT_PATH` and `$SSL_KEY_PATH` on Unraid: `ssh_exec UNRAID "test -f $SSL_CERT_PATH && test -f $SSL_KEY_PATH"` — fail if missing. | **letsencrypt**: `ssh_exec UNRAID "test -f /etc/letsencrypt/live/$GITEA_DOMAIN/fullchain.pem"`. **existing**: cert files already verified. |
|
||||
| 6 | Deploy HTTPS Nginx config | Re-render `nginx-gitea.conf.tpl` in **HTTPS mode** (adds `listen 443 ssl`, cert paths, HTTP→HTTPS redirect). SCP to `$NGINX_CONF_PATH/gitea.conf` (overwrites HTTP-only version). | Cert exists from step 5 |
|
||||
| 7 | Test Nginx config (HTTPS) | `ssh_exec UNRAID "docker exec $NGINX_CONTAINER_NAME nginx -t"` — if fails, revert to HTTP-only config and exit 1 | — (always run) |
|
||||
| 8 | Reload Nginx (HTTPS) | `ssh_exec UNRAID "docker exec $NGINX_CONTAINER_NAME nginx -s reload"` | — |
|
||||
| 9 | Verify HTTPS works | `curl -sf https://$GITEA_DOMAIN/api/v1/version` returns 200. Also: `curl -sI https://$GITEA_DOMAIN` to confirm no redirect loops. | — |
|
||||
| 10 | Set up cert auto-renewal cron | **Only if `SSL_MODE=letsencrypt`**: `ssh_exec UNRAID "echo '0 3 * * * docker run --rm -v /etc/letsencrypt:/etc/letsencrypt -v /var/www/html:/var/www/html certbot/certbot renew --quiet && docker exec $NGINX_CONTAINER_NAME nginx -s reload' \| crontab -"` — runs daily at 3 AM. **If `SSL_MODE=existing`**: skip (user manages their own cert renewal). | `ssh_exec UNRAID "crontab -l 2>/dev/null \| grep -q certbot"` |
|
||||
| 11 | Archive GitHub repos | For each repo: (a) Save original description: `github_api GET /repos/$GITHUB_USERNAME/$REPO` → store `description` field, (b) `github_api PATCH /repos/$GITHUB_USERNAME/$REPO {"archived": true, "description": "[MOVED] Now at https://$GITEA_DOMAIN/$GITEA_ORG_NAME/$REPO — was: $ORIGINAL_DESCRIPTION"}` | `github_api GET /repos/$GITHUB_USERNAME/$REPO` has `"archived": true` |
|
||||
| 1 | Create Caddy dirs | `ssh_exec UNRAID "mkdir -p $CADDY_DATA_PATH/{data,config}"` | `test -d $CADDY_DATA_PATH` |
|
||||
| 2 | Deploy Caddyfile | Render `Caddyfile.tpl` with TLS_BLOCK (cloudflare DNS-01 or existing cert paths), SCP to `$CADDY_DATA_PATH/Caddyfile` | `test -f $CADDY_DATA_PATH/Caddyfile` |
|
||||
| 3 | Deploy Caddy docker-compose | Render `docker-compose-caddy.yml.tpl`, SCP to `$CADDY_DATA_PATH/docker-compose.yml` | `test -f $CADDY_DATA_PATH/docker-compose.yml` |
|
||||
| 4 | Start Caddy | `docker compose up -d` in `$CADDY_DATA_PATH` | Caddy container already running |
|
||||
| 5 | Wait for HTTPS | Poll `https://$GITEA_DOMAIN/api/v1/version` with retries until cert is obtained | — |
|
||||
| 6 | Mark GitHub repos as mirrors | Save pre-cutover state to `.manifests/phase8_github_repo_state.json`, update description to `[MIRROR]`, disable wiki/projects/Pages | GitHub repo description starts with `[MIRROR]` |
|
||||
|
||||
**Nginx template must support two render passes**:
|
||||
The `nginx-gitea.conf.tpl` template is rendered with `$SSL_ENABLED=true/false` (set by the script, not .env):
|
||||
- **HTTP-only** (`SSL_ENABLED=false`): `listen 80`, proxy_pass, ACME challenge location (if `SSL_MODE=letsencrypt`), no SSL directives
|
||||
- **HTTPS** (`SSL_ENABLED=true`): `listen 443 ssl`, cert paths, `listen 80` with 301 redirect, proxy_pass, WebSocket headers
|
||||
|
||||
**Cert paths in the template depend on `SSL_MODE`**:
|
||||
- `letsencrypt`: `ssl_certificate /etc/letsencrypt/live/$GITEA_DOMAIN/fullchain.pem`, `ssl_certificate_key /etc/letsencrypt/live/$GITEA_DOMAIN/privkey.pem`
|
||||
- `existing`: `ssl_certificate $SSL_CERT_PATH`, `ssl_certificate_key $SSL_KEY_PATH`
|
||||
|
||||
**Certbot volume mounts**:
|
||||
- `/etc/letsencrypt` on Unraid host → mounted into both Certbot container and Nginx container
|
||||
- `/var/www/html` on Unraid host → Nginx serves this for ACME challenges, Certbot writes challenge files here
|
||||
- Verify these mount paths exist on the Nginx container: `ssh_exec UNRAID "docker inspect $NGINX_CONTAINER_NAME --format '{{json .Mounts}}'"` — if `/etc/letsencrypt` or webroot is not mounted, the script must fail with instructions to add the volume mounts to the Nginx container config.
|
||||
**TLS mode handling**:
|
||||
- `cloudflare`: Caddyfile uses `tls { dns cloudflare {env.CF_API_TOKEN} }`, docker-compose passes `CF_API_TOKEN` env var
|
||||
- `existing`: Caddyfile uses `tls /path/to/cert /path/to/key`, docker-compose mounts cert/key as volumes
|
||||
|
||||
**Done when**:
|
||||
- [ ] `https://$GITEA_DOMAIN` returns valid HTTPS response (no cert errors)
|
||||
- [ ] `curl https://$GITEA_DOMAIN/api/v1/version` returns Gitea version JSON
|
||||
- [ ] Certificate is from Let's Encrypt: `openssl s_client -connect $GITEA_DOMAIN:443 </dev/null 2>/dev/null | openssl x509 -noout -issuer` contains "Let's Encrypt"
|
||||
- [ ] HTTP requests redirect to HTTPS: `curl -sI http://$GITEA_DOMAIN` returns 301 with `Location: https://...`
|
||||
- [ ] Cert auto-renewal cron is installed
|
||||
- [ ] All GitHub repos show as archived with original description preserved in the new description
|
||||
- [ ] Nginx config test passes before every reload (never reload a broken config)
|
||||
- [ ] Running again skips cert generation, skips already-archived repos
|
||||
- [ ] If Nginx container doesn't have required volume mounts, script fails with clear instructions
|
||||
- [ ] `https://$GITEA_DOMAIN` returns valid HTTPS response
|
||||
- [ ] HTTP requests redirect to HTTPS (301)
|
||||
- [ ] SSL certificate is valid (openssl check)
|
||||
- [ ] All repos accessible via HTTPS API
|
||||
- [ ] GitHub repos marked with `[MIRROR]` description prefix
|
||||
|
||||
---
|
||||
|
||||
### 12.2 — `phase8_post_check.sh`
|
||||
|
||||
- [ ] HTTPS works with valid cert: `curl -sf https://$GITEA_DOMAIN/` returns 200
|
||||
- [ ] Certificate is from Let's Encrypt (not self-signed): check with `openssl s_client`
|
||||
- [ ] All repos accessible: `curl -sf https://$GITEA_DOMAIN/$ORG/$REPO` returns 200
|
||||
- [ ] GitHub repos are archived: `github_api GET /repos/$USER/$REPO` has `"archived": true`
|
||||
- [ ] HTTPS works with valid cert: `curl -sf https://$GITEA_DOMAIN/api/v1/version` returns 200
|
||||
- [ ] HTTP redirects to HTTPS: `curl -sI http://$GITEA_DOMAIN/` returns 301
|
||||
- [ ] Certificate is valid: `openssl s_client` returns non-empty issuer
|
||||
- [ ] All repos accessible: API call to each repo returns 200
|
||||
- [ ] GitHub repos marked as mirrors: description starts with `[MIRROR]`
|
||||
|
||||
---
|
||||
|
||||
### 12.3 — `phase8_teardown.sh`
|
||||
|
||||
1. Remove Nginx config: `ssh_exec UNRAID "rm -f $NGINX_CONF_PATH/gitea.conf"`
|
||||
2. Reload Nginx: `ssh_exec UNRAID "docker exec $NGINX_CONTAINER_NAME nginx -s reload"`
|
||||
3. Remove cert renewal cron: `ssh_exec UNRAID "crontab -l 2>/dev/null | grep -v certbot | crontab -"`
|
||||
4. Prompt: "Remove SSL certificates for $GITEA_DOMAIN? [y/N]"
|
||||
- If yes: `ssh_exec UNRAID "rm -rf /etc/letsencrypt/live/$GITEA_DOMAIN /etc/letsencrypt/archive/$GITEA_DOMAIN /etc/letsencrypt/renewal/$GITEA_DOMAIN.conf"`
|
||||
5. Un-archive GitHub repos: for each repo, `github_api PATCH /repos/$GITHUB_USERNAME/$REPO {"archived": false}`. Restore original description if it was saved in the archive description (parse after "was: ").
|
||||
1. Stop + remove Caddy container: `docker compose down` in `$CADDY_DATA_PATH`
|
||||
2. Remove Caddy config files: `rm -f $CADDY_DATA_PATH/docker-compose.yml $CADDY_DATA_PATH/Caddyfile`
|
||||
3. Optionally remove Caddy TLS data: `rm -rf $CADDY_DATA_PATH/data $CADDY_DATA_PATH/config`
|
||||
4. Restore GitHub repo settings from saved Phase 8 state snapshot (description, homepage, wiki, projects, Pages). Falls back to parsing `[MIRROR] ... — was: ORIGINAL` if snapshot missing.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user