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.
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)
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 main2026-10-05 09:33:07 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Second code phase of RepoFoundry: the GitHub and Gitea steps, and the split of
create-project.shinto files with one responsibility each.Implements MIL-002 and US-001.02 (UC-001 steps 4 to 7).
What the script does now
--applyit only reads from the hosts (GETrequests) and prints a plan. With
--applyit prints the plan again and asksa final "Create these now" before it creates anything. Nothing is ever
deleted.
organization, and the right to create repositories there), that the name is
free, that Gitea offers
AGPL-3.0when GitHub is chosen, and the SSH test onport 10022 (its result decides later whether
originuses SSH or HTTPS).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); withoutGitHub the Gitea repository is empty.
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_commitor the first sync reported an error.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.
confirmed or not confirmed.
New optional config keys:
GITEA_SSH_PORT(default 10022) andMIRROR_INTERVAL(default 10m0s).The split
src/create-project.shis now the entry point (header, strict mode, loading,main). The work lives insrc/lib/, one responsibility per file (20 files,listed in the README). Each file names its responsibility and the functions it
provides.
tests/test-structure.shguards it: every file is loaded, nofunction is defined twice, files do nothing when loaded, and none is ignored
by git.
.gitignoregets!src/lib: the Python template ignores any folder namedlib/, which would have kept the new files out of git.Verification
tests/run-tests.sh: 599 checks, 0 failed;shellcheck -xover the entrypoint and every library file, and
shfmt, are clean. The tests use a stubcurl and ssh; no real host is contacted.
--applyno longer required;dieno longerredacting a token; a function defined twice;
!src/libremoved..envpassed every preflight check and printed no token. This was read-only.
Not verified yet (why this PR uses
Refs, notCloses)--applyrun has happened: the creation calls, the real mirror push,and the claim that
auto_initplus a license leaves onlyLICENSEareverified 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).
the README:
read:orgon GitHub, andwrite:organizationon Gitea fororganization repositories.
Notes for the reviewer
src/create-project.sh, then the files insrc/lib/named inthe README;
preflight.sh,repositories.sh,mirror.shandapply.sharethe new behaviour, the rest moved unchanged.
known limitations.
Refs #9
Refs #10
Refs #11
Refs #12
Refs #13
Refs #14
🤖 Generated with Claude Code