feat: move sshd into its own Nix feature module, gate Tech Support behind it

Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/d45dc36f-0b3b-48bb-950f-700afe45dd06

Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-05 15:09:02 +00:00
committed by GitHub
parent 109c92a33a
commit df2768c6fc
7 changed files with 151 additions and 21 deletions

View File

@@ -192,6 +192,20 @@ FEATURE_REGISTRY = [
"conflicts_with": ["bip110"],
"port_requirements": [],
},
{
"id": "sshd",
"name": "SSH Remote Access",
"description": "Enable SSH for remote terminal access. Required for Tech Support. Disabled by default for security — enable only when needed.",
"category": "support",
"needs_domain": False,
"domain_name": None,
"needs_ddns": False,
"extra_fields": [],
"conflicts_with": [],
"port_requirements": [
{"port": "22", "protocol": "TCP", "description": "SSH"},
],
},
{
"id": "btcpay-web",
"name": "BTCPay Server Web Access",
@@ -218,6 +232,7 @@ FEATURE_SERVICE_MAP = {
"bip110": None,
"bitcoin-core": None,
"btcpay-web": "btcpayserver.service",
"sshd": "sshd.service",
}
# Port requirements for service tiles (keyed by unit name or icon)
@@ -249,6 +264,8 @@ SERVICE_PORT_REQUIREMENTS: dict[str, list[dict]] = {
"phpfpm-nextcloud.service": _PORTS_WEB,
"phpfpm-wordpress.service": _PORTS_WEB,
"haven-relay.service": _PORTS_WEB,
# SSH (only open when feature is enabled)
"sshd.service": [{"port": "22", "protocol": "TCP", "description": "SSH"}],
}
# Maps service unit names to their domain file name in DOMAINS_DIR.
@@ -285,8 +302,8 @@ ROLE_CATEGORIES: dict[str, set[str] | None] = {
# Features shown per role (None = show all)
ROLE_FEATURES: dict[str, set[str] | None] = {
"server_plus_desktop": None,
"desktop": {"rdp"},
"node": {"rdp", "bip110", "bitcoin-core", "mempool", "btcpay-web"},
"desktop": {"rdp", "sshd"},
"node": {"rdp", "bip110", "bitcoin-core", "mempool", "btcpay-web", "sshd"},
}
SERVICE_DESCRIPTIONS: dict[str, str] = {
@@ -370,6 +387,12 @@ SERVICE_DESCRIPTIONS: dict[str, str] = {
"Manage your system visually without being physically present. "
"Sovran_SystemsOS sets up secure remote access with generated credentials — connect and go."
),
"sshd.service": (
"Secure Shell (SSH) remote access. When enabled, authorized users can connect "
"to your machine over the network via encrypted terminal sessions. "
"Sovran_SystemsOS keeps SSH disabled by default for maximum security — "
"enable it only when you need remote access or Tech Support."
),
"root-password-setup.service": (
"Your system account credentials. These are the keys to your Sovran_SystemsOS machine — "
"root access, user accounts, and SSH passphrases. Keep them safe."
@@ -1026,6 +1049,15 @@ def _is_feature_enabled_in_config(feature_id: str) -> bool | None:
return None
def _is_sshd_feature_enabled() -> bool:
"""Check if the sshd feature is enabled via hub overrides or config."""
overrides, _ = _read_hub_overrides()
if "sshd" in overrides:
return bool(overrides["sshd"])
config_state = _is_feature_enabled_in_config("sshd")
return bool(config_state) if config_state is not None else False
# ── Tech Support helpers ──────────────────────────────────────────
def _is_support_active() -> bool:
@@ -2091,11 +2123,13 @@ async def api_support_status():
"""Check if tech support SSH access is currently enabled."""
loop = asyncio.get_event_loop()
active = await loop.run_in_executor(None, _is_support_active)
sshd_enabled = await loop.run_in_executor(None, _is_sshd_feature_enabled)
session = await loop.run_in_executor(None, _get_support_session_info)
unlock_info = await loop.run_in_executor(None, _get_wallet_unlock_info)
wallet_unlocked = bool(unlock_info)
return {
"active": active,
"sshd_enabled": sshd_enabled,
"enabled_at": session.get("enabled_at"),
"enabled_at_human": session.get("enabled_at_human"),
"wallet_protected": session.get("wallet_protected", False),
@@ -2109,8 +2143,18 @@ async def api_support_status():
@app.post("/api/support/enable")
async def api_support_enable():
"""Add the Sovran support SSH key to allow remote tech support."""
"""Add the Sovran support SSH key to allow remote tech support.
Requires the sshd feature to be enabled first."""
loop = asyncio.get_event_loop()
# Gate: SSH feature must be enabled before support can be activated
sshd_on = await loop.run_in_executor(None, _is_sshd_feature_enabled)
if not sshd_on:
raise HTTPException(
status_code=400,
detail="SSH must be enabled first. Please enable SSH Remote Access in the Feature Manager, then try again.",
)
ok = await loop.run_in_executor(None, _enable_support)
if not ok:
raise HTTPException(status_code=500, detail="Failed to enable support access")