Merge pull request #11 from naturallaw777/copilot/eliminate-hub-overrides
Eliminate hub-overrides.nix: write feature toggles directly into custom.nix
This commit is contained in:
@@ -36,7 +36,9 @@ REBUILD_LOG = "/var/log/sovran-hub-rebuild.log"
|
|||||||
REBUILD_STATUS = "/var/log/sovran-hub-rebuild.status"
|
REBUILD_STATUS = "/var/log/sovran-hub-rebuild.status"
|
||||||
REBUILD_UNIT = "sovran-hub-rebuild.service"
|
REBUILD_UNIT = "sovran-hub-rebuild.service"
|
||||||
|
|
||||||
HUB_OVERRIDES_NIX = "/etc/nixos/hub-overrides.nix"
|
CUSTOM_NIX = "/etc/nixos/custom.nix"
|
||||||
|
HUB_BEGIN = " # ── Hub Managed (do not edit) ──────────────"
|
||||||
|
HUB_END = " # ── End Hub Managed ────────────────────────"
|
||||||
DOMAINS_DIR = "/var/lib/domains"
|
DOMAINS_DIR = "/var/lib/domains"
|
||||||
NOSTR_NPUB_FILE = "/var/lib/secrets/nostr_npub"
|
NOSTR_NPUB_FILE = "/var/lib/secrets/nostr_npub"
|
||||||
NJALLA_SCRIPT = "/var/lib/njalla/njalla.sh"
|
NJALLA_SCRIPT = "/var/lib/njalla/njalla.sh"
|
||||||
@@ -434,21 +436,30 @@ def _read_rebuild_log(offset: int = 0) -> tuple[str, int]:
|
|||||||
return "", 0
|
return "", 0
|
||||||
|
|
||||||
|
|
||||||
# ── hub-overrides.nix helpers ─────────────────────────────────────
|
# ── custom.nix Hub Managed section helpers ────────────────────────
|
||||||
|
|
||||||
def _read_hub_overrides() -> tuple[dict, str | None]:
|
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] = {}
|
features: dict[str, bool] = {}
|
||||||
nostr_npub = None
|
nostr_npub = None
|
||||||
try:
|
try:
|
||||||
with open(HUB_OVERRIDES_NIX, "r") as f:
|
with open(CUSTOM_NIX, "r") as f:
|
||||||
content = f.read()
|
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(
|
for m in re.finditer(
|
||||||
r'sovran_systemsOS\.features\.([a-zA-Z0-9_-]+)\s*=\s*(?:lib\.mkForce\s+)?(true|false)\s*;',
|
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"
|
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:
|
if m2:
|
||||||
nostr_npub = m2.group(1)
|
nostr_npub = m2.group(1)
|
||||||
except FileNotFoundError:
|
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:
|
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 = []
|
lines = []
|
||||||
for feat_id, enabled in features.items():
|
for feat_id, enabled in features.items():
|
||||||
val = "true" if enabled else "false"
|
val = "true" if enabled else "false"
|
||||||
lines.append(f" sovran_systemsOS.features.{feat_id} = lib.mkForce {val};")
|
lines.append(f" sovran_systemsOS.features.{feat_id} = lib.mkForce {val};")
|
||||||
if nostr_npub:
|
if nostr_npub:
|
||||||
lines.append(f' sovran_systemsOS.nostr_npub = lib.mkForce "{nostr_npub}";')
|
lines.append(f' sovran_systemsOS.nostr_npub = lib.mkForce "{nostr_npub}";')
|
||||||
body = "\n".join(lines) + "\n" if lines else ""
|
hub_block = (
|
||||||
content = (
|
HUB_BEGIN + "\n"
|
||||||
"# Auto-generated by Sovran Hub — do not edit manually\n"
|
+ "\n".join(lines) + ("\n" if lines else "")
|
||||||
"{ lib, ... }:\n"
|
+ HUB_END + "\n"
|
||||||
"{\n"
|
|
||||||
+ body
|
|
||||||
+ "}\n"
|
|
||||||
)
|
)
|
||||||
nix_dir = os.path.dirname(HUB_OVERRIDES_NIX)
|
|
||||||
if nix_dir:
|
try:
|
||||||
os.makedirs(nix_dir, exist_ok=True)
|
with open(CUSTOM_NIX, "r") as f:
|
||||||
with open(HUB_OVERRIDES_NIX, "w") 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)
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
@@ -631,7 +661,7 @@ async def api_services():
|
|||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
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)
|
overrides, _ = await loop.run_in_executor(None, _read_hub_overrides)
|
||||||
|
|
||||||
async def get_status(entry):
|
async def get_status(entry):
|
||||||
@@ -640,7 +670,7 @@ async def api_services():
|
|||||||
icon = entry.get("icon", "")
|
icon = entry.get("icon", "")
|
||||||
enabled = entry.get("enabled", True)
|
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)
|
feat_id = unit_to_feature.get(unit)
|
||||||
if feat_id is None:
|
if feat_id is None:
|
||||||
feat_id = FEATURE_ICON_MAP.get(icon)
|
feat_id = FEATURE_ICON_MAP.get(icon)
|
||||||
@@ -838,7 +868,7 @@ async def api_features():
|
|||||||
feat_id = feat["id"]
|
feat_id = feat["id"]
|
||||||
|
|
||||||
# Determine enabled state:
|
# 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)
|
# 2. Fall back to config.json services (features enabled in custom.nix)
|
||||||
if feat_id in overrides:
|
if feat_id in overrides:
|
||||||
enabled = overrides[feat_id]
|
enabled = overrides[feat_id]
|
||||||
|
|||||||
@@ -4,9 +4,7 @@
|
|||||||
imports = [
|
imports = [
|
||||||
./modules/modules.nix
|
./modules/modules.nix
|
||||||
./iso/branding.nix
|
./iso/branding.nix
|
||||||
] ++ (if builtins.pathExists /etc/nixos/hub-overrides.nix
|
];
|
||||||
then [ /etc/nixos/hub-overrides.nix ]
|
|
||||||
else []);
|
|
||||||
|
|
||||||
# ── Boot ────────────────────────────────────────────────────
|
# ── Boot ────────────────────────────────────────────────────
|
||||||
boot.loader.systemd-boot.enable = true;
|
boot.loader.systemd-boot.enable = true;
|
||||||
|
|||||||
@@ -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 ];
|
networking.firewall.allowedTCPPorts = [ 8937 ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user