# Manifest Lifecycle Hooks — Design
**Status:** design (2026-06-21) · Task #20 · Prereq for migrating complex stacks
(indeedhub, netbird) off legacy Rust installers.
See `docs/PRODUCTION-MASTER-PLAN.md`, `docs/APP-PACKAGING-MIGRATION-PLAN.md`
("controlled hooks").
---
## 1. Problem
Some apps need a step the static manifest can't express: a **post-start container
mutation**. The motivating case is indeedhub's `patch_indeedhub_nostr_provider()`:
1. `podman exec indeedhub sed -i '/X-Frame-Options/d' /etc/nginx/conf.d/default.conf`
(strip the header so the app loads in our iframe)
2. `podman cp /opt/archipelago/web-ui/nostr-provider.js indeedhub:/usr/share/nginx/html/`
3. patch nginx conf to inject `#' /etc/nginx/conf.d/default.conf"]
- exec: ["nginx", "-s", "reload"]
pre_start: [] # (future) run before each start — repair/ownership
```
Types (in `archipelago-container`):
```rust
pub enum HookStep {
Exec { exec: Vec },
CopyFromHost { copy_from_host: HostCopy },
}
pub struct HostCopy { pub src: String, pub dest: String }
pub struct LifecycleHooks {
#[serde(default)] pub post_install: Vec,
#[serde(default)] pub pre_start: Vec,
}
```
`hooks` is `#[serde(default)]` + forward-compatible (absent = no hooks).
## 4. Execution
`container::hooks::run_post_install(manifest, container_name, data_dir)`:
- Resolve container name via `compute_container_name`.
- For each step in order:
- `Exec` → `podman exec ` (timeout-bounded).
- `CopyFromHost` → canonicalise `src` against the allowlist roots; reject on
escape; `podman cp :`.
- Log each step; on error, `warn!` and continue (best-effort).
Called from the orchestrator's install path **after** the container is up
(post-create/health), and gated so it runs on install (not every reconcile).
Validation (`AppManifest::validate`): every `copy_from_host.src` must resolve
inside an allowlist root and contain no `..`; `exec` must be non-empty.
## 5. indeedhub migration (the payoff)
With hooks, indeedhub becomes fully manifest-driven: 7 member manifests
(postgres/redis/minio/relay/api/ffmpeg/frontend) + the frontend manifest carries
the `post_install` hook above. `install_indeedhub_stack` becomes orchestrator-first
(like btcpay), legacy as fallback. Same pattern unblocks netbird's setup steps.
## 6. Phases
1. **Schema + executor + validation + unit tests** (this design) — `exec` +
`copy_from_host`, allowlist-enforced.
2. **Wire into orchestrator install** (post-create, install-only).
3. **indeedhub**: author member manifests + frontend `post_install` hook; wire
`install_indeedhub_stack` orchestrator-first; live-migrate + verify on .228.
4. **netbird**: assess its setup steps; migrate with hooks.
5. `pre_start` hooks (repair/ownership) if needed.