From 268abddb282cee12e6cb55c68d23393f973365ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 19:16:36 +0000 Subject: [PATCH] Add deprecated bip110 no-op shim and Hub migration - modules/core/roles.nix: re-declare bip110 as a nullOr bool no-op option so existing custom.nix files with `lib.mkForce true` continue to evaluate; add config.warnings block that fires only when the stale flag is explicitly set - server.py: add DEPRECATED_FEATURE_IDS constant; skip deprecated ids in _read_hub_overrides and _write_hub_overrides; add _migrate_strip_deprecated_features helper that rewrites the Hub Managed section without deprecated lines on startup; add @app.on_event("startup") handler _startup_migrate_deprecated_features --- app/sovran_systemsos_web/server.py | 52 +++++++++++++++++++++++++++++- modules/core/roles.nix | 24 ++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index f5a8d5e..683ca7f 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -265,6 +265,10 @@ FEATURE_REGISTRY = [ }, ] +# Feature ids that have been removed/deprecated. The Hub must never write these +# back into custom.nix, and should strip any it finds (see startup migration). +DEPRECATED_FEATURE_IDS: set[str] = {"bip110"} + # Map feature IDs to their systemd units in config.json FEATURE_SERVICE_MAP = { "rdp": "gnome-remote-desktop.service", @@ -1505,7 +1509,9 @@ def _read_hub_overrides() -> tuple[dict, str | None, str | None, str | None]: r'sovran_systemsOS\.features\.([a-zA-Z0-9_-]+)\s*=\s*(?:lib\.mkForce\s+)?(true|false)\s*;', section, ): - features[m.group(1)] = m.group(2) == "true" + feat_id = m.group(1) + if feat_id not in DEPRECATED_FEATURE_IDS: + features[feat_id] = m.group(2) == "true" for m in re.finditer( r'sovran_systemsOS\.web\.btcpayserver\s*=\s*(?:lib\.mkForce\s+)?(true|false)\s*;', section, @@ -1538,6 +1544,8 @@ def _write_hub_overrides(features: dict, nostr_npub: str | None, timezone: str | """Write the Hub Managed section inside custom.nix.""" lines = [] for feat_id, enabled in features.items(): + if feat_id in DEPRECATED_FEATURE_IDS: + continue val = "true" if enabled else "false" if feat_id == "btcpay-web": lines.append(f" sovran_systemsOS.web.btcpayserver = lib.mkForce {val};") @@ -1583,6 +1591,40 @@ def _write_hub_overrides(features: dict, nostr_npub: str | None, timezone: str | f.write(content) +def _migrate_strip_deprecated_features() -> None: + """One-time migration: remove deprecated feature lines from the Hub Managed + section of custom.nix. Any feature id in DEPRECATED_FEATURE_IDS is dropped + while all other Hub-managed settings (other features, nostr_npub, timezone, + locale) are preserved byte-for-byte in meaning. + + This is a no-op (and never raises) if CUSTOM_NIX is missing, unreadable, or + contains no deprecated lines. + """ + try: + with open(CUSTOM_NIX, "r") as f: + content = f.read() + except (FileNotFoundError, OSError): + return + + # Quick-exit: if none of the deprecated ids appear, nothing to do. + hub_begin = content.find(HUB_BEGIN) + hub_end = content.find(HUB_END) + if hub_begin == -1 or hub_end == -1: + return + section = content[hub_begin:hub_end] + if not any(f"features.{dep_id}" in section for dep_id in DEPRECATED_FEATURE_IDS): + return + + try: + features, nostr_npub, timezone, locale = _read_hub_overrides() + # _read_hub_overrides already excludes DEPRECATED_FEATURE_IDS, so + # calling _write_hub_overrides with its output drops the stale lines. + _write_hub_overrides(features, nostr_npub, timezone, locale) + except Exception: + # Never let a migration failure break startup. + pass + + # ── Feature status helpers ───────────────────────────────────────── def _is_feature_enabled_in_config(feature_id: str) -> bool | None: @@ -4570,6 +4612,14 @@ async def _startup_recover_stale_status(): await loop.run_in_executor(None, _recover_stale_status, REBUILD_STATUS, REBUILD_LOG, REBUILD_UNIT) +@app.on_event("startup") +async def _startup_migrate_deprecated_features(): + """Strip deprecated feature lines (e.g. bip110) from the Hub Managed section + of custom.nix so they are never re-written and do not cause stale warnings.""" + loop = asyncio.get_event_loop() + await loop.run_in_executor(None, _migrate_strip_deprecated_features) + + async def _background_domain_reachability_checker(): """Periodically curl configured domains and cache reachability results.""" await asyncio.sleep(_DOMAIN_REACHABILITY_STARTUP_DELAY) diff --git a/modules/core/roles.nix b/modules/core/roles.nix index 48f6972..0180622 100755 --- a/modules/core/roles.nix +++ b/modules/core/roles.nix @@ -48,6 +48,19 @@ bitcoin-core = lib.mkEnableOption "Bitcoin Core"; rdp = lib.mkEnableOption "Gnome Remote Desktop"; sshd = lib.mkEnableOption "SSH remote access"; + + # Deprecated: BIP-110 is now built into mainline Bitcoin Knots and is the + # default node. This option is retained ONLY so that existing machines with + # `sovran_systemsOS.features.bip110 = lib.mkForce true;` left in their local + # custom.nix continue to evaluate. It has no effect and will be removed in a + # future release once the Hub has cleaned up old custom.nix files. + bip110 = lib.mkOption { + type = lib.types.nullOr lib.types.bool; + default = null; + internal = true; + visible = false; + description = "(Deprecated, no-op) BIP-110 is now built into Bitcoin Knots."; + }; }; # ── Web exposure (controls Caddy vhosts) ────────────────── @@ -88,4 +101,15 @@ description = "Nostr public key (npub1...) for Haven relay"; }; }; + + config = lib.mkIf (config.sovran_systemsOS.features.bip110 != null) { + warnings = [ + '' + sovran_systemsOS.features.bip110 is deprecated and has no effect: + BIP-110 is now built into mainline Bitcoin Knots, which is the default node. + You can safely remove the `sovran_systemsOS.features.bip110` line from + /etc/nixos/custom.nix. The Sovran Hub will also remove it automatically. + '' + ]; + }; }