Set up a hub's platform¶
touchmark setup github and touchmark setup gitlab do the platform's part of creating
a hub: the reader and the writer, the write key kept to the default branch, and the hub's
protection. Run it once from a checkout of the hub, with a maintainer's token.
cd ~/src/engineering-assets # setup reads hub.yml and the origin remote
read -rs TOUCHMARK_HUB_TOKEN && export TOUCHMARK_HUB_TOKEN # never on the command line
touchmark setup gitlab --hub . --group acme/services --dry-run
touchmark setup gitlab --hub . --group acme/services
touchmark setup github --hub .
It sets up only layouts that keep the write key isolated (security.write_isolation:
platform or external), reads before it writes, and changes only what differs: a second
run writes nothing, and --dry-run only reads. It never prints a token or a key. It ends
with the checks of doctor --hub-token.
GitLab¶
Everything goes through the API. The token needs the api scope and must belong to a
Maintainer of the hub and an Owner of the group whose projects are the targets (or to an
administrator). The hub must live outside that group, unless hub.yml says
security.writer_on_hub: guard: then setup accepts a hub inside it, checks that the writer
is below Maintainer on the hub (writer-guard), and also turns on Pipelines must succeed,
turns off Skipped pipelines are considered successful (merge-checks), and sets the CI/CD
configuration file to .gitlab-ci.yml@<hub path>:<default branch> (ci-config), so that
merge request pipelines run the default branch's CI file. The final check also reads the
variables of the groups above the hub, which GitLab shows to their Owners only: for a
Maintainer that check stays unknown.
setup:
- makes the reader and the writer: service accounts where GitLab lets the token
create them (GitLab CE has them from 19.x; a self-managed instance lets group Owners
create them only when an administrator allows it), group access tokens of the group
otherwise.
--accountschooses,--reader-nameand--writer-namename them. The reader is a Reporter of the group, the writer a Developer, and the writer must not reach the hub (underwriter_on_hub: guard, not as a Maintainer); - mints their tokens straight into the hub's variables:
TOUCHMARK_READ_TOKENmasked and not protected, since merge request pipelines read it, andTOUCHMARK_WRITE_TOKENprotected, masked and hidden, with the environment scopetouchmark-distribute. A write key that it finds unprotected, unmasked or in another scope is rotated, which revokes the old token. Tokens live--token-daysdays (365). To renew one, delete its variable and run setup again; - locks the hub down: creates the environment
touchmark-distribute, protects the default branch so that no one pushes to it (Maintainers merge, no force push), sets Minimum role to use pipeline variables to No one allowed, keeps protected variables out of merge request pipelines; - schedules the runs: the daily
touchmark distributeand weeklytouchmark doctorpipeline schedules (--schedules=falseskips them).
GitHub¶
The token must administer the hub: a classic token with repo, or a fine-grained one
with Administration, Environments and Variables write and Secrets read.
setup:
- limits the environment
touchmark-distributeto the default branch. It keeps the environment's reviewers and wait timer, and deletes any other branch policy; - creates the reader and writer Apps from a manifest: the reader reads metadata,
contents and pull requests; the writer reads metadata and writes contents, pull
requests and workflows. They are named
<owner>-assets-readand<owner>-assets-writeunless--reader-nameand--writer-namesay otherwise. This step needs a browser: setup prints a link to a page it serves on127.0.0.1(--listen), and you confirm each App on GitHub within--timeout(30 minutes); - stores the Apps' ids in the Actions variables
TOUCHMARK_READ_APP_ID(the repository's) andTOUCHMARK_WRITE_APP_ID(the environment's); - writes each App's private key to a file in a new private directory (
--key-dir) and prints thegh secret setcommand that stores it. GitHub accepts a secret only encrypted as a libsodium sealed box, which touchmark does not implement. Run the commands, delete the directory, and run setup again: it checks the secrets by name; - adds the ruleset
touchmark hubto the default branch: pull requests with an approval and a code owner's review, no force push, no deletion. Where the plan or the token does not allow it, setup says what to set by hand.
Then install both Apps on the target repositories only, never on the hub, and set
writer in hub.yml to the writer App's bot. setup prints both links and the name.
Variants¶
security.write_isolation: external: setup does all of this except storing the write key, which belongs in your secrets store: it leaves the writer's token for you to mint on GitLab, or the writer App's key in its file on GitHub, and prints the OIDC claims to bind it to.- Several providers in
hub.yml: the variables areTOUCHMARK_<ID>_….--provider IDpicks the one to set up, and--urlnames a self-managed instance thathub.ymldoes not list yet.--projectnames the hub when the origin remote does not. - Gitea and Forgejo have no
setup: their Actions give every branch the secrets, so nothing can keep the write key to the default branch. Run the hub's CI on GitHub or GitLab and deliver to them from there, or follow A hub on Gitea or Forgejo.
Reading the result¶
Each step ends with a status: ok (as wanted, nothing written), done (changed in this
run), would (what --dry-run would change), warn (as wanted, with a caveat),
manual (left to you), unknown (could not be read) or fail. --format json prints
the setup report.
| Exit | Meaning |
|---|---|
0 |
everything is in place |
1 |
a step failed |
2 |
nothing written: the token, the hub or the flags are wrong |
3 |
a step is left to you: do it, then run setup again |
Every flag: touchmark setup --help.