Out of Scope - ehharvey/homelab-ops GitHub Wiki

Out of Scope

This page tracks work that is out-of-scope, but still worth documenting.

  • Running more than one Incus cluster member — no longer "deferred with no home," as of 2026-09-20 this is Roadmap Phase 4 (Decisions.md §26), and since 2026-09-28 the project's main line (Decisions.md §27). It is tracked as its own set of issues: a manual join spike, membership config, the joiner seed and joiner cert, the agent-driven join, an agent on every member, storage/network parity, real node-loss proof, and add/remove + quorum recovery. Cluster-group placement is paused. Phase 3 only takes Incus from a bare daemon to a real one-member cluster (Decisions.md §26 Tier A), which is what the App Manager's leader-election design (Decisions.md § App Manager HA) actually needs. Wrapping Operations Center stays out, but how it joins members is worth reading before building the agent-driven join (Decisions.md §27).
  • GitOps auto-apply and rollback (today: diff-and-warn only)
  • Private repos, repo-sharing across environments
  • IPv6, DHCP/DNS write-back
  • Cert rotation/revocation, moving off self-signed to a real CA
  • Multi-disk / multi-NIC instance overrides
  • Commit-hash-per-node tracking
  • Phone-home / hardware-manifest reporting over Tailscale (a one-shot manifest ping from a node to the app — distinct from the continuous logs/metrics telemetry now in Phase 3 scope, see Roadmap.md/Decisions.md § Metrics — and distinct from the pre-install helper-OS registration/confirmation flow under Decisions.md § Multi-OS support, which is interactive/blocking rather than a one-shot post-install ping)
  • Migrating the web app's own runtime from its Docker Compose/binary deployment into the IncusOS fleet
  • Running the web container as a non-root user (defense-in-depth): switch the final Dockerfile stage to distroless's nonroot user (uid 65532) / the :nonroot image tag. Tackle after Phase 3 — 0.x is single-user/no-auth (below), so root-in-container isn't a headline risk yet. Read-only config mounts (e.g. CLIENT_CERT_PATH) and /tmp (mode 1777) already work as non-root; the one thing to verify first is that a mounted STORE_PATH volume (the sqlite file) is writable by uid 65532, or the store fails to open on startup.
  • Standardized schema output for the fleet-definition format and/or the web app's REST API: JSON Schema generated from internal/config's structs (editor autocomplete, CI validation of fleet YAML) and/or OpenAPI for the existing HTTP API (see Architecture.md § HTTP API). No GraphQL — the app's scope is simple CRUD-ish config management, not query-heavy enough to justify a schema-first GraphQL layer. This is the expected growth path for validation if it ever outgrows the hand-rolled config.Validate chosen in Decisions.md § Validation approach — generate the schema from the (typed net/netip) structs, rather than adopt a schema-DSL library.
  • Multi-user on web app. 0.x should focus on just 1 user for now. No auth needed.
  • Supporting more than IncusOS as a managed-node target. Two directions are planned but not yet built (see Decisions.md § Multi-OS support): plain Debian w/ Incus, installed via a systemd-repart-based helper OS with a pre-install hardware-registration/confirmation flow; and Talos (Kubernetes nodes), single-node install first, full cluster lifecycle (control-plane/worker roles, talosctl bootstrap) deferred further still. Both are blocked on the OS-target abstraction and node→app networking work described there.
  • DB-specific (or other renderer-specific) write-promotion wiring for kind: App beyond the generic Renderer.Promote seam (see #92, docs/AppManager.md, and Decisions.md § Stateful app support for the shape this would take)
  • Backup/restore for kind: App data/volumes. Note this is a prerequisite for any real managed database, not a nice-to-have: replication is not backup — a DROP TABLE replicates perfectly (Decisions.md § Stateful app support).
  • Multi-instance renderers — the cardinality half of this is in scope (replicas: N / per-node on kind: App, and a reconcile loop keyed by (App, member); see docs/AppManager.md), but the renderers that would need N > 1 are not: a database (class 3) or a quorum member (class 5) needs volumes, version-skew classification and DB-native HA before replicas: 3 means anything. See docs/AppClasses.md and Decisions.md § Stateful app support.
  • Cross-node App placement/scheduling/bin-packing. kind: App has no placement field in 0.x — Incus's own scheduler decides, which is moot on today's single-member cluster. Placement is not replicas: that says only how many, never where (App.Node was deleted precisely because it meant both). When real placement is eventually needed, the intended mechanism is Incus's own cluster groups (tag members with a capability, target creation at the group) via a separate field, invalid in combination with replicas: per-node, not an operator naming individual nodes — see Decisions.md § App Manager HA and docs/AppClasses.md § Placement is a third axis.
  • Persistent volumes / storage schema for kind: App. There's no volume field at all today, which is what confines every App to being stateless (or destructively updated — docs/AppClasses.md class 6). Needed before a database, and it brings its own lifecycle questions (does a volume outlive its generation? per-member or per-generation? orphan policy?).
  • Version-skew classification for kind: App upgrades. A bump is detected as "declared image differs from the live instance's tag" — deliberately coarse, and sufficient for every renderer 0.x ships. It is not sufficient for classes 3/5: Postgres physical replication requires matching major versions (16 → 17 can't blue-green at all — that's pg_upgrade/logical replication), and Kubernetes has an N-2 skew policy plus control-plane-first ordering. See docs/AppClasses.md § Version skew.
  • Run-to-completion / batch Apps (a migration or backup job). Renderer.Healthy answers "healthy right now"; a job wants "exited 0", and blue-green is meaningless for it. This is the natural home for a database backup sidecar if one is ever needed.
  • A stable endpoint/service address for kind: App. Apps get whatever address Incus gives them; there's no field for a static IP or a service name, and DNS write-back is out of scope (above). This is unresolved rather than decided — a proxy fronting a database needs a stable address to be useful, which would make it docs/AppClasses.md class 6 (scarce resource, needing a floating-IP handoff) rather than the class 1 it otherwise looks like.
  • Cross-App reference / service discovery — how one App learns another App's members' addresses. There's no mechanism at all today: instances get whatever address Incus assigns, there's no service DNS, and no field lets an App name another App. This is the binding constraint on the proxy-in-front-of-a-database topology, ahead of anything else on this list — the proxy can only "health-check the DB layer directly" (Decisions.md § Stateful app support) if it can enumerate the DB's instances first. Prior art points at a reference field rather than a grouping construct: CloudNativePG's Pooler names its Cluster (cluster: {name: …}); Patroni + HAProxy does the same via templated backend lists. Related but distinct from the endpoint entry above: that one is "how do clients reach an App", this one is "how does an App reach another App".
  • Composition / multi-role deployments (grouping several Apps into one versioned unit — a kind: Deployment, Helm-release, or operator-CR shape). replicas is homogeneous fan-out: N copies of one image driven by one renderer, differing only by ordinal. A proxy + writer + readers is several images, several roles, and up to three different classes, and nothing composes them — different roles are different Apps, related only by convergence. Deliberately not built, and the reason is the useful part: a grouping construct would be the union of three constraints that are each already deferred — dependency ordering (below), version-skew classification (above), and shared/orphaned teardown (below). Adding the container before any of those exist would group nothing. Apps are independent by construction, so this stays purely additive whenever a real constraint justifies it. Revisit trigger: the first workload with a genuine cross-App constraint — concretely Kubernetes, where the control plane must upgrade before workers under an N-2 skew policy. Note a database + proxy is not that trigger: the proxy is version-independent of the database and discovers it by health check, which is why the prior art keeps them as separate objects (see the cross-App reference entry above). Vocabulary warning, since this repo leans on k8s terms: replicas already is a k8s Deployment; what this entry defers is closer to a Helm release.
  • App-level dependency ordering (no "App B waits on App A" graph). Note this is one of the three constraints the composition entry above is waiting on — and that startup ordering specifically doesn't need it: Patroni retries its DCS forever and a proxy retries its backends, so eventual convergence covers it without a graph.
  • Automatic rollback after a successful App promotion (health-gating is strictly pre-promotion)
  • Orphaned-App teardown (App removed from git; live instances not auto-deleted)
  • Operator-facing UI/API for App/rollout status (beyond incus list)
  • Self-hosted ACME CA (e.g. step-ca) so nodes fetch a real, chain-verifiable server certificate for their Incus API instead of the self-signed one Incus generates at first boot. Raised alongside #91: internal/nodeprovision/cmd/validate-tunnel-harness's direct-to-Incus TLS clients currently set InsecureSkipVerify: true (dismissed as an accepted CodeQL finding, not fixed), since there's no CA to chain-verify against and nothing in the seed tells the web app what a node's server cert will be in advance — trust instead comes from the WireGuard tunnel's own peer authentication plus the client cert. A self-hosted ACME CA the web app trusts, with IncusOS configured (seed-time or post-boot) to fetch its server cert via ACME from that CA, would let these calls do real server-certificate verification instead of skipping it — closing that gap properly rather than just accepting it. Overlaps with the existing "moving off self-signed to a real CA" item above, but concrete enough (specific tool, specific trigger) to track on its own. Needs: confirming IncusOS actually exposes an ACME-configuration hook (seed file or incus-osd API, same kind of gap the Tailscale seed-hook investigation in Decisions.md §8 hit), and deciding where step-ca itself would run (web app process vs. a separate managed service).
  • Automated leader election — 0.x ships operator-designated leadership (docs/Decisions.md §25): failover is a person committing a new primary. The new primary waits for the old one to stand down, fenced by git commits and an acting handoff. The automated ranked election over Incus heartbeats is specified in §25 (claim/verify protocol from single-writer registers, self-fencing, a takeover dead-time) precisely enough to build behind the existing leaderelection.Elector interface; what to verify first is cross-agent read visibility and the clustered path. Also deferred: Temporal as the orchestrator (which would remove the leader altogether), and Kubernetes-hosted workloads.
⚠️ **GitHub.com Fallback** ⚠️