docs: fix stale references in PLAN, contracts, README

- README.md: update prompt count ~65 to ~70
- contracts/gitea-api.md: fix "Used in" annotations — POST
  branch_protections is Phase 7 only (not Phase 9), DELETE
  branch_protections is Phase 7 teardown only (Phase 9 uses PATCH),
  add missing used-in entries for GET commits and GET contents
- PLAN.md: replace stale UNRAID_GITEA_PORT/FEDORA_GITEA_PORT with
  macvlan vars, update REPO_N_NAME to REPO_NAMES, fix version defaults
  (1.23->1.25, 0.2.11->0.3.0), add missing setup/ files to tree,
  fix prompt table to match current ~70 variables, update require_vars
  lists to match actual scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
S
2026-03-01 13:21:40 -05:00
parent 9494645b3a
commit 13f73b850f
3 changed files with 44 additions and 70 deletions

102
PLAN.md
View File

@@ -36,9 +36,14 @@ gitea-migration/
│ └── common.sh │ └── common.sh
├── setup/ ├── setup/
│ ├── configure_env.sh │ ├── configure_env.sh
│ ├── configure_runners.sh
│ ├── macbook.sh │ ├── macbook.sh
│ ├── unraid.sh │ ├── unraid.sh
── fedora.sh ── fedora.sh
│ ├── cross_host_ssh.sh
│ ├── env_to_bitwarden.sh
│ ├── bitwarden_to_env.sh
│ └── cleanup.sh
├── templates/ ├── templates/
│ ├── docker-compose-gitea.yml.tpl │ ├── docker-compose-gitea.yml.tpl
│ ├── docker-compose-runner.yml.tpl │ ├── docker-compose-runner.yml.tpl
@@ -330,12 +335,12 @@ gitea-migration/
- Gitea image pinned to `$GITEA_VERSION` - Gitea image pinned to `$GITEA_VERSION`
- Container name: `gitea` - Container name: `gitea`
- Volumes: `$DATA_PATH/data:/data`, `$DATA_PATH/config:/data/gitea/conf` - Volumes: `$DATA_PATH/data:/data`, `$DATA_PATH/config:/data/gitea/conf`
- Ports: `$GITEA_PORT:3000`, `$GITEA_SSH_PORT:22` (add `GITEA_SSH_PORT` to .env if missing) - Networks: macvlan with `$GITEA_CONTAINER_IP` (dedicated LAN IP, no port mapping)
- Environment: `USER_UID=1000`, `USER_GID=1000` - Environment: `USER_UID=1000`, `USER_GID=1000`
- Restart policy: `unless-stopped` - Restart policy: `unless-stopped`
- No database service (SQLite is file-based) - No database service for sqlite3; conditional DB service block for external DBs
**Variables used** (must all be in .env): `GITEA_VERSION`, `DATA_PATH`, `GITEA_PORT` **Variables used**: `GITEA_VERSION`, `DATA_PATH`, `GITEA_CONTAINER_IP` (+ DB vars if external DB)
**Done when**: **Done when**:
- [ ] `render_template` produces valid YAML (test with `python3 -c "import yaml; yaml.safe_load(open('output.yml'))"` or `yq`) - [ ] `render_template` produces valid YAML (test with `python3 -c "import yaml; yaml.safe_load(open('output.yml'))"` or `yq`)
@@ -502,56 +507,25 @@ gitea-migration/
| # | Variable | Validation | Default | | # | Variable | Validation | Default |
|---|----------|------------|---------| |---|----------|------------|---------|
| 1 | `UNRAID_IP` | IP address | — | | 1-5 | `UNRAID_IP`, `UNRAID_SSH_USER`, `UNRAID_SSH_PORT`, `UNRAID_GITEA_DATA_PATH`, `UNRAID_SSH_KEY` | IP, non-empty, port, path, optional | —, —, 22, —, — |
| 2 | `UNRAID_SSH_USER` | Non-empty | — | | 6-10 | `FEDORA_IP`, `FEDORA_SSH_USER`, `FEDORA_SSH_PORT`, `FEDORA_GITEA_DATA_PATH`, `FEDORA_SSH_KEY` | IP, non-empty, port, path, optional | —, —, 22, —, — |
| 3 | `UNRAID_SSH_PORT` | Port | 22 | | 11-24 | Macvlan networking: `*_MACVLAN_PARENT`, `*_MACVLAN_SUBNET`, `*_MACVLAN_GATEWAY`, `*_MACVLAN_IP_RANGE`, `*_GITEA_IP`, `*_DB_IP`, `UNRAID_CADDY_IP` (per host) | non-empty, non-empty, IP, non-empty, IP, optional, IP | |
| 4 | `UNRAID_GITEA_PORT` | Port | 3000 | | 25-30 | `GITEA_ADMIN_USER`, `GITEA_ADMIN_PASSWORD`, `GITEA_ADMIN_EMAIL`, `GITEA_ORG_NAME`, `GITEA_INSTANCE_NAME`, `GITEA_DB_TYPE` | non-empty, password, email, non-empty, non-empty, db_type | —, —, —, —, —, sqlite3 |
| 5 | `UNRAID_GITEA_SSH_PORT` | Port | 2222 | | 31-35 | *(conditional, only if DB_TYPE != sqlite3)* `GITEA_DB_HOST`, `GITEA_DB_PORT`, `GITEA_DB_NAME`, `GITEA_DB_USER`, `GITEA_DB_PASSWD` | non-empty, port, non-empty, non-empty, password | —, auto, gitea, gitea, — |
| 6 | `UNRAID_GITEA_DATA_PATH` | Absolute path | — | | 36-37 | `GITEA_VERSION`, `ACT_RUNNER_VERSION` | Non-empty | 1.25, 0.3.0 |
| 7 | `FEDORA_IP` | IP address | — | | 38-39 | `GITEA_DOMAIN`, `GITEA_INTERNAL_URL` | Non-empty, URL | — |
| 8 | `FEDORA_SSH_USER` | Non-empty | — | | 40-43 | `GITEA_BACKUP_INTERNAL_URL`, `GITEA_BACKUP_MIRROR_INTERVAL`, `BACKUP_STORAGE_PATH`, `BACKUP_RETENTION_COUNT` | URL, non-empty, path, integer | —, 8h, —, 5 |
| 9 | `FEDORA_SSH_PORT` | Port | 22 | | 44-45 | `GITHUB_USERNAME`, `GITHUB_TOKEN` | Non-empty | |
| 10 | `FEDORA_GITEA_PORT` | Port | 3000 | | 46 | "How many repos?" + N × repo names → `REPO_NAMES` | positive integer, non-empty | — |
| 11 | `FEDORA_GITEA_SSH_PORT` | Port | 2222 | | 47-53 | `MIGRATE_ISSUES`, `MIGRATE_LABELS`, `MIGRATE_MILESTONES`, `MIGRATE_WIKI`, `MIGRATION_POLL_INTERVAL_SEC`, `MIGRATION_POLL_TIMEOUT_SEC`, `GITHUB_MIRROR_INTERVAL` | bool, bool, bool, bool, positive_integer, positive_integer, non-empty | false, true, false, false, 3, 600, 8h |
| 12 | `FEDORA_GITEA_DATA_PATH` | Absolute path | — | | 54-57 | `RUNNER_DEFAULT_IMAGE`, `RUNNER_DATA_BASE_PATH`, `LOCAL_RUNNER_DATA_BASE_PATH`, `LOCAL_REGISTRY` | non-empty, non-empty, non-empty, optional | catthehacker/ubuntu:act-latest, /mnt/nvme/gitea-runner, ~/gitea-runner, — |
| 13 | `GITEA_ADMIN_USER` | Non-empty | — | | 58-60 | `TLS_MODE`, `CADDY_DOMAIN`, `CADDY_DATA_PATH` | tls_mode, non-empty, path | cloudflare, —, — |
| 14 | `GITEA_ADMIN_PASSWORD` | Min 8 chars | — | | 61-63 | *(conditional)* `CLOUDFLARE_API_TOKEN` or `SSL_CERT_PATH` + `SSL_KEY_PATH` | non-empty / path | — |
| 15 | `GITEA_ADMIN_EMAIL` | Email | — | | 64-66 | `PROTECTED_BRANCH`, `REQUIRE_PR_REVIEW`, `REQUIRED_APPROVALS` | Non-empty, bool, integer | main, false, 1 |
| 16 | `GITEA_ORG_NAME` | Non-empty | — | | 67-70 | `SEMGREP_VERSION`, `TRIVY_VERSION`, `GITLEAKS_VERSION`, `SECURITY_FAIL_ON_ERROR` | Non-empty, non-empty, non-empty, bool | latest, latest, latest, true |
| 17 | `GITEA_INSTANCE_NAME` | Non-empty | — |
| 18 | `GITEA_DB_TYPE` | Non-empty | sqlite3 |
| 19 | `GITEA_VERSION` | Non-empty | 1.23 |
| 20 | `ACT_RUNNER_VERSION` | Non-empty | 0.2.11 |
| 21 | `GITEA_DOMAIN` | Non-empty | — |
| 22 | `GITEA_INTERNAL_URL` | URL | — |
| 23 | `GITEA_BACKUP_INTERNAL_URL` | URL | — |
| 24 | `GITEA_BACKUP_MIRROR_INTERVAL` | Non-empty | 8h |
| 25 | `BACKUP_STORAGE_PATH` | Absolute path | — |
| 26 | `BACKUP_RETENTION_COUNT` | Integer | 5 |
| 27 | `GITHUB_USERNAME` | Non-empty | — |
| 28 | `GITHUB_TOKEN` | Non-empty | — |
| 29 | `REPO_NAMES` | Non-empty | — |
| 30 | `MIGRATE_ISSUES` | true/false | false |
| 33 | `MIGRATE_LABELS` | true/false | true |
| 34 | `MIGRATE_MILESTONES` | true/false | false |
| 35 | `MIGRATE_WIKI` | true/false | false |
| 36 | `GITHUB_MIRROR_INTERVAL` | Non-empty | 8h |
| 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 |
| 47 | `SEMGREP_VERSION` | Non-empty | latest |
| 48 | `TRIVY_VERSION` | Non-empty | latest |
| 49 | `GITLEAKS_VERSION` | Non-empty | latest |
| 50 | `SECURITY_FAIL_ON_ERROR` | true/false | true |
**Done when**: **Done when**:
- [ ] Each prompt shows progress: `[N/~62]` with section header when entering a new section - [x] Each prompt shows progress: `[N/~70]` with section header when entering a new section
- [ ] Running with no existing `.env` walks through all prompts and produces a valid `.env` - [ ] 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 - [ ] 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 - [ ] DB prompts are conditional: if `GITEA_DB_TYPE` is not `sqlite3`, prompt for host/port/name/user/password
@@ -709,7 +683,7 @@ gitea-migration/
**Depends on**: preflight passed, templates exist **Depends on**: preflight passed, templates exist
**Produces**: Running Gitea instance on Unraid, admin user, API token in .env, org created **Produces**: Running Gitea instance on Unraid, admin user, API token in .env, org created
**`require_vars`**: `UNRAID_IP`, `UNRAID_SSH_USER`, `UNRAID_SSH_PORT`, `UNRAID_GITEA_PORT`, `UNRAID_GITEA_SSH_PORT`, `UNRAID_GITEA_DATA_PATH`, `GITEA_ADMIN_USER`, `GITEA_ADMIN_PASSWORD`, `GITEA_ADMIN_EMAIL`, `GITEA_ORG_NAME`, `GITEA_INSTANCE_NAME`, `GITEA_DB_TYPE`, `GITEA_VERSION`, `GITEA_INTERNAL_URL`, `GITEA_DOMAIN` **`require_vars`**: `UNRAID_IP`, `UNRAID_SSH_USER`, `UNRAID_SSH_PORT`, `UNRAID_GITEA_DATA_PATH`, `UNRAID_MACVLAN_PARENT`, `UNRAID_MACVLAN_SUBNET`, `UNRAID_MACVLAN_GATEWAY`, `UNRAID_MACVLAN_IP_RANGE`, `UNRAID_GITEA_IP`, `GITEA_ADMIN_USER`, `GITEA_ADMIN_PASSWORD`, `GITEA_ADMIN_EMAIL`, `GITEA_ORG_NAME`, `GITEA_INSTANCE_NAME`, `GITEA_DB_TYPE`, `GITEA_VERSION`, `GITEA_INTERNAL_URL`, `GITEA_DOMAIN` (+ `UNRAID_DB_IP` + DB vars if DB_TYPE != sqlite3)
**Steps with idempotency**: **Steps with idempotency**:
@@ -777,10 +751,10 @@ gitea-migration/
**Depends on**: preflight passed, templates exist **Depends on**: preflight passed, templates exist
**Produces**: Running Gitea instance on Fedora, admin user, API token in .env **Produces**: Running Gitea instance on Fedora, admin user, API token in .env
**`require_vars`**: `FEDORA_IP`, `FEDORA_SSH_USER`, `FEDORA_SSH_PORT`, `FEDORA_GITEA_PORT`, `FEDORA_GITEA_SSH_PORT`, `FEDORA_GITEA_DATA_PATH`, `GITEA_ADMIN_USER`, `GITEA_ADMIN_PASSWORD`, `GITEA_ADMIN_EMAIL`, `GITEA_INSTANCE_NAME`, `GITEA_DB_TYPE`, `GITEA_VERSION`, `GITEA_BACKUP_INTERNAL_URL` **`require_vars`**: `FEDORA_IP`, `FEDORA_SSH_USER`, `FEDORA_SSH_PORT`, `FEDORA_GITEA_DATA_PATH`, `FEDORA_MACVLAN_PARENT`, `FEDORA_MACVLAN_SUBNET`, `FEDORA_MACVLAN_GATEWAY`, `FEDORA_MACVLAN_IP_RANGE`, `FEDORA_GITEA_IP`, `GITEA_ADMIN_USER`, `GITEA_ADMIN_PASSWORD`, `GITEA_ADMIN_EMAIL`, `GITEA_DB_TYPE`, `GITEA_VERSION`, `GITEA_BACKUP_INTERNAL_URL` (+ `FEDORA_DB_IP` + DB vars if DB_TYPE != sqlite3)
**Identical to phase1 except**: **Identical to phase1 except**:
- Target: Fedora (uses `FEDORA_IP`, `FEDORA_SSH_USER`, `FEDORA_SSH_PORT`, `FEDORA_GITEA_DATA_PATH`, `FEDORA_GITEA_PORT`) - Target: Fedora (uses `FEDORA_IP`, `FEDORA_SSH_USER`, `FEDORA_SSH_PORT`, `FEDORA_GITEA_DATA_PATH`, `FEDORA_GITEA_IP`)
- Uses `GITEA_BACKUP_INTERNAL_URL` for API calls - Uses `GITEA_BACKUP_INTERNAL_URL` for API calls
- Saves token as `GITEA_BACKUP_ADMIN_TOKEN` - Saves token as `GITEA_BACKUP_ADMIN_TOKEN`
- Uses same admin credentials (`GITEA_ADMIN_USER`/`GITEA_ADMIN_PASSWORD`/`GITEA_ADMIN_EMAIL`) - Uses same admin credentials (`GITEA_ADMIN_USER`/`GITEA_ADMIN_PASSWORD`/`GITEA_ADMIN_EMAIL`)
@@ -884,23 +858,23 @@ For each runner in `runners.conf`: `manage_runner.sh remove --name <name>`. Clea
### 8.1 — `phase4_migrate_repos.sh` ### 8.1 — `phase4_migrate_repos.sh`
**Depends on**: Phase 1 + Phase 2 completed (both Gitea instances running) **Depends on**: Phase 1 + Phase 2 completed (both Gitea instances running)
**Produces**: All 3 repos on Unraid primary under org + pull mirrors on Fedora **Produces**: All repos on Unraid primary under org + pull mirrors on Fedora
**`require_vars`**: `GITEA_ADMIN_TOKEN` *(auto)*, `GITEA_BACKUP_ADMIN_TOKEN` *(auto)*, `GITEA_INTERNAL_URL`, `GITEA_BACKUP_INTERNAL_URL`, `GITEA_ORG_NAME`, `GITEA_ADMIN_USER`, `GITEA_ADMIN_PASSWORD`, `GITHUB_USERNAME`, `GITHUB_TOKEN`, `REPO_NAMES`, `MIGRATE_ISSUES`, `MIGRATE_LABELS`, `MIGRATE_MILESTONES`, `MIGRATE_WIKI`, `GITEA_BACKUP_MIRROR_INTERVAL` **`require_vars`**: `GITEA_ADMIN_TOKEN` *(auto)*, `GITEA_BACKUP_ADMIN_TOKEN` *(auto)*, `GITEA_INTERNAL_URL`, `GITEA_BACKUP_INTERNAL_URL`, `GITEA_ORG_NAME`, `GITEA_ADMIN_USER`, `GITEA_ADMIN_PASSWORD`, `GITHUB_USERNAME`, `GITHUB_TOKEN`, `REPO_NAMES`, `MIGRATE_ISSUES`, `MIGRATE_LABELS`, `MIGRATE_MILESTONES`, `MIGRATE_WIKI`, `GITEA_BACKUP_MIRROR_INTERVAL`
**Steps**: **Steps**:
For each `REPO_N_NAME` (N=1,2,3): For each repo in `REPO_NAMES`:
| # | Action | API call | Idempotency check | | # | Action | API call | Idempotency check |
|---|--------|----------|-------------------| |---|--------|----------|-------------------|
| 1 | Import repo from GitHub to Unraid | `gitea_api POST /repos/migrate` with `clone_addr=https://github.com/$GITHUB_USERNAME/$REPO_N_NAME.git`, `auth_token=$GITHUB_TOKEN`, `repo_owner=$GITEA_ORG_NAME`, `repo_name=$REPO_N_NAME`, `mirror=false`, `issues=$MIGRATE_ISSUES`, `labels=$MIGRATE_LABELS`, `milestones=$MIGRATE_MILESTONES`, `wiki=$MIGRATE_WIKI` | `gitea_api GET /repos/$GITEA_ORG_NAME/$REPO_N_NAME` returns 200 | | 1 | Import repo from GitHub to Unraid | `gitea_api POST /repos/migrate` with `clone_addr=https://github.com/$GITHUB_USERNAME/$REPO.git`, `auth_token=$GITHUB_TOKEN`, `repo_owner=$GITEA_ORG_NAME`, `repo_name=$REPO`, `mirror=false`, `issues=$MIGRATE_ISSUES`, `labels=$MIGRATE_LABELS`, `milestones=$MIGRATE_MILESTONES`, `wiki=$MIGRATE_WIKI` | `gitea_api GET /repos/$GITEA_ORG_NAME/$REPO` returns 200 |
| 2 | Wait for migration | Poll `gitea_api GET /repos/$GITEA_ORG_NAME/$REPO_N_NAME` until `empty=false` (repo has content) | — | | 2 | Wait for migration | Poll `gitea_api GET /repos/$GITEA_ORG_NAME/$REPO` until `empty=false` (repo has content) | — |
| 3 | Create pull mirror on Fedora | `gitea_backup_api POST /repos/migrate` with `clone_addr=$GITEA_INTERNAL_URL/$GITEA_ORG_NAME/$REPO_N_NAME.git`, `auth_username=$GITEA_ADMIN_USER`, `auth_password=$GITEA_ADMIN_PASSWORD`, `repo_owner=$GITEA_BACKUP_ADMIN_USER`, `mirror=true`, `mirror_interval=$GITEA_BACKUP_MIRROR_INTERVAL` | `gitea_backup_api GET /repos/$GITEA_BACKUP_ADMIN_USER/$REPO_N_NAME` returns 200 | | 3 | Create pull mirror on Fedora | `gitea_backup_api POST /repos/migrate` with `clone_addr=$GITEA_INTERNAL_URL/$GITEA_ORG_NAME/$REPO.git`, `auth_username=$GITEA_ADMIN_USER`, `auth_password=$GITEA_ADMIN_PASSWORD`, `repo_owner=$GITEA_ADMIN_USER`, `mirror=true`, `mirror_interval=$GITEA_BACKUP_MIRROR_INTERVAL` | `gitea_backup_api GET /repos/$GITEA_ADMIN_USER/$REPO` returns 200 |
**Done when**: **Done when**:
- [ ] All 3 repos exist under `$GITEA_ORG_NAME` on Unraid with commits - [ ] All repos exist under `$GITEA_ORG_NAME` on Unraid with commits
- [ ] Each repo's default branch matches the GitHub source - [ ] Each repo's default branch matches the GitHub source
- [ ] All 3 mirror repos exist on Fedora under admin user - [ ] All mirror repos exist on Fedora under admin user
- [ ] Fedora mirrors show `mirror=true` in API response - [ ] Fedora mirrors show `mirror=true` in API response
- [ ] Fedora mirrors have synced at least once (has commits) - [ ] Fedora mirrors have synced at least once (has commits)
- [ ] Running again skips all existing repos - [ ] Running again skips all existing repos
@@ -920,8 +894,8 @@ For each `REPO_N_NAME` (N=1,2,3):
### 8.3 — `phase4_teardown.sh` ### 8.3 — `phase4_teardown.sh`
1. For each repo: `gitea_api DELETE /repos/$GITEA_ORG_NAME/$REPO_N_NAME` 1. For each repo in `REPO_NAMES`: `gitea_api DELETE /repos/$GITEA_ORG_NAME/$REPO`
2. For each mirror: `gitea_backup_api DELETE /repos/$ADMIN/$REPO_N_NAME` 2. For each mirror: `gitea_backup_api DELETE /repos/$ADMIN/$REPO`
3. Prompt before each deletion 3. Prompt before each deletion
--- ---

View File

@@ -67,7 +67,7 @@ gitea-migration/
├── runners.conf.example # Runner definitions template ├── runners.conf.example # Runner definitions template
├── lib/common.sh # Shared functions + .env validators ├── lib/common.sh # Shared functions + .env validators
├── setup/ ├── setup/
│ ├── configure_env.sh # Interactive .env wizard (~65 prompts) │ ├── configure_env.sh # Interactive .env wizard (~70 prompts)
│ ├── configure_runners.sh # Interactive runner definition wizard │ ├── configure_runners.sh # Interactive runner definition wizard
│ ├── macbook.sh # Local prerequisites (brew packages) │ ├── macbook.sh # Local prerequisites (brew packages)
│ ├── unraid.sh # Remote prerequisites (static binaries) │ ├── unraid.sh # Remote prerequisites (static binaries)

View File

@@ -206,7 +206,7 @@ Auth: `Authorization: token ${GITEA_ADMIN_TOKEN}` (or `GITEA_BACKUP_ADMIN_TOKEN`
### GET /repos/{owner}/{repo}/commits ### GET /repos/{owner}/{repo}/commits
**Used in**: Phase 4 post-check **Used in**: Phase 4 (migration polling), Phase 4 post-check, Phase 6 post-check (compare HEAD SHAs)
**Purpose**: Verify repo has commits **Purpose**: Verify repo has commits
**Query params**: `?limit=1` **Query params**: `?limit=1`
@@ -217,7 +217,7 @@ Auth: `Authorization: token ${GITEA_ADMIN_TOKEN}` (or `GITEA_BACKUP_ADMIN_TOKEN`
### GET /repos/{owner}/{repo}/contents/{filepath} ### GET /repos/{owner}/{repo}/contents/{filepath}
**Used in**: Phase 5 post-check (check `.gitea/workflows/` exists), Phase 9 (check security workflow exists) **Used in**: Phase 5 (idempotency check), Phase 5 post-check, Phase 5 teardown, Phase 9 (idempotency check), Phase 9 post-check, Phase 9 teardown
**Purpose**: Get file or directory contents **Purpose**: Get file or directory contents
**Response** (200): File/directory metadata **Response** (200): File/directory metadata
@@ -341,7 +341,7 @@ Auth: `Authorization: token ${GITEA_ADMIN_TOKEN}` (or `GITEA_BACKUP_ADMIN_TOKEN`
### POST /repos/{owner}/{repo}/branch_protections ### POST /repos/{owner}/{repo}/branch_protections
**Used in**: Phase 7, Phase 9 (update with status checks) **Used in**: Phase 7
**Purpose**: Create branch protection rule **Purpose**: Create branch protection rule
**Request**: **Request**:
@@ -383,7 +383,7 @@ Auth: `Authorization: token ${GITEA_ADMIN_TOKEN}` (or `GITEA_BACKUP_ADMIN_TOKEN`
### PATCH /repos/{owner}/{repo}/branch_protections/{name} ### PATCH /repos/{owner}/{repo}/branch_protections/{name}
**Used in**: Phase 9 (add status check contexts) **Used in**: Phase 9 (add status check contexts), Phase 9 teardown (clear status check contexts)
**Purpose**: Update branch protection rule **Purpose**: Update branch protection rule
**Request** (partial update): **Request** (partial update):
@@ -400,7 +400,7 @@ Auth: `Authorization: token ${GITEA_ADMIN_TOKEN}` (or `GITEA_BACKUP_ADMIN_TOKEN`
### DELETE /repos/{owner}/{repo}/branch_protections/{name} ### DELETE /repos/{owner}/{repo}/branch_protections/{name}
**Used in**: Phase 7 teardown, Phase 9 teardown **Used in**: Phase 7 teardown
**Purpose**: Delete branch protection rule **Purpose**: Delete branch protection rule
**Response**: 204 No Content **Response**: 204 No Content