diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index 2499733..c337981 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -36,8 +36,10 @@ REBUILD_LOG = "/var/log/sovran-hub-rebuild.log" REBUILD_STATUS = "/var/log/sovran-hub-rebuild.status" REBUILD_UNIT = "sovran-hub-rebuild.service" -HUB_OVERRIDES_NIX = "/etc/nixos/hub-overrides.nix" -DOMAINS_DIR = "/var/lib/domains" +CUSTOM_NIX = "/etc/nixos/custom.nix" +HUB_BEGIN = " # ── Hub Managed (do not edit) ──────────────" +HUB_END = " # ── End Hub Managed ────────────────────────" +DOMAINS_DIR = "/var/lib/domains" NOSTR_NPUB_FILE = "/var/lib/secrets/nostr_npub" NJALLA_SCRIPT = "/var/lib/njalla/njalla.sh" @@ -434,21 +436,30 @@ def _read_rebuild_log(offset: int = 0) -> tuple[str, int]: return "", 0 -# ── hub-overrides.nix helpers ───────────────────────────────────── +# ── custom.nix Hub Managed section helpers ──────────────────────── def _read_hub_overrides() -> tuple[dict, str | None]: - """Parse hub-overrides.nix. Returns (features_dict, nostr_npub_or_none).""" + """Parse the Hub Managed section inside custom.nix. + Returns (features_dict, nostr_npub_or_none).""" features: dict[str, bool] = {} nostr_npub = None try: - with open(HUB_OVERRIDES_NIX, "r") as f: + with open(CUSTOM_NIX, "r") as f: content = f.read() + begin = content.find(HUB_BEGIN) + end = content.find(HUB_END) + if begin == -1 or end == -1: + return features, nostr_npub + section = content[begin:end] for m in re.finditer( r'sovran_systemsOS\.features\.([a-zA-Z0-9_-]+)\s*=\s*(?:lib\.mkForce\s+)?(true|false)\s*;', - content, + section, ): features[m.group(1)] = m.group(2) == "true" - m2 = re.search(r'sovran_systemsOS\.nostr_npub\s*=\s*(?:lib\.mkForce\s+)?"([^"]*)"', content) + m2 = re.search( + r'sovran_systemsOS\.nostr_npub\s*=\s*(?:lib\.mkForce\s+)?"([^"]*)"', + section, + ) if m2: nostr_npub = m2.group(1) except FileNotFoundError: @@ -457,25 +468,44 @@ def _read_hub_overrides() -> tuple[dict, str | None]: def _write_hub_overrides(features: dict, nostr_npub: str | None) -> None: - """Write a complete hub-overrides.nix from the given state.""" + """Write the Hub Managed section inside custom.nix.""" lines = [] for feat_id, enabled in features.items(): val = "true" if enabled else "false" lines.append(f" sovran_systemsOS.features.{feat_id} = lib.mkForce {val};") if nostr_npub: lines.append(f' sovran_systemsOS.nostr_npub = lib.mkForce "{nostr_npub}";') - body = "\n".join(lines) + "\n" if lines else "" - content = ( - "# Auto-generated by Sovran Hub — do not edit manually\n" - "{ lib, ... }:\n" - "{\n" - + body - + "}\n" + hub_block = ( + HUB_BEGIN + "\n" + + "\n".join(lines) + ("\n" if lines else "") + + HUB_END + "\n" ) - nix_dir = os.path.dirname(HUB_OVERRIDES_NIX) - if nix_dir: - os.makedirs(nix_dir, exist_ok=True) - with open(HUB_OVERRIDES_NIX, "w") as f: + + try: + with open(CUSTOM_NIX, "r") as f: + content = f.read() + except FileNotFoundError: + return + + begin = content.find(HUB_BEGIN) + end = content.find(HUB_END) + + if begin != -1 and end != -1: + # Replace existing hub section (include the HUB_END line itself) + newline_after_end = content.find("\n", end) + if newline_after_end == -1: + end_of_marker = len(content) + else: + end_of_marker = newline_after_end + 1 + content = content[:begin] + hub_block + content[end_of_marker:] + else: + # Insert hub section just before the final closing } + last_brace = content.rfind("}") + if last_brace == -1: + return + content = content[:last_brace] + "\n" + hub_block + content[last_brace:] + + with open(CUSTOM_NIX, "w") as f: f.write(content) @@ -631,7 +661,7 @@ async def api_services(): loop = asyncio.get_event_loop() - # Read runtime feature overrides from hub-overrides.nix + # Read runtime feature overrides from custom.nix Hub Managed section overrides, _ = await loop.run_in_executor(None, _read_hub_overrides) async def get_status(entry): @@ -640,7 +670,7 @@ async def api_services(): icon = entry.get("icon", "") enabled = entry.get("enabled", True) - # Overlay runtime feature state from hub-overrides.nix + # Overlay runtime feature state from custom.nix Hub Managed section feat_id = unit_to_feature.get(unit) if feat_id is None: feat_id = FEATURE_ICON_MAP.get(icon) @@ -838,7 +868,7 @@ async def api_features(): feat_id = feat["id"] # Determine enabled state: - # 1. Check hub-overrides.nix first (explicit hub toggle) + # 1. Check custom.nix Hub Managed section first (explicit hub toggle) # 2. Fall back to config.json services (features enabled in custom.nix) if feat_id in overrides: enabled = overrides[feat_id] diff --git a/configuration.nix b/configuration.nix index aa51f70..d738c70 100644 --- a/configuration.nix +++ b/configuration.nix @@ -4,9 +4,7 @@ imports = [ ./modules/modules.nix ./iso/branding.nix - ] ++ (if builtins.pathExists /etc/nixos/hub-overrides.nix - then [ /etc/nixos/hub-overrides.nix ] - else []); + ]; # ── Boot ──────────────────────────────────────────────────── boot.loader.systemd-boot.enable = true; diff --git a/modules/core/sovran-hub.nix b/modules/core/sovran-hub.nix index 0d3b56d..862a9b1 100644 --- a/modules/core/sovran-hub.nix +++ b/modules/core/sovran-hub.nix @@ -283,24 +283,6 @@ in }; }; - systemd.services.hub-overrides-init = { - description = "Initialize hub-overrides.nix if missing"; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - unitConfig.ConditionPathExists = "!/etc/nixos/hub-overrides.nix"; - script = '' - cat > /etc/nixos/hub-overrides.nix <<'EOF' -# Auto-generated by Sovran Hub — do not edit manually -{ lib, ... }: -{ -} -EOF - ''; - }; - networking.firewall.allowedTCPPorts = [ 8937 ]; }; }