From 9e948ce6cf262f7519a986f00879957fd12f4e6c Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 4 Jun 2026 12:25:28 +0200 Subject: [PATCH 1/2] Update nix flake, update config options --- flake.lock | 20 +++++++-------- flake.nix | 72 ++++++++++++++---------------------------------------- 2 files changed, 29 insertions(+), 63 deletions(-) diff --git a/flake.lock b/flake.lock index 7c708a8..0d30539 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1778869304, - "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", + "lastModified": 1780243769, + "narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", + "rev": "331800de5053fcebacf6813adb5db9c9dca22a0c", "type": "github" }, "original": { @@ -64,11 +64,11 @@ ] }, "locked": { - "lastModified": 1779247103, - "narHash": "sha256-DwltBoBl9a7fCzlKi3xnNha1NHbfvawwkNdnTXEyfFQ=", + "lastModified": 1780543271, + "narHash": "sha256-oPJ7eJN1sM37v92Rp/eyQL7/rUm0BOvXEBAoq/zN0cM=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "86dbfb70dc1c2967245d87ed6d07d2c8bda305e3", + "rev": "c30ca201c5093540cf792f6982f81ba1aa0f3514", "type": "github" }, "original": { @@ -80,11 +80,11 @@ "ttp": { "flake": false, "locked": { - "lastModified": 1779304128, - "narHash": "sha256-C/z7JV4RxcuhdYE7vS+x4WJA1rXM/w03kjNfPPRi334=", + "lastModified": 1780494955, + "narHash": "sha256-i2VRRF6yNips3c4JHgfvmvMxb0HTkTCn69lmsKZLHRw=", "ref": "refs/heads/main", - "rev": "3e3f939f64088cb01c13348b58c504593953ec0e", - "revCount": 122, + "rev": "23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2", + "revCount": 125, "type": "git", "url": "https://git.methanium.net/tensamin/ttp.git" }, diff --git a/flake.nix b/flake.nix index 8cf53d7..24f3f07 100644 --- a/flake.nix +++ b/flake.nix @@ -26,7 +26,6 @@ systems = [ "x86_64-linux" "aarch64-linux" - "x86_64-darwin" "aarch64-darwin" ]; @@ -96,7 +95,6 @@ else pkgs.writeText "iota-config.json" (builtins.toJSON cfg.settings); descriptionText = "Tensamin Iota"; - #+ lib.optionalString cfg.useTmux " (attach TUI: tmux -S ${cfg.dataDir}/tmux.sock attach -t iota)"; in { options.services.iota = { enable = lib.mkEnableOption "Enable the Iota service."; @@ -132,16 +130,24 @@ description = "Whether to open the firewall for ports used by Iota."; }; - ttpBind = lib.mkOption { - type = lib.types.str; - default = "0.0.0.0"; - description = "IP address to bind the TTP/QUIC server to."; - }; - bindAddress = lib.mkOption { type = lib.types.str; default = "0.0.0.0"; - description = "IP address to bind the HTTP server to."; + description = "IP address to bind the Iota HTTP and TTP/QUIC servers to."; + }; + + web = { + bindAddress = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "IP address to bind the ttyd terminal server to."; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 7681; + description = "Port to bind the ttyd terminal server to."; + }; }; package = lib.mkOption { @@ -150,12 +156,6 @@ description = "The Iota package to use."; }; - useTmux = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to run Iota inside a tmux session for shared TUI access."; - }; - settings = lib.mkOption { type = lib.types.attrs; default = {}; @@ -181,19 +181,7 @@ users.groups.iota = {}; - systemd.services.iota = let - iotaTmuxCmd = pkgs.writeShellScript "iota-tmux-cmd" '' - mkdir -p ${cfg.dataDir} - echo "[$(date)] Running Iota..." - ${cfg.package}/bin/iota - status=$? - echo "" - echo "[$(date)] Iota exited with status: $status" - echo "Press any key to exit..." - read -r -n 1 - exit $status - ''; - in { + systemd.services.iota = { description = descriptionText; wantedBy = ["multi-user.target"]; after = ["network.target"]; @@ -205,29 +193,7 @@ Group = "iota"; WorkingDirectory = cfg.dataDir; - ExecStart = - if cfg.useTmux - then - pkgs.writeShellScript "iota-start" '' - set -e - export TMUX_TMPDIR=${cfg.dataDir} - ${pkgs.coreutils}/bin/mkdir -p ${cfg.dataDir} - ${pkgs.coreutils}/bin/chown iota:iota ${cfg.dataDir} - - echo "[iota-start] Creating tmux session..." - if ! ${pkgs.tmux}/bin/tmux -S ${cfg.dataDir}/tmux.sock new-session -d -s iota "${iotaTmuxCmd}"; then - echo "[iota-start] ERROR: tmux new-session failed" - exit 1 - fi - echo "[iota-start] tmux session created, waiting..." - echo "[iota-start] Run 'tmux -S ${cfg.dataDir}/tmux.sock attach -t iota' to attach to the tmux session." - - while ${pkgs.tmux}/bin/tmux -S ${cfg.dataDir}/tmux.sock has-session -t iota 2>/dev/null; do - sleep 2 - done - echo "[iota-start] tmux session ended" - '' - else "${cfg.package}/bin/iota"; + ExecStart = "${pkgs.ttyd}/bin/ttyd -W -i ${cfg.web.bindAddress} -p ${toString cfg.web.port} ${cfg.package}/bin/iota"; ExecStartPre = [ ("+" @@ -262,7 +228,7 @@ LockPersonality = true; MemoryDenyWriteExecute = true; Environment = [ - "TTP_BIND=${cfg.ttpBind}" + "TTP_BIND=${cfg.bindAddress}" "BIND_ADDRESS=${cfg.bindAddress}" ]; } @@ -272,7 +238,7 @@ }; networking.firewall = lib.mkIf cfg.openFirewall { - allowedTCPPorts = [1984]; + allowedTCPPorts = [1984 cfg.web.port]; allowedUDPPorts = [1984]; }; }; From d517144562f29589e47ef1f262ed1c62de2e823b Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 4 Jun 2026 22:39:14 +0200 Subject: [PATCH 2/2] Persist ttyd iota using tmux --- flake.nix | 153 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 133 insertions(+), 20 deletions(-) diff --git a/flake.nix b/flake.nix index 24f3f07..be637c5 100644 --- a/flake.nix +++ b/flake.nix @@ -39,28 +39,43 @@ inherit system; overlays = [(import rust-overlay)]; }; + rustToolchain = rustPkgs.rust-bin.stable.latest.default.override { extensions = ["rust-src" "rust-analyzer" "clippy" "rustfmt"]; }; in { packages = { default = self'.packages.iota; + iota = pkgs.rustPlatform.buildRustPackage { pname = "iota"; version = "0.1.0"; src = ./.; + cargoLock = { lockFile = ./Cargo.lock; allowBuiltinFetchGit = true; }; - nativeBuildInputs = with pkgs; [cmake perl pkg-config]; - buildInputs = with pkgs; [openssl sqlite]; + + nativeBuildInputs = with pkgs; [ + cmake + perl + pkg-config + ]; + + buildInputs = with pkgs; [ + openssl + sqlite + ]; + dontUseCmakeConfigure = true; + preConfigure = '' if [ -d ../cargo-vendor-dir/ttp-core-0.1.0 ]; then cp ${ttp}/ttp-codec.json ../cargo-vendor-dir/ttp-codec.json fi ''; + postInstall = '' mv $out/bin/iota-core $out/bin/iota for f in $out/bin/*; do @@ -69,13 +84,24 @@ fi done ''; + passthru.dataDir = "/var/lib/iota"; }; }; devShells.default = pkgs.mkShell { - nativeBuildInputs = with pkgs; [rustToolchain git cmake perl pkg-config]; - buildInputs = with pkgs; [openssl sqlite]; + nativeBuildInputs = with pkgs; [ + rustToolchain + git + cmake + perl + pkg-config + ]; + + buildInputs = with pkgs; [ + openssl + sqlite + ]; }; }; @@ -87,7 +113,10 @@ ... }: let cfg = config.services.iota; - defaultPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.default or (throw "iota: no pre-built package for system ${pkgs.stdenv.hostPlatform.system}"); + + defaultPackage = + self.packages.${pkgs.stdenv.hostPlatform.system}.default + or (throw "iota: no pre-built package for system ${pkgs.stdenv.hostPlatform.system}"); configFile = if cfg.settingsFile != null @@ -95,6 +124,61 @@ else pkgs.writeText "iota-config.json" (builtins.toJSON cfg.settings); descriptionText = "Tensamin Iota"; + + tmuxSessionName = "iota"; + tmuxSocket = "${cfg.dataDir}/tmux.sock"; + + setupScript = pkgs.writeShellScript "iota-setup" '' + set -euo pipefail + + mkdir -p ${lib.escapeShellArg cfg.dataDir}/certs + + ${lib.optionalString (cfg.certFile != null) '' + ln -sf ${lib.escapeShellArg cfg.certFile} ${lib.escapeShellArg cfg.dataDir}/certs/cert.pem + ''} + + ${lib.optionalString (cfg.keyFile != null) '' + ln -sf ${lib.escapeShellArg cfg.keyFile} ${lib.escapeShellArg cfg.dataDir}/certs/cert.key + ''} + + install -m 644 ${lib.escapeShellArg configFile} ${lib.escapeShellArg cfg.dataDir}/config.json + + chown -R iota:iota ${lib.escapeShellArg cfg.dataDir} + ''; + + iotaSessionScript = pkgs.writeShellScript "iota-tmux-session" '' + set -euo pipefail + + socket=${lib.escapeShellArg tmuxSocket} + session=${lib.escapeShellArg tmuxSessionName} + + if ! ${pkgs.tmux}/bin/tmux -S "$socket" has-session -t "$session" 2>/dev/null; then + ${pkgs.tmux}/bin/tmux -S "$socket" new-session \ + -d \ + -s "$session" \ + -c ${lib.escapeShellArg cfg.dataDir} \ + ${lib.escapeShellArg "${cfg.package}/bin/iota"} + fi + + while ${pkgs.tmux}/bin/tmux -S "$socket" has-session -t "$session" 2>/dev/null; do + sleep 5 + done + + exit 1 + ''; + + ttydScript = pkgs.writeShellScript "iota-ttyd" '' + set -euo pipefail + + exec ${pkgs.ttyd}/bin/ttyd \ + -W \ + -i ${lib.escapeShellArg cfg.web.bindAddress} \ + -p ${lib.escapeShellArg (toString cfg.web.port)} \ + ${pkgs.tmux}/bin/tmux \ + -S ${lib.escapeShellArg tmuxSocket} \ + attach-session \ + -t ${lib.escapeShellArg tmuxSessionName} + ''; in { options.services.iota = { enable = lib.mkEnableOption "Enable the Iota service."; @@ -181,8 +265,8 @@ users.groups.iota = {}; - systemd.services.iota = { - description = descriptionText; + systemd.services.iota-session = { + description = "${descriptionText} tmux session"; wantedBy = ["multi-user.target"]; after = ["network.target"]; @@ -193,22 +277,16 @@ Group = "iota"; WorkingDirectory = cfg.dataDir; - ExecStart = "${pkgs.ttyd}/bin/ttyd -W -i ${cfg.web.bindAddress} -p ${toString cfg.web.port} ${cfg.package}/bin/iota"; - ExecStartPre = [ - ("+" - + pkgs.writeShellScript "iota-setup" '' - mkdir -p ${cfg.dataDir}/certs - - ${lib.optionalString (cfg.certFile != null) "ln -sf ${cfg.certFile} ${cfg.dataDir}/certs/cert.pem"} - ${lib.optionalString (cfg.keyFile != null) "ln -sf ${cfg.keyFile} ${cfg.dataDir}/certs/cert.key"} - - install -m 644 ${configFile} ${cfg.dataDir}/config.json - - chown -R iota:iota ${cfg.dataDir} - '') + ("+" + setupScript) ]; + ExecStart = iotaSessionScript; + + ExecStop = '' + ${pkgs.tmux}/bin/tmux -S ${lib.escapeShellArg tmuxSocket} kill-session -t ${lib.escapeShellArg tmuxSessionName} + ''; + Restart = "always"; RestartSec = "5s"; @@ -237,6 +315,41 @@ }; }; + systemd.services.iota = { + description = descriptionText; + wantedBy = ["multi-user.target"]; + requires = ["iota-session.service"]; + after = ["iota-session.service" "network.target"]; + + serviceConfig = { + Type = "simple"; + User = "iota"; + Group = "iota"; + WorkingDirectory = cfg.dataDir; + + ExecStart = ttydScript; + + Restart = "always"; + RestartSec = "5s"; + + AmbientCapabilities = ["CAP_NET_BIND_SERVICE"]; + CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"]; + + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + NoNewPrivileges = true; + ReadWritePaths = [cfg.dataDir]; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + }; + }; + networking.firewall = lib.mkIf cfg.openFirewall { allowedTCPPorts = [1984 cfg.web.port]; allowedUDPPorts = [1984];