Speculative: move dashboard into k3s with hostPath vault + git-pull sidecar #3

Closed
opened 2026-05-23 20:55:19 +00:00 by coilysiren · 0 comments
Owner

Originally filed by @coilysiren on 2026-05-14T12:11:02Z - https://github.com/coilysiren/personal-dashboard/issues/63

Speculative, not a right-now problem. Filing so the shape is on record if a future reason to rebuild appears.

Premise

Today the dashboard is a native systemd daemon on kai-server reading the vault from a local path. Isolation comes from Unix perms (0700, dedicated service user) plus systemd sandboxing (ProtectHome, ReadOnlyPaths). This works.

The speculative alternative: deploy into k3s on the same host, mount the vault via hostPath, run a sidecar that refreshes the vault on a timer.

Why it is tempting

  • One deployment substrate (k3s) instead of two (k3s + native systemd) on kai-server.
  • "hostPath pods don't see each other's mounts unless they explicitly mount" feels like a clean isolation story.
  • Sidecar pattern is a natural home for the vault refresh loop.

Why it does not earn its weight today

  1. hostPath is not stronger isolation than Unix perms. Any pod manifest naming the path can mount it. k3s does not enforce PodSecurityAdmission or hostPath restrictions by default, and pods default to root. "No other manifest mentions the path" is the same guarantee as "no other systemd unit reads /var/lib/personal-dashboard/vault/."

  2. Obsidian Sync (cloud) has no headless API. E2E-encrypted, only official clients speak it. A sidecar cannot pull from Obsidian Cloud directly. Realistic refresh sources:

    • Headless Obsidian under xvfb. Fragile, breaks on updates.
    • Obsidian Git plugin on Mac/phone pushing to a git remote, sidecar git pull on a timer. Standard self-hoster answer.
    • Self-hosted livesync via CouchDB. New moving part.

    The vault is already a git repo, so the git-pull shape is correct regardless of whether the consumer is k3s or native systemd. The "pull from Obsidian Cloud" framing does not buy anything the native path does not already have.

  3. Repo-recall loopback. Repo-recall runs natively on kai-server. A k3s dashboard would need k3s-to-native loopback that the native deployment does not need. Solvable, but added complexity for no isolation gain.

If we ever do this anyway

k3s-to-native loopback options for repo-recall, increasing cleanliness:

  1. hostNetwork: true on the dashboard pod. 127.0.0.1:<port> works. Loses cluster DNS for that pod.
  2. Hardcode the node's LAN or Tailscale IP in dashboard config. Single-node k3s, IP is stable.
  3. Headless Service + manual Endpoints pointing at node IP and repo-recall port. Pod talks to repo-recall.default.svc.cluster.local. Most k8s-native, most YAML.

Vault refresh sidecar: git pull against the vault's git remote on a timer (5-15 min). Obsidian Git plugin on Mac and phone pushes commits. hostPath mounted into both the sidecar (rw) and the dashboard (ro).

Bonus joke shape from the original conversation: a Linuxbrew-based Kubernetes operator that provisions the hostPath + sidecar + refresh timer as a single CR. Not actually a container, just Linuxbrew on the host wired into a CRD. For funsies, not for shipping.

Decision

Stay on native systemd. If isolation needs to harden, tighten the existing unit (User=, Group=, ProtectHome=true, ReadOnlyPaths=, ReadWritePaths= scoped to dashboard state only). Vault refresh, if ever needed, lands as a systemd.timer next to the dashboard unit doing git pull against the vault remote. Same outcome as the sidecar with one fewer substrate.

Revisit this issue if:

  • A second consumer of the vault on kai-server appears that wants a different refresh cadence.
  • We move enough other native daemons into k3s that the systemd-on-kai-server surface is the odd one out.
  • Obsidian ships a real headless sync API (unlikely).
_Originally filed by @coilysiren on 2026-05-14T12:11:02Z - [https://github.com/coilysiren/personal-dashboard/issues/63](https://github.com/coilysiren/personal-dashboard/issues/63)_ Speculative, not a right-now problem. Filing so the shape is on record if a future reason to rebuild appears. ## Premise Today the dashboard is a native systemd daemon on kai-server reading the vault from a local path. Isolation comes from Unix perms (`0700`, dedicated service user) plus systemd sandboxing (`ProtectHome`, `ReadOnlyPaths`). This works. The speculative alternative: deploy into k3s on the same host, mount the vault via `hostPath`, run a sidecar that refreshes the vault on a timer. ## Why it is tempting - One deployment substrate (k3s) instead of two (k3s + native systemd) on kai-server. - "hostPath pods don't see each other's mounts unless they explicitly mount" feels like a clean isolation story. - Sidecar pattern is a natural home for the vault refresh loop. ## Why it does not earn its weight today 1. **hostPath is not stronger isolation than Unix perms.** Any pod manifest naming the path can mount it. k3s does not enforce PodSecurityAdmission or hostPath restrictions by default, and pods default to root. "No other manifest mentions the path" is the same guarantee as "no other systemd unit reads `/var/lib/personal-dashboard/vault/`." 2. **Obsidian Sync (cloud) has no headless API.** E2E-encrypted, only official clients speak it. A sidecar cannot pull from Obsidian Cloud directly. Realistic refresh sources: - Headless Obsidian under xvfb. Fragile, breaks on updates. - **Obsidian Git plugin on Mac/phone pushing to a git remote, sidecar `git pull` on a timer.** Standard self-hoster answer. - Self-hosted livesync via CouchDB. New moving part. The vault is already a git repo, so the git-pull shape is correct regardless of whether the consumer is k3s or native systemd. The "pull from Obsidian Cloud" framing does not buy anything the native path does not already have. 3. **Repo-recall loopback.** Repo-recall runs natively on kai-server. A k3s dashboard would need k3s-to-native loopback that the native deployment does not need. Solvable, but added complexity for no isolation gain. ## If we ever do this anyway k3s-to-native loopback options for repo-recall, increasing cleanliness: 1. `hostNetwork: true` on the dashboard pod. `127.0.0.1:<port>` works. Loses cluster DNS for that pod. 2. Hardcode the node's LAN or Tailscale IP in dashboard config. Single-node k3s, IP is stable. 3. Headless Service + manual Endpoints pointing at node IP and repo-recall port. Pod talks to `repo-recall.default.svc.cluster.local`. Most k8s-native, most YAML. Vault refresh sidecar: `git pull` against the vault's git remote on a timer (5-15 min). Obsidian Git plugin on Mac and phone pushes commits. hostPath mounted into both the sidecar (rw) and the dashboard (ro). Bonus joke shape from the original conversation: a Linuxbrew-based Kubernetes operator that provisions the hostPath + sidecar + refresh timer as a single CR. Not actually a container, just Linuxbrew on the host wired into a CRD. For funsies, not for shipping. ## Decision Stay on native systemd. If isolation needs to harden, tighten the existing unit (`User=`, `Group=`, `ProtectHome=true`, `ReadOnlyPaths=`, `ReadWritePaths=` scoped to dashboard state only). Vault refresh, if ever needed, lands as a `systemd.timer` next to the dashboard unit doing `git pull` against the vault remote. Same outcome as the sidecar with one fewer substrate. Revisit this issue if: - A second consumer of the vault on kai-server appears that wants a different refresh cadence. - We move enough other native daemons into k3s that the systemd-on-kai-server surface is the odd one out. - Obsidian ships a real headless sync API (unlikely).
coilysiren 2026-05-30 05:44:18 +00:00
  • closed this issue
  • added the
    icebox
    label
Commenting is not possible because the repository is archived.
No labels
icebox
P0
P1
P2
P3
P4
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilysiren/personal-dashboard#3
No description provided.