`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
100 lines
5.5 KiB
Markdown
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.
|