Setting up the repo
The one-time GitHub setup around a new repo, after the render: making it
public, protecting main, the About box, and publishing its first release.
Public repos
public defaults to yes. Choosing no for private repositories omits .github/SECURITY.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md, pull_request_template.md, issue forms, and the DCO sign-off check.
Public repositories also receive .github/workflows/scorecard.yml to publish OpenSSF Scorecard results for the README badge.
Making a repo public
Before switching a private repository to public:
- Scan git history for committed secrets:
betterleaks git --log-opts=--all .
Revoke any detected credentials and rewrite history before publishing.
2. Confirm package name availability on target registries (PyPI, crates.io, npm).
3. Review open issues and pull requests to ensure internal notes or identifiers are removed.
4. Verify the status statement in README.md.
Update the repository configuration with Copier:
copier update --data public=true --data license=MIT
Protecting main
just repo-settings applies .github/rulesets/main.json using the GitHub CLI:
- Blocks force-pushes and branch deletions.
- Enforces linear history and squash merges.
- Requires pull requests. A required check blocks every PR that skips CI, because a skipped workflow leaves its check pending. If some PRs in your repo skip CI, drop
required_status_checksfrom the ruleset or make that check always report. In private repositories, the checksgit hooks,pull request titleanddependency reviewmust pass. - In public repositories, requires one approving review submitted after the latest push. Required checks reside in
.github/rulesets/main-checks.json, which requires the same three,DCO sign-off, and each stack's own check (ts: lint, types, testsand so on, withpython: tests (windows)underpython_windows) for a stack at the repo root,composein a Compose-only repo, plussite: buildwhen the project has a site. A nested stack's workflow runs only when its folder changes, so its check is not required. - With
dependency_bot=renovate, permits the Renovate app to bypass review for minor and patch updates once required checks pass. - Applies
.github/rulesets/tags.jsonto restrictv*tag creation to repository administrators. - Enables Dependabot security alerts (
gh api -X PUT repos/OWNER/REPO/vulnerability-alerts).
It leaves one setting to you: refusing any action not pinned to a full commit SHA. The template's workflows pin every action, but the setting also stops a workflow added later from using a tag. Turn it on once the repo's own workflows pin too, since an unpinned action then fails its run:
gh api -X PUT repos/OWNER/REPO/actions/permissions -F enabled=true -f allowed_actions=all -F sha_pinning_required=true
This call also resets allowed_actions to all; check the current value first with gh api repos/OWNER/REPO/actions/permissions.
Execute just repo-settings once after creating the repository and again whenever ruleset templates change. Requires administrative permissions.
The GitHub About box
just repo-about syncs repository metadata with gh repo edit:
- Updates repository description from
project_description. - Sets the homepage:
site_urlfor a web app or a site on a Worker, else the GitHub Pages address of the site or the documentation site. - Adds stack topics (
python,typescript,rust,go,docker). Existing manual topics are preserved.
Publishing
To publish a release:
- Set the version
X.Y.Zin the manifest you publish (pyproject.tomlorpackage.json). A citable project sets it in every manifestcitesync.tomllists, or in every manifest at the root when it has nocitesync.toml.CITATION.cffstarts without aversion, which citesync skips. Once you add one, after the first release, keep it in step, or citesync fails withversion-mismatch. - Run
just release X.Y.Z. It checks that version and, in a citable project, runs the citesync hook, then cuts## [Unreleased]inCHANGELOG.mdinto## [X.Y.Z] - date, fixes the compare links and commits. In a web app it also sets the version inpackage.jsonitself and commits both files. - Merge that commit to
main, then runjust release X.Y.Zagain on up-to-datemain. It signs the annotated tagvX.Y.Zonorigin/main, checks it against.github/allowed_signersonorigin/mainand pushes it. A web app's run first refuses apackage.jsononorigin/mainat another version, and itsrelease.ymlrefuses such a tag again before it publishes. See Signed release tags for the key it needs.
With publish_to_pypi, python-release.yml publishes to PyPI and attaches distributions and Sigstore provenance to the GitHub release. Other configurations publish using release.yml.
If a publishing job fails before any artifacts upload to the registry, re-run the failed CI job on the existing tag. PyPI uploads stay strict: an existing filename fails the job, so inspect the registry for a partial upload first. If any release artifact is already published, keep the git tag and publish a new patch release. This includes a failure in docker.yml's attestation step after the image push. GitHub Actions can re-run a job, but not an individual step; re-running this job rebuilds the image and repoints the semver tags.
Publishing controls:
| Option | Disabled | Enabled |
|---|---|---|
publish_to_pypi |
Package marked Private :: Do Not Upload |
python-release.yml publishes to PyPI |
publish_to_npm |
Package set to "private": true |
ts-release.yml publishes to npm |
publish_to_ghcr |
Images built and tested; no push | Images pushed to GitHub Container Registry |
Setting up trusted publishing (OIDC):
- Configure trusted publishers on pypi.org or npmjs.com. For npm, manually publish version 0.1.0 before enabling trusted publishing.
- Enable publishing via Copier:
copier update --data publish_to_pypi=true
- Run
just repo-settingsto create the enabled publishing environments (pypi,npm,ghcr). It sets yourself as required reviewer and av*tag policy (ghcralso onmain).
Signed release tags
Every workflow that publishes from a tag (release.yml, python-release.yml, ts-release.yml, and the tag push in docker.yml) first runs .github/scripts/verify-tag.sh. It passes only an annotated tag with an SSH signature from a key listed in .github/allowed_signers, read from the default branch rather than from the tag, so a tag cannot bring its own key. GitHub's "Verified" mark is not enough: it accepts a key registered to any GitHub account. tag-release.sh runs the same check before it pushes.
The file renders with a comment and no keys, so the first release fails until you add yours. Sign with an SSH key (git config gpg.format ssh and git config user.signingkey ~/.ssh/id_ed25519.pub), then add it and commit to main:
echo "$(git config user.email) namespaces=\"git\" $(cat "$(git config --type=path user.signingkey | sed 's/\.pub$//').pub")" >>.github/allowed_signers
The check holds together with the v* tag ruleset, which lets only admins push tags: a tag runs the workflow and script from its own commit, so a tag pushed by someone else could carry a weaker check. copier update and Renovate also move a project to the newest v* tag of the template whether or not that tag's release.yml accepted it.
A GPG-signed tag fails the check: git tag -v reads an allowed signers file for SSH signatures only. allowed_signers is the project's file after the first render, and copier update leaves it alone. To check a tag yourself: git -c gpg.ssh.allowedSignersFile=.github/allowed_signers tag -v vX.Y.Z.
First release
A public citable project, a project at level standard or community, and one with publish_to_pypi get .github/first-release.md: the one-time steps around the first release, only those its answers need. just repo-settings prints the command that opens it as an issue. Delete the file once the issue is open.
The steps are the release signing key, PyPI trusted publishing, Zenodo, Software Heritage, the Research Software Directory and the OpenSSF Best Practices badge. A public citable README gets a Software Heritage badge. It reads "not found" until the archive has visited the repo, then links to the archived copy and its SWHID. After just release pushes a tag, it prints the Software Heritage save link.