Files
werkator/docs/prs/2026-10-01-PR#87-self-deploy-only-from-its-repository.md
miandmhoennig dbb6d9e4be
werkator build succeeded after 02:06
werkator/deploy build succeeded after 00:07
fix(config): only the repository the instance deploys itself from may restart it (#87)
`tools/remote repo-add` installed Werkator's own `builds.deploy` (restartsInstance, unsandboxed) into werkcrappit on mih09. Its `main@deploy` ran on the host with werkcrappit's build command and would have restarted the instance on every green main.

- Registry entries get `selfDeploys`; with a registry, `restartsInstance` is refused in every repository whose entry lacks it (repository not served, logged). At most one entry may say it.
- `init --apply` repeats; fragments are merged in order.
- `tools/remote`: `WERKATOR_SELF_DEPLOY_CONFIG` for the watched repository alone; `repo-add` refuses a shared fragment with `restartsInstance`; a failed fragment upload now aborts.
- v1.26.0

**Before merging:** add `selfDeploys: true` to the `werkator` entry of `~/.werkator.yml` on mih09, or the self-deployed v1.26.0 will not serve `werkator`.

PR-doc: `docs/prs/2026-10-01-PR#87-self-deploy-only-from-its-repository.md`

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #87
2026-10-01 14:17:39 +02:00

100 lines
5.5 KiB
Markdown

> **WARNING:** This document describes only the change applied in this PR.
> It may already be outdated once the next PR is merged.
> Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.
## The Problem
`tools/remote repo-add` runs `werkator init --apply` with the instance fragment named by `WERKATOR_INIT_CONFIG`.
Since PR#61, the fragment of mih09 also carries Werkator's own `builds.deploy`: `afterSuccessOf: default`, `branches: [main]`, `restartsInstance: true`, `werkdock.enabled: false`.
On 2026-10-01, `repo-add` installed this block into the newly registered repository `werkcrappit`.
Its `main@deploy` then ran on the host, outside the sandbox, with werkcrappit's inherited `buildCommand` (`./gradlew build`).
It failed only because the host has no JDK 21.
With a JDK, every green `main` build of werkcrappit would have restarted the instance.
Werkator accepted the block, because nothing in a repository's own files can say whether the instance runs from that repository.
The fragment mixed two things: the sandbox policy that every repository gets, and the deployment that belongs to one repository.
## Non-Goals
- No detection of the repository the instance runs from; the operator says it in the instance file.
- No change to an instance without a registry: it serves one repository, and that one may restart it.
- No change to the pinning of `restartsInstance` against branch layers; it already holds.
## The Scenarios
### Feature: only the repository the instance deploys itself from may restart it
#### Scenario#87.01: Several fragments are merged in order
So that the self-deployment can travel in its own fragment, apart from the sandbox policy.
- **Given** two fragments, the second one overriding a key of the first and adding a build
- **When** `init --apply` gets both
- **Then** the applied layer holds their merge, the later fragment winning
- **And** a header names both files
##### Verified by
- [ConfigLoaderTest](../../src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderTest.kt) — "several fragments are merged in order"
- [InitCommandTest](../../src/test/kotlin/de/hoennig/werkator/commands/InitCommandTest.kt) — the existing `--apply` tests with the option as a list
#### Scenario#87.02: restartsInstance needs selfDeploys in the registry
So that a deployment block copied into a foreign repository cannot restart the instance.
- **Given** a registry with `werkator` marked `selfDeploys: true` and `werkcrappit` unmarked, both with a `restartsInstance` build
- **When** their configurations are loaded
- **Then** `werkator` loads with the restarting build
- **And** `werkcrappit` is refused, naming the build, the instance file and `selfDeploys: true`
- **And** a directory outside the registry is refused too, and so is a branch layer on a refused configuration
##### Verified by
- [ConfigLoaderTest](../../src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderTest.kt) — "only the registry entry marked selfDeploys may restart the instance"
#### Scenario#87.03: A refused repository is not served
So that the error shows at start, and the other repositories keep building.
- **Given** an unmarked registry entry whose repository sets `restartsInstance`
- **When** the registry opens
- **Then** that repository is skipped with an error, and the others are served
##### Verified by
- [RepoRegistryTest](../../src/test/kotlin/de/hoennig/werkator/repo/RepoRegistryTest.kt) — "a repository that would restart the instance without being marked selfDeploys is not served"
#### Scenario#87.04: One repository at most deploys the instance
So that "the repository the instance runs from" names one repository.
- **Given** two registry entries with `selfDeploys: true`
- **When** the registry opens
- **Then** the start aborts, naming both entries
##### Verified by
- [RepoRegistryTest](../../src/test/kotlin/de/hoennig/werkator/repo/RepoRegistryTest.kt) — "two entries marked selfDeploys abort the start"
## The Solution
`RepositoryEntry` gets `selfDeploys`.
`ConfigLoader.loadPrimary` refuses any `restartsInstance: true` in `builds` or the legacy `branches` when a registry exists and the repository's entry does not say `selfDeploys: true`.
The paths are compared after resolving symlinks, so `~/werkator` and its real path match.
The check sits in `loadPrimary`, so every load — the registry at start, the watcher per poll, the branch layer at build time — stands on it.
`RepositoryEntry.directory` now resolves an entry's path for the registry and the check alike.
`init --apply` repeats, and `ConfigLoader.applyInstanceFragments` merges the fragments in order.
`tools/remote` gets `WERKATOR_SELF_DEPLOY_CONFIG`: `repo-init` and `instance-start` apply it on top of `WERKATOR_INIT_CONFIG`, `repo-add` never does.
`repo-add` dies when `WERKATOR_INIT_CONFIG` sets `restartsInstance: true`, before it installs anything.
A failed upload inside a command substitution now ends the script; `set -e` does not reach into one.
Rollout on mih09: add `selfDeploys: true` to the `werkator` entry of `~/.werkator.yml` before the new version starts.
An older Werkator ignores the key, so the order is safe; the other way round, the new version would not serve `werkator` and could not deploy itself again.
Then split `.env.mih09.yml` locally into the sandbox policy and `.env.mih09.self-deploy.yml`.
## Additional Changes
- `docs/configuration.md`, `docs/deployment.md`, `docs/llms.md`, `AGENTS.md` and the architecture skill describe `selfDeploys`, the repeated `--apply` and the second fragment.
- Version 1.26.0 with its release note.