MIL-002: GitHub and Gitea steps (dry run, repositories, push mirror) and split into library files #25

Merged
Tirsvad merged 1 commits from mil-002-repositories-and-mirror into main 2026-10-05 09:33:07 +02:00
Owner

Second code phase of RepoFoundry: the GitHub and Gitea steps, and the split of
create-project.sh into files with one responsibility each.

Implements MIL-002 and US-001.02 (UC-001 steps 4 to 7).

What the script does now

  • Dry run by default. Without --apply it only reads from the hosts (GET
    requests) and prints a plan. With --apply it prints the plan again and asks
    a final "Create these now" before it creates anything. Nothing is ever
    deleted.
  • Preflight (#9): both tokens, that the owners exist (user or
    organization, and the right to create repositories there), that the name is
    free, that Gitea offers AGPL-3.0 when GitHub is chosen, and the SSH test on
    port 10022 (its result decides later whether origin uses SSH or HTTPS).
  • Repositories (#10, #11): the GitHub repository is created empty and only
    if GitHub is chosen. Choosing GitHub also applies the AGPL-3.0 license to the
    Gitea repository (created with auto_init, so it is not empty); without
    GitHub the Gitea repository is empty.
  • Push mirror (#12): Gitea -> GitHub, with the GitHub token as the mirror
    password (sent in the request body, never in an address). The mirror is read
    back, a first sync is requested, and the script warns if Gitea ignored
    sync_on_commit or the first sync reported an error.
  • Partial failure and resume (#13): a failed step stops the run and prints
    what exists, what failed and how to continue. A repeated run offers to reuse
    an empty repository (Gitea: or one holding only the license) and an existing
    mirror.
  • README (#14): token permissions and known limitations, each marked
    confirmed or not confirmed.

New optional config keys: GITEA_SSH_PORT (default 10022) and
MIRROR_INTERVAL (default 10m0s).

The split

src/create-project.sh is now the entry point (header, strict mode, loading,
main). The work lives in src/lib/, one responsibility per file (20 files,
listed in the README). Each file names its responsibility and the functions it
provides. tests/test-structure.sh guards it: every file is loaded, no
function is defined twice, files do nothing when loaded, and none is ignored
by git.

.gitignore gets !src/lib: the Python template ignores any folder named
lib/, which would have kept the new files out of git.

Verification

  • tests/run-tests.sh: 599 checks, 0 failed; shellcheck -x over the entry
    point and every library file, and shfmt, are clean. The tests use a stub
    curl and ssh; no real host is contacted.
  • Mutation checks caught: --apply no longer required; die no longer
    redacting a token; a function defined twice; !src/lib removed.
  • A real dry run against git.tirsystem.com and GitHub with the real .env
    passed every preflight check and printed no token. This was read-only.

Not verified yet (why this PR uses Refs, not Closes)

  • No real --apply run has happened: the creation calls, the real mirror push,
    and the claim that auto_init plus a license leaves only LICENSE are
    verified against stubs only. MIL-002's Go/No-Go needs a run on disposable
    repositories (a user owner and an organization owner, and a push through the
    mirror).
  • Two token scopes are not confirmed by the documentation and are marked so in
    the README: read:org on GitHub, and write:organization on Gitea for
    organization repositories.

Notes for the reviewer

  • Reading order: src/create-project.sh, then the files in src/lib/ named in
    the README; preflight.sh, repositories.sh, mirror.sh and apply.sh are
    the new behaviour, the rest moved unchanged.
  • The mirror password is stored by the Gitea server; this is documented under
    known limitations.

Refs #9
Refs #10
Refs #11
Refs #12
Refs #13
Refs #14

🤖 Generated with Claude Code

Second code phase of RepoFoundry: the GitHub and Gitea steps, and the split of `create-project.sh` into files with one responsibility each. Implements MIL-002 and US-001.02 (UC-001 steps 4 to 7). ## What the script does now - **Dry run by default.** Without `--apply` it only reads from the hosts (GET requests) and prints a plan. With `--apply` it prints the plan again and asks a final "Create these now" before it creates anything. Nothing is ever deleted. - **Preflight (#9):** both tokens, that the owners exist (user or organization, and the right to create repositories there), that the name is free, that Gitea offers `AGPL-3.0` when GitHub is chosen, and the SSH test on port 10022 (its result decides later whether `origin` uses SSH or HTTPS). - **Repositories (#10, #11):** the GitHub repository is created empty and only if GitHub is chosen. Choosing GitHub also applies the AGPL-3.0 license to the Gitea repository (created with `auto_init`, so it is not empty); without GitHub the Gitea repository is empty. - **Push mirror (#12):** Gitea -> GitHub, with the GitHub token as the mirror password (sent in the request body, never in an address). The mirror is read back, a first sync is requested, and the script warns if Gitea ignored `sync_on_commit` or the first sync reported an error. - **Partial failure and resume (#13):** a failed step stops the run and prints what exists, what failed and how to continue. A repeated run offers to reuse an empty repository (Gitea: or one holding only the license) and an existing mirror. - **README (#14):** token permissions and known limitations, each marked confirmed or not confirmed. New optional config keys: `GITEA_SSH_PORT` (default 10022) and `MIRROR_INTERVAL` (default 10m0s). ## The split `src/create-project.sh` is now the entry point (header, strict mode, loading, `main`). The work lives in `src/lib/`, one responsibility per file (20 files, listed in the README). Each file names its responsibility and the functions it provides. `tests/test-structure.sh` guards it: every file is loaded, no function is defined twice, files do nothing when loaded, and none is ignored by git. `.gitignore` gets `!src/lib`: the Python template ignores any folder named `lib/`, which would have kept the new files out of git. ## Verification - `tests/run-tests.sh`: 599 checks, 0 failed; `shellcheck -x` over the entry point and every library file, and `shfmt`, are clean. The tests use a stub curl and ssh; no real host is contacted. - Mutation checks caught: `--apply` no longer required; `die` no longer redacting a token; a function defined twice; `!src/lib` removed. - A real **dry run** against git.tirsystem.com and GitHub with the real `.env` passed every preflight check and printed no token. This was read-only. ## Not verified yet (why this PR uses `Refs`, not `Closes`) - No real `--apply` run has happened: the creation calls, the real mirror push, and the claim that `auto_init` plus a license leaves only `LICENSE` are verified against stubs only. MIL-002's Go/No-Go needs a run on disposable repositories (a user owner and an organization owner, and a push through the mirror). - Two token scopes are not confirmed by the documentation and are marked so in the README: `read:org` on GitHub, and `write:organization` on Gitea for organization repositories. ## Notes for the reviewer - Reading order: `src/create-project.sh`, then the files in `src/lib/` named in the README; `preflight.sh`, `repositories.sh`, `mirror.sh` and `apply.sh` are the new behaviour, the rest moved unchanged. - The mirror password is stored by the Gitea server; this is documented under known limitations. Refs #9 Refs #10 Refs #11 Refs #12 Refs #13 Refs #14 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Tirsvad added 1 commit 2026-10-05 09:30:28 +02:00
Dry run by default: the script reads from both hosts (tokens, owners,
names, license, SSH) and prints a plan; --apply creates the repositories
and the Gitea -> GitHub push mirror after a final yes. Choosing GitHub
applies the AGPL-3.0 license to the Gitea repository. A failed step is
reported with what exists and how to continue; nothing is ever deleted.

create-project.sh is now the entry point; the work lives in src/lib/, one
responsibility per file. .gitignore gets !src/lib (the Python template
ignores any lib/ folder). Tests grow to 599 checks, with a stub curl and
ssh, and guards for the file structure.

Task: MIL-002#1
Task: MIL-002#2
Task: MIL-002#3
Task: MIL-002#4
Task: MIL-002#5
Task: MIL-002#6
Refs #9
Refs #10
Refs #11
Refs #12
Refs #13
Refs #14

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Tirsvad merged commit 5584ddf397 into main 2026-10-05 09:33:07 +02:00
Sign in to join this conversation.