Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@

<!-- How you checked that the change works: the commands you ran, the tests you added, what you tried by hand. -->

## Release note

<!-- One to three sentences on what changes for the people who use this repository. Delete this section for a change they will not notice. -->

## Checklist

- [ ] The commit messages follow [Conventional Commits](https://www.conventionalcommits.org/).
- [ ] The title follows [Conventional Commits](https://www.conventionalcommits.org/) and says what changes for the reader.
- [ ] Tests cover the change, and the checks pass.
- [ ] If the change alters behavior, the documentation describes the new behavior.

<!-- For a breaking change, add `!` to the title and end this description with a `BREAKING CHANGE:` footer, one bullet per upgrade step. -->
1 change: 0 additions & 1 deletion CODEOWNERS

This file was deleted.

48 changes: 26 additions & 22 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,49 @@ These rules apply to every public cplieger repository.

## Commits and releases

Commits follow [Conventional Commits](https://www.conventionalcommits.org/). The subject of each releasing commit becomes a line in the release notes, so write it for that reader.
Each pull request is squash-merged with its title as the release-notes line. Write it in [Conventional Commits](https://www.conventionalcommits.org/) form, as the change users see: `fix: subtitles in mov_text tracks decode again`. With one commit, make the title its subject.

With a `cliff.toml`, the commit type decides whether a change releases and its version number:
A change users notice gets a one-to-three-sentence `## Release note` in the description. Leave the `deps` and `devdeps` scopes to Renovate.

| Commit | Release |
| --- | --- |
| `feat:` | minor version, listed under Added |
| `fix:` | patch version, listed under Fixed |
| `sec:` | patch version, listed under Security |
| `refactor:`, `perf:` or a type this table does not name | patch version, listed under Changed |
| `chore(deps):` | patch version, listed under Dependencies |
| `chore:`, `chore(devdeps):`, `ci:`, `docs:`, `style:`, `test:`, `fuzz:`, `lint:`, `debug:`, `release:` | no release |
| `!` after a type that releases, or a `BREAKING CHANGE:` footer on one | major version |
With a `cliff.toml` and default branch `main`, the commit type decides the release:

A hardening fix that adds no public API is `sec:`, not `feat:`.
| Commit | Version | Section |
| --- | --- | --- |
| `feat:` | minor | Added |
| `fix:` | patch | Fixed |
| `sec:` | patch | Security |
| `refactor:`, `perf:` or an unlisted type | patch | Changed |
| `chore(deps):` | patch | Dependencies |
| `chore:`, `chore(devdeps):`, `ci:`, `docs:`, `style:`, `test:`, `fuzz:`, `lint:`, `debug:`, `release:` | no release | |
| a releasing type with `!` or a `BREAKING CHANGE:` footer | major | |

A repository with no release starts at `v1.0.0`. One whose latest version is below 1.0 stays in `0.x`, where a `feat:` commit raises the patch version and a breaking change raises the minor version.
A hardening fix with no new public API is `sec:`. A repository with no release starts at `v1.0.0`. Below 1.0, `feat:` raises the patch version and a breaking change the minor.

The release notes print a `BREAKING CHANGE:` footer as the upgrade steps, so write it as one bullet per step.
A breaking change carries `!` in the title and ends the description with a `BREAKING CHANGE:` footer, one bullet per upgrade step.

A commit that changes only these files never releases, whatever its type:
Changes only to these files never release:

- Markdown files, the root `LICENSE` and the root `.github/` folder
- the root `alerts/` folder and the root `compose.yaml`
- the root `tests/` folder, any `testdata/` folder, and `_test.go`, `.test.ts` and `.spec.ts` files
- any `package-lock.json`, `.punused-ignore` or knip configuration file
- the root `.editorconfig`, `.gitattributes`, `.gitignore` and `.dockerignore`

## Files that belong to cplieger/ci
## Repositories whose default branch is `dev`

A file whose opening comment says it is synced from [cplieger/ci](https://github.com/cplieger/ci) is a copy that the next sync replaces. Change it there. `.prettierrc.json`, `.stylelintrc.json` and `.htmlvalidate.json` are copies too, because a JSON file cannot carry that line.
Your pull request goes to `dev`. `main` holds the released version and takes only promotions of `dev` and automated pull requests, which release a patch when they change shipped files.

## Checks
A [promotion](https://github.com/cplieger/ci/blob/main/docs/workflows.md#the-two-branch-release-model) releases the next minor version, or from 1.0 the next major for a breaking change.

## Synced files

Clone [cplieger/ci](https://github.com/cplieger/ci) next to the repository and run `bash ../ci/ci-local.sh` from the repository root before you push. A `check(s) not validated locally` result leaves those checks to CI.
The next sync replaces a file whose opening comment says it is synced from [cplieger/ci](https://github.com/cplieger/ci), and `.prettierrc.json`, `.stylelintrc.json` and `.htmlvalidate.json`. Change them there.

## Checks

`scripts/install-local-tools.sh` in cplieger/ci installs the tool versions CI uses.
Before you push, run `bash ../ci/ci-local.sh` from the repository root with [cplieger/ci](https://github.com/cplieger/ci) cloned beside it. Its `scripts/install-local-tools.sh` installs CI's tool versions. A `check(s) not validated locally` result leaves them to CI.

In an npm or JSR package, the `version` field in `package.json` and `jsr.json` is a placeholder the release replaces. Leave it.
Leave the placeholder `version` in `package.json` and `jsr.json` to the release.

## Review

Expand All @@ -54,4 +58,4 @@ If an AI assistant wrote a change, have other AI agents review it in depth befor

## Conduct and security

Everyone taking part follows the [Code of Conduct](CODE_OF_CONDUCT.md). Report a vulnerability privately through the [security policy](SECURITY.md), never in a public issue. [SUPPORT.md](SUPPORT.md) says where to ask.
The [Code of Conduct](CODE_OF_CONDUCT.md) applies to everyone. Report a vulnerability privately through the [security policy](SECURITY.md). [SUPPORT.md](SUPPORT.md) says where to ask.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,17 @@ This repository holds the default community-health files and the shared Renovate
GitHub uses a file from this repository for any cplieger repository, public or private, that has no file of that type itself. A repository's own copy in `.github/`, its root or `docs/` takes precedence. A repository with its own `.github/ISSUE_TEMPLATE` folder gets none of the default forms. GitHub shows the defaults on its website only, and they are not part of a repository's clones, packages or downloads. The defaults work only while this repository is public.

- `CODE_OF_CONDUCT.md` is adapted from the [Contributor Covenant 3.0](https://www.contributor-covenant.org/version/3/0/code_of_conduct/), which is licensed under CC BY-SA 4.0.
- `CONTRIBUTING.md` covers the commit types that decide each release, the files that never release, the files synced from cplieger/ci, the local checks and review.
- `CONTRIBUTING.md` covers pull request titles, the commit types that decide each release and the files that never release. It also covers repositories whose default branch is `dev`, the files synced from cplieger/ci, the local checks and review.
- `SECURITY.md` explains how to report a vulnerability privately from a repository's Security tab and how to verify a release. The maintainer acknowledges a report within 7 days and releases a fix before public disclosure, normally within 90 days of the report.
- `SUPPORT.md` says where to ask a question, report a bug or report a vulnerability.
- `.github/ISSUE_TEMPLATE/` holds the bug report, feature request and question forms. Its `config.yml` turns off blank issues and links the security policy.
- `.github/PULL_REQUEST_TEMPLATE.md` asks for a summary, linked issues, the changes, testing and a checklist.
- `.github/PULL_REQUEST_TEMPLATE.md` asks for a summary, linked issues, testing, an optional release note and a checklist.

The forms add the `bug`, `enhancement` and `question` labels. GitHub needs a form's label to exist in the repository that uses the form. All three are among the labels GitHub creates in every new repository.

## What each repository keeps itself

- `LICENSE`, because GitHub cannot use a default license file. Each repository carries its own, so it ships in clones and downloads. The `LICENSE` here covers this repository only.
- `CODEOWNERS`, because it applies only to the repository it sits in.
- `README.md`.

## The Renovate preset
Expand All @@ -36,4 +35,13 @@ Another account can extend `github>cplieger/.github` too. The preset's rules als

The reusable workflows and the lint and format configs the repositories share live in [cplieger/ci](https://github.com/cplieger/ci).

## The two-branch Renovate preset

`two-branch.json` is a second preset, for a repository whose default branch is `dev` and whose `main` branch holds the released version. That repository's `renovate.json` extends `github>cplieger/.github:two-branch` alone, because `default.json` already applies through `inheritConfig`. Renovate then opens pull requests against both branches:

- On `dev`, every update `default.json` allows. Most open as soon as they are released. An npm package waits three days first, the `golang` image one day and `docker/buildx` three days. Vulnerability fixes and cplieger's own packages never wait, and pre-releases of those packages are accepted there. Lock-file maintenance runs daily.
- On `main`, only the updates Renovate merges automatically and that change what users receive, in one pull request each Saturday. Major updates, Go toolchain and base-image minor versions, `devDependencies`, files under example and test folders, workflow pins and lock-file maintenance stay on `dev`.
- Vulnerability fixes open on both branches at once. Besides `security`, each one gets a label naming its update type, such as `security-patch`. A Go toolchain minor version gets `security-major`.
- On both branches, an fclones update also rewrites the crate license files under `licenses/crates/` in the same commit, so the image build accepts it without a manual step.

This project was built with AI-assisted tooling using [Claude](https://claude.com), [GPT](https://openai.com), and [Kiro](https://kiro.dev). The human maintainer defines architecture, supervises implementation, and makes all final decisions.
17 changes: 1 addition & 16 deletions default.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@
]
},
{
"description": "Recompute Dockerfile sha256 integrity pins in the SAME commit as the version bump, so these PRs are born green and automerge with no human step. No datasource publishes the sha256 of the artifact a version names (github-tags exposes a git commit, npm exposes SHA-512, keepalived.org / samba.org / the postfix mirror publish nothing), so every one of these pins used to need a maintainer to run curl | sha256sum and paste the result while the build sat red. Our Renovate is SELF-HOSTED, so postUpgradeTasks is available and does exactly this: the command runs after the manager rewrites the version literal (Renovate writes updated files to disk first, so the script sees the new version) and its Dockerfile edit is folded into the same commit. Each pin declares its own source URL in a `# repin: dep=<depName> url=<...>` marker line directly above the ARG, which is also the human-readable recompute recipe those Dockerfiles used to carry as prose. scripts/repin-sha.sh is synced from cplieger/ci; it is a no-op for any dep with no matching marker, so this rule is harmless in a repo that has not adopted a given pin. allowedCommands is globalOnly and lives in the self-hosted Renovate's global config (anchored to exactly this invocation); with no allowlist entry nothing executes, so the mechanism is fail-closed by default. FAILURE POSTURE: a failing command does not abort the branch, it records an artifactError, which forces a PR, prints an artifact-problem section in the PR body and skips branch-automerge -- and the stale sha still fails the Dockerfile's own sha256sum -c gate, reddening ci / validate. So the worst case is the old behaviour, a red PR waiting for a human, never a silent bad merge. executionMode stays the default `update` so depName/newVersion are the single dep's values rather than a whole grouped branch's. matchDepNames keeps it a no-op for every other dep in the cplieger repos. WHEN A PIN MAY JOIN THIS RULE: recomputing a hash proves nothing about who produced the bytes, so a pin whose ONLY integrity story is this sha256 gains authenticity from the reviewed commit and nothing else. That is still worth automating (the pin freezes the input and re-checks it on every later rebuild), but a pin whose upstream publishes a detached signature should verify it IN THE BUILD against a keyring committed in-repo, and then the recompute cannot degrade anything \u2014 swapped bytes fail gpgv whoever hashed them. docker-smtp-relay's postfix-release.gpg is the model and docker-rsync-scheduler followed it in 2026-08 (which is what let RsyncProject/rsync move from the manual-sha-bump rules below into this one); prefer that shape over withholding the automation.",
"description": "Recompute Dockerfile sha256 integrity pins in the SAME commit as the version bump, so these PRs are born green and automerge with no human step. No datasource publishes the sha256 of the artifact a version names (github-tags exposes a git commit, npm exposes SHA-512, keepalived.org / samba.org / the postfix mirror publish nothing), so every one of these pins used to need a maintainer to run curl | sha256sum and paste the result while the build sat red. Our Renovate is SELF-HOSTED, so postUpgradeTasks is available and does exactly this: the command runs after the manager rewrites the version literal (Renovate writes updated files to disk first, so the script sees the new version) and its Dockerfile edit is folded into the same commit. Each pin declares its own source URL in a `# repin: dep=<depName> url=<...>` marker line directly above the ARG, which is also the human-readable recompute recipe those Dockerfiles used to carry as prose. scripts/repin-sha.sh is synced from cplieger/ci; it is a no-op for any dep with no matching marker, so this rule is harmless in a repo that has not adopted a given pin. allowedCommands is globalOnly and lives in the self-hosted Renovate's global config (anchored to exactly this invocation); with no allowlist entry nothing executes, so the mechanism is fail-closed by default. FAILURE POSTURE: a failing command does not abort the branch, it records an artifactError, which forces a PR, prints an artifact-problem section in the PR body and skips branch-automerge -- and the stale sha still fails the Dockerfile's own sha256sum -c gate, reddening ci / validate. So the worst case is the old behaviour, a red PR waiting for a human, never a silent bad merge. executionMode stays the default `update` so depName/newVersion are the single dep's values rather than a whole grouped branch's. matchDepNames keeps it a no-op for every other dep in the cplieger repos. WHEN A PIN MAY JOIN THIS RULE: recomputing a hash proves nothing about who produced the bytes, so a pin whose ONLY integrity story is this sha256 gains authenticity from the reviewed commit and nothing else. That is still worth automating (the pin freezes the input and re-checks it on every later rebuild), but a pin whose upstream publishes a detached signature should verify it IN THE BUILD against a keyring committed in-repo, and then the recompute cannot degrade anything \u2014 swapped bytes fail gpgv whoever hashed them. docker-smtp-relay's postfix-release.gpg is the model and docker-rsync-scheduler followed it in 2026-08 (which is what let RsyncProject/rsync join this rule); prefer that shape over withholding the automation.",
"matchManagers": [
"custom.regex"
],
Expand Down Expand Up @@ -311,21 +311,6 @@
"This PR needs one manual edit. The repin postUpgradeTask recomputes `FCLONES_SHA256_AMD64`, but the arm64 build pins the commit the tag points at, and nothing here moves it. Get the commit with this command and paste it into `ARG FCLONES_COMMIT` in the Dockerfile:\n\n```sh\ngit ls-remote https://github.com/pkolaczk/fclones.git \"refs/tags/{{{newVersion}}}^{}\"\n```"
]
},
{
"description": "rmlint (docker-rmlint-scheduler) builds rmlint from source across coupled pins the customManager does not move together: it bumps RMLINT_VERSION but leaves ARG RMLINT_COMMIT (the commit the tag dereferences to) and the 'Audited against rmlint <version>;' comment in config.go stale, both of which fail-close the build (a commit-mismatch check and a go-builder grep gate). Label the bump and embed the recompute + re-audit steps so the maintainer fix is mechanical. matchDepNames keeps it a no-op for other repos.",
"matchManagers": [
"custom.regex"
],
"matchDepNames": [
"sahib/rmlint"
],
"addLabels": [
"manual-sha-bump"
],
"prBodyNotes": [
":warning: **Manual pin bump + re-audit required.** rmlint builds from source with coupled pins the bot cannot move.\n\n1. Recompute `ARG RMLINT_COMMIT` (the tag dereferenced to its commit) and paste it into the Dockerfile:\n\n```sh\ngit ls-remote https://github.com/sahib/rmlint.git \"refs/tags/{{{newVersion}}}^{}\"\n```\n\n2. Re-audit `rmlint --help` for new output-redirect / command-exec flags, extend `dangerousFlags` in `config.go` if needed, and bump the `Audited against rmlint <version>;` comment to `{{{newVersion}}}` (a go-builder grep gate fails the build until it matches). See CONTRIBUTING.md."
]
},
{
"description": "Group the Go toolchain version dep (golang) with its per-arch tarball sha256 deps (golang-amd64 / golang-arm64, from the custom datasources) into ONE PR so GO_VERSION and both GO_SHA256_* pins always move together and the build never lands with a stale sha. This replaced the former golang manual-sha-bump rule now that the shas auto-update (go.dev's ?mode=json publishes each tarball's sha256, so the customDatasources resolve the new digest per arch \u2014 same auto-update model as the kiro-cli pin). For repos that pin only the Go version with no sha lines (marotte / web-terminal-server), only `golang` matches, so the PR is just renamed \u2014 no behavior change.",
"matchDepNames": [
Expand Down
Loading
Loading