docs: condense comments across the repo
Comments had drifted into multi-paragraph narrative (git commit lineage, debugging stories, restated code) in several hot spots (scripts/deploy, hermes-agent.nix, flake.nix, gitea.nix, headscale.nix). Trim every comment to its load-bearing "why" — gotchas, safety warnings, and non-obvious rationale survive verbatim in substance, just tightened to 1-2 sentences; historical narrative and anything already covered in CLAUDE.md is cut. No code/logic changed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UJqEmY1y3AYX3JoX4Y6b21
This commit is contained in:
@@ -1,59 +1,47 @@
|
||||
{ config, pkgs, inputs, ... }:
|
||||
|
||||
# livesync-bridge (vrtmrz) — mirrors an Obsidian LiveSync vault out of CouchDB
|
||||
# on jupiter (services/dev/obsidian-livesync.nix) into a real directory of
|
||||
# markdown here, so luna can read and write the vault as files. Obsidian itself
|
||||
# is an Electron GUI with no headless mode, and an agent wants files anyway.
|
||||
#
|
||||
# ⚠️ THE WRITE-BACK PATH IS THE RISKY ONE. Upstream has three open, unanswered
|
||||
# issues on storage->couchdb — #50 (Jun 2026, writes detected and logged as
|
||||
# uploaded, database never updated), #23 (only lowercase filenames transmitted
|
||||
# from storage), #46 (silent stall on files over ~30KB). All fail QUIETLY: the
|
||||
# log says success and the note never arrives. So do not treat this directory
|
||||
# as durable storage for anything luna cannot regenerate, and check that her
|
||||
# edits actually reach your devices before trusting it. (E2EE itself is fine —
|
||||
# PeerCouchDB.ts hard-errors if a passphrase is missing for an encrypted
|
||||
# remote, so it is a deliberate code path. The one issue claiming E2EE breaks
|
||||
# bridging, #12, is a single unreproduced report with no maintainer reply.)
|
||||
# on jupiter (services/dev/obsidian-livesync.nix) into real markdown files
|
||||
# here, since Obsidian itself is a GUI-only Electron app and luna needs files.
|
||||
#
|
||||
# ⚠️ THE WRITE-BACK PATH IS THE RISKY ONE: upstream has open bugs where a
|
||||
# write is logged as uploaded but the database is never updated (#50), only
|
||||
# lowercase filenames sync from storage (#23), and files over ~30KB silently
|
||||
# stall (#46) — all fail quietly with no error in the log. Don't treat this
|
||||
# directory as durable for anything luna can't regenerate, and verify her
|
||||
# edits actually reach your devices. (E2EE itself is fine — it hard-errors on
|
||||
# a missing passphrase rather than failing silently.)
|
||||
#
|
||||
# EXPECTED NOISE ON FIRST SYNC: a stack trace per historically-deleted file —
|
||||
# NotFound: ... remove '<vault>/Welcome.md' at PeerStorage.delete
|
||||
# CouchDB keeps deletion tombstones, and the bridge replays them against a
|
||||
# directory where the file never existed. PeerStorage.ts:33-40 catches it,
|
||||
# logs, and returns false, so nothing is wrong; it only LOOKS fatal because
|
||||
# main.ts pins the logger to LOG_LEVEL_DEBUG, which prints exception dumps
|
||||
# that are otherwise verbose-level. It stops once the initial catch-up ends.
|
||||
# Talks to CouchDB over the TAILNET (jupiter.orbit.sol:5984), not through
|
||||
# neptun: mars is a tailnet node, so the public vhost, its TLS and its path
|
||||
# allowlist are all irrelevant here.
|
||||
# CouchDB replays deletion tombstones against a directory where the file
|
||||
# never existed. Harmless, caught and logged, and stops once the initial
|
||||
# catch-up ends.
|
||||
#
|
||||
# Talks to CouchDB over the tailnet (jupiter.orbit.sol:5984) directly — mars
|
||||
# is a tailnet node, so neptun's public vhost/TLS/allowlist don't apply here.
|
||||
let
|
||||
stateDir = "/var/lib/livesync-bridge";
|
||||
appDir = "${stateDir}/app";
|
||||
vaultDir = "${stateDir}/vault";
|
||||
|
||||
# The same uid/gid the hermes-agent container runs as (hermes-agent.nix).
|
||||
# Deliberate: the bridge and luna both read and write these files, and
|
||||
# sharing one uid removes any dependence on the container's umask. Two
|
||||
# different uids in a shared group only works while every file stays
|
||||
# group-writable, and a single 0644 file dropped by the agent would stall
|
||||
# sync on that path with nothing but a permission error in the log.
|
||||
# The same uid/gid hermes-agent runs as (hermes-agent.nix), so both peers
|
||||
# share files without depending on umask — two uids in a shared group only
|
||||
# works while every file stays group-writable, and one 0644 file from the
|
||||
# agent would silently stall sync.
|
||||
hermesUid = 986;
|
||||
|
||||
# Which vault. `group` is what pairs the two peers — both must match or the
|
||||
# bridge starts cleanly and simply never syncs anything.
|
||||
# `group` pairs the two peers — mismatched and the bridge starts but never
|
||||
# syncs.
|
||||
#
|
||||
# ⚠️ `database` must be the name entered in the Obsidian plugin for luna's
|
||||
# vault. Get it wrong and nothing errors: the credential below is CouchDB's
|
||||
# admin, so PouchDB CREATES the misnamed database and replicates an empty
|
||||
# vault into it quite happily.
|
||||
# ⚠️ `database` must match the name entered in the Obsidian plugin exactly:
|
||||
# get it wrong and nothing errors, since the admin credential below lets
|
||||
# PouchDB just create the misnamed database and replicate an empty vault.
|
||||
peerGroup = "luna";
|
||||
database = "luna_wiki";
|
||||
in
|
||||
{
|
||||
# hermes-agent.nix declares the GROUP (gid 983) but no user: the container
|
||||
# brings its own uid and needs no host account. The bridge does need one to
|
||||
# run as, so the matching user is declared here.
|
||||
# hermes-agent.nix declares the group (gid 983) but no user — the container
|
||||
# needs no host account, but this service does, so it's declared here.
|
||||
users.users.hermes = {
|
||||
uid = hermesUid;
|
||||
group = "hermes";
|
||||
@@ -62,27 +50,23 @@ in
|
||||
description = "Hermes agent uid, shared with the livesync-bridge service";
|
||||
};
|
||||
|
||||
# Created here rather than by the service so they exist before anything
|
||||
# tries to use them:
|
||||
# - vaultDir before podman-hermes-agent starts, because a bind-mount
|
||||
# source that does not exist is created by podman as root:root and the
|
||||
# bridge then cannot write into its own vault;
|
||||
# - appDir because WorkingDirectory applies to ExecStartPre as well, so a
|
||||
# missing one fails the unit before preStart ever gets to create it.
|
||||
# Created here, not by the service, so they exist before anything needs
|
||||
# them: vaultDir before podman-hermes-agent starts (else podman creates it
|
||||
# as root:root), and appDir before ExecStartPre runs (WorkingDirectory
|
||||
# applies to it too).
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${vaultDir} 0770 hermes hermes -"
|
||||
"d ${appDir} 0750 hermes hermes -"
|
||||
"d ${stateDir}/deno 0750 hermes hermes -"
|
||||
];
|
||||
|
||||
# The bridge's peer config, rendered by sops because it carries three
|
||||
# secrets inline (CouchDB password + both passphrases) and the file format
|
||||
# has no include mechanism.
|
||||
# Rendered by sops (three inline secrets: CouchDB password + both
|
||||
# passphrases; the json format has no include mechanism).
|
||||
#
|
||||
# ⚠️ sops substitutes placeholders into the ALREADY-RENDERED json, so a
|
||||
# secret containing a double quote or a backslash produces an invalid config
|
||||
# and the bridge logs "Could not parse configuration!" and then sits there
|
||||
# with zero peers — it does not exit. Keep all three values alphanumeric.
|
||||
# ⚠️ sops substitutes into the ALREADY-RENDERED json, so a secret with a
|
||||
# quote or backslash yields invalid config — the bridge then just sits with
|
||||
# zero peers logging "Could not parse configuration!" instead of exiting.
|
||||
# Keep all three values alphanumeric.
|
||||
sops.templates."livesync-bridge.json" = {
|
||||
owner = "hermes";
|
||||
content = builtins.toJSON {
|
||||
@@ -96,11 +80,10 @@ in
|
||||
username = "obsidian";
|
||||
password = config.sops.placeholder.couchdb_luna_password;
|
||||
passphrase = config.sops.placeholder.obsidian_luna_passphrase;
|
||||
# The plugin derives path obfuscation from the same passphrase it
|
||||
# uses for content, so this is the same secret. Split into its own
|
||||
# field because the bridge takes them separately — if paths come
|
||||
# back as garbage while contents decode fine, this is the field that
|
||||
# is wrong.
|
||||
# Same secret as the content passphrase — the plugin derives path
|
||||
# obfuscation from it too, but the bridge takes them as separate
|
||||
# fields. If paths come back as garbage while contents decode fine,
|
||||
# this is the field to check.
|
||||
obfuscatePassphrase = config.sops.placeholder.obsidian_luna_passphrase;
|
||||
# Reads the chunking tweaks the plugin stored in the remote, instead
|
||||
# of guessing sizes that then disagree with every other client.
|
||||
@@ -137,22 +120,17 @@ in
|
||||
HOME = stateDir;
|
||||
};
|
||||
|
||||
# Copy the pinned source out of the store and install its locked deps.
|
||||
# It cannot run from /nix/store directly: deno.jsonc sets
|
||||
# `nodeModulesDir: manual` with byonm, so `deno install` must write a
|
||||
# node_modules/ next to the sources.
|
||||
# Copies the pinned source out of the store and installs locked deps,
|
||||
# since deno.jsonc's `nodeModulesDir: manual` (byonm) needs to write
|
||||
# node_modules/ next to the sources — it can't run from /nix/store directly.
|
||||
#
|
||||
# The copy target is a FIXED path on purpose. Deno keys localStorage —
|
||||
# which is where the bridge records per-file sync state (Peer.ts:119) — by
|
||||
# the main module's origin, and stores it under
|
||||
# DENO_DIR/location_data/<sha of that origin>. VERIFIED by running the same
|
||||
# source from two paths against one DENO_DIR: two separate origin dirs
|
||||
# appear. Running straight from /nix/store would therefore change the
|
||||
# origin on every input bump and silently reset the bridge to a full
|
||||
# rescan of both peers.
|
||||
# The copy target is a FIXED path on purpose: Deno keys its localStorage
|
||||
# (where the bridge tracks per-file sync state) by the main module's
|
||||
# origin, so running straight from /nix/store would change that origin —
|
||||
# and reset the bridge to a full rescan of both peers — on every input bump.
|
||||
#
|
||||
# Guarded by a stamp file so this is a no-op on ordinary restarts; only a
|
||||
# flake input bump pays for the re-install (which needs network).
|
||||
# Guarded by a stamp file: a no-op on ordinary restarts, only a flake
|
||||
# input bump pays for the (networked) re-install.
|
||||
preStart = ''
|
||||
set -eu
|
||||
stamp=${stateDir}/.src
|
||||
|
||||
Reference in New Issue
Block a user