nix/ec-node: auto-bootstrap web bridge from local control peers
This commit is contained in:
parent
2778715304
commit
c9996dd5ad
4 changed files with 236 additions and 4 deletions
|
|
@ -144,6 +144,51 @@ in
|
|||
default = [ ];
|
||||
description = "Optional iroh endpoint addresses to seed control gossip joins.";
|
||||
};
|
||||
|
||||
bridgeWeb = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Run `ec-node control-bridge-web` to upsert announced streams into the web directory.";
|
||||
};
|
||||
|
||||
directoryUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "https://every.channel";
|
||||
description = "Directory base URL used by control-bridge-web for `/api/stream-upsert`.";
|
||||
};
|
||||
|
||||
authTokenFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional file containing bearer token for `/api/stream-upsert`.";
|
||||
};
|
||||
|
||||
timeoutMs = lib.mkOption {
|
||||
type = lib.types.ints.nonnegative;
|
||||
default = 30000;
|
||||
description = "Bridge run timeout; service restarts and refreshes gossip peers after this window (0 = run forever).";
|
||||
};
|
||||
|
||||
maxAgeMs = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 60000;
|
||||
description = "Maximum accepted staleness for control announcements consumed by the web bridge.";
|
||||
};
|
||||
|
||||
streamPrefix = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional stream-id prefix filter for web upserts.";
|
||||
};
|
||||
|
||||
discovery = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "dht,mdns,dns";
|
||||
description = "Optional discovery mode override for the web bridge; falls back to `control.discovery` when unset.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
broadcasts = lib.mkOption {
|
||||
|
|
@ -195,7 +240,7 @@ in
|
|||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /run/every-channel 0755 root root - -"
|
||||
"d /run/every-channel 1777 root root - -"
|
||||
];
|
||||
|
||||
systemd.services =
|
||||
|
|
@ -227,6 +272,7 @@ in
|
|||
"cmd+=(${lib.concatStringsSep " " (map lib.escapeShellArg cfg.extraArgs)})";
|
||||
explicitInputStr = if b.input == null then "" else b.input;
|
||||
channelStr = if b.channel == null then "" else b.channel;
|
||||
controlEndpointOutPath = "/run/every-channel/control-peer-${sanitizeUnitName b.name}.json";
|
||||
controlDiscoveryStr = if cfg.control.discovery == null then "" else cfg.control.discovery;
|
||||
controlIrohSecretStr = if cfg.control.irohSecret == null then "" else cfg.control.irohSecret;
|
||||
controlGossipPeerLines = lib.concatMapStrings (peer: "cmd+=(--gossip-peer ${lib.escapeShellArg peer})\n") cfg.control.gossipPeers;
|
||||
|
|
@ -356,6 +402,7 @@ in
|
|||
if [[ -n "$control_iroh_secret" ]]; then
|
||||
cmd+=(--iroh-secret "$control_iroh_secret")
|
||||
fi
|
||||
cmd+=(--control-endpoint-addr-out ${lib.escapeShellArg controlEndpointOutPath})
|
||||
${controlGossipPeerLines}
|
||||
''}
|
||||
${extraArgsLine}
|
||||
|
|
@ -407,11 +454,125 @@ in
|
|||
RestrictSUIDSGID = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallArchitectures = "native";
|
||||
ReadWritePaths = lib.optionals cfg.control.enable [ "/run/every-channel" ];
|
||||
};
|
||||
|
||||
environment = cfg.environment;
|
||||
};
|
||||
})
|
||||
cfg.broadcasts);
|
||||
cfg.broadcasts)
|
||||
// lib.optionalAttrs (cfg.control.enable && cfg.control.bridgeWeb.enable)
|
||||
(let
|
||||
bridgeUnit = "every-channel-control-bridge-web";
|
||||
bridgePeerFiles = map (b: "/run/every-channel/control-peer-${sanitizeUnitName b.name}.json") cfg.broadcasts;
|
||||
bridgeDiscoveryStr =
|
||||
if cfg.control.bridgeWeb.discovery != null then cfg.control.bridgeWeb.discovery
|
||||
else if cfg.control.discovery != null then cfg.control.discovery
|
||||
else "";
|
||||
bridgeStreamPrefixStr = if cfg.control.bridgeWeb.streamPrefix == null then "" else cfg.control.bridgeWeb.streamPrefix;
|
||||
bridgeAuthTokenFile = if cfg.control.bridgeWeb.authTokenFile == null then "" else cfg.control.bridgeWeb.authTokenFile;
|
||||
staticGossipPeerLines = lib.concatMapStrings (peer: ''
|
||||
cmd+=(--gossip-peer ${lib.escapeShellArg peer})
|
||||
have_peer=1
|
||||
'') cfg.control.gossipPeers;
|
||||
peerFileLoopLines = lib.concatMapStrings (peerFile: ''
|
||||
if [[ -s ${lib.escapeShellArg peerFile} ]]; then
|
||||
peer="$(tr -d '\r\n' < ${lib.escapeShellArg peerFile})"
|
||||
if [[ -n "$peer" ]]; then
|
||||
cmd+=(--gossip-peer "$peer")
|
||||
have_peer=1
|
||||
fi
|
||||
fi
|
||||
'') bridgePeerFiles;
|
||||
bridgeRunner = pkgs.writeShellApplication {
|
||||
name = bridgeUnit;
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
cfg.package
|
||||
];
|
||||
text = ''
|
||||
set -euo pipefail
|
||||
|
||||
directory_url=${lib.escapeShellArg cfg.control.bridgeWeb.directoryUrl}
|
||||
bridge_discovery=${lib.escapeShellArg bridgeDiscoveryStr}
|
||||
stream_prefix=${lib.escapeShellArg bridgeStreamPrefixStr}
|
||||
auth_token_file=${lib.escapeShellArg bridgeAuthTokenFile}
|
||||
|
||||
while true; do
|
||||
cmd=(
|
||||
${lib.escapeShellArg "${cfg.package}/bin/ec-node"}
|
||||
control-bridge-web
|
||||
--directory-url "$directory_url"
|
||||
--timeout-ms ${toString cfg.control.bridgeWeb.timeoutMs}
|
||||
--max-age-ms ${toString cfg.control.bridgeWeb.maxAgeMs}
|
||||
)
|
||||
|
||||
if [[ -n "$bridge_discovery" ]]; then
|
||||
cmd+=(--discovery "$bridge_discovery")
|
||||
fi
|
||||
if [[ -n "$stream_prefix" ]]; then
|
||||
cmd+=(--stream-prefix "$stream_prefix")
|
||||
fi
|
||||
if [[ -n "$auth_token_file" && -r "$auth_token_file" ]]; then
|
||||
token="$(tr -d '\r\n' < "$auth_token_file")"
|
||||
if [[ -n "$token" ]]; then
|
||||
cmd+=(--auth-token "$token")
|
||||
fi
|
||||
fi
|
||||
|
||||
have_peer=0
|
||||
${peerFileLoopLines}
|
||||
${staticGossipPeerLines}
|
||||
|
||||
if [[ "$have_peer" -eq 0 ]]; then
|
||||
sleep 2
|
||||
continue
|
||||
fi
|
||||
|
||||
"''${cmd[@]}" || true
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
"${bridgeUnit}" = {
|
||||
description = "every.channel control bridge to web directory";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after =
|
||||
[ "network-online.target" ]
|
||||
++ map (b: "every-channel-wt-publish-${sanitizeUnitName b.name}.service") cfg.broadcasts;
|
||||
wants =
|
||||
[ "network-online.target" ]
|
||||
++ map (b: "every-channel-wt-publish-${sanitizeUnitName b.name}.service") cfg.broadcasts;
|
||||
|
||||
unitConfig = {
|
||||
StartLimitIntervalSec = 0;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${bridgeRunner}/bin/${bridgeUnit}";
|
||||
Restart = "always";
|
||||
RestartSec = 2;
|
||||
|
||||
DynamicUser = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
RestrictSUIDSGID = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallArchitectures = "native";
|
||||
};
|
||||
|
||||
environment = cfg.environment;
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue