[Feat] Split Daemon & TUI
This commit is contained in:
parent
f82500ea7d
commit
36a70e82a0
35 changed files with 970 additions and 239 deletions
56
Cargo.lock
generated
56
Cargo.lock
generated
|
|
@ -2085,6 +2085,14 @@ dependencies = [
|
||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iota"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"iota-cli",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iota-auth"
|
name = "iota-auth"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -2151,6 +2159,7 @@ dependencies = [
|
||||||
"hkdf 0.12.4",
|
"hkdf 0.12.4",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
|
"iota-ipc",
|
||||||
"iota-logger",
|
"iota-logger",
|
||||||
"iota-state",
|
"iota-state",
|
||||||
"iota-storage",
|
"iota-storage",
|
||||||
|
|
@ -2178,7 +2187,6 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"tungstenite",
|
"tungstenite",
|
||||||
"uuid",
|
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"warp",
|
"warp",
|
||||||
"x448",
|
"x448",
|
||||||
|
|
@ -2195,27 +2203,41 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iota-core"
|
name = "iota-daemon"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dashmap",
|
"iota-daemon-lib",
|
||||||
"iota-cli",
|
"iota-ipc",
|
||||||
"iota-logger",
|
"iota-logger",
|
||||||
"iota-state",
|
"iota-state",
|
||||||
"iota-storage",
|
"iota-storage",
|
||||||
"iota-terms",
|
|
||||||
"iota-updater",
|
|
||||||
"iota-util",
|
|
||||||
"json",
|
|
||||||
"mtp",
|
|
||||||
"omikron-connector",
|
"omikron-connector",
|
||||||
"once_cell",
|
|
||||||
"pnet",
|
|
||||||
"ratatui",
|
|
||||||
"reqwest",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"web-server",
|
"web-server",
|
||||||
"web-ui",
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iota-daemon-lib"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"iota-ipc",
|
||||||
|
"iota-logger",
|
||||||
|
"iota-state",
|
||||||
|
"iota-storage",
|
||||||
|
"iota-util",
|
||||||
|
"mtp",
|
||||||
|
"omikron-connector",
|
||||||
|
"sysinfo",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iota-ipc"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2228,6 +2250,7 @@ dependencies = [
|
||||||
"mtp",
|
"mtp",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2238,6 +2261,7 @@ dependencies = [
|
||||||
"json",
|
"json",
|
||||||
"mtp",
|
"mtp",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"serde",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
@ -4805,9 +4829,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.53.0"
|
version = "1.53.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee"
|
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@ members = [
|
||||||
"iota-terms",
|
"iota-terms",
|
||||||
"iota-state",
|
"iota-state",
|
||||||
"iota-cli",
|
"iota-cli",
|
||||||
"iota-core",
|
"iota",
|
||||||
|
"iota-daemon",
|
||||||
|
"iota-daemon-lib",
|
||||||
|
"iota-ipc",
|
||||||
"omikron-connector",
|
"omikron-connector",
|
||||||
"web-server",
|
"web-server",
|
||||||
"web-ui",
|
"web-ui",
|
||||||
|
|
|
||||||
43
flake.lock
generated
43
flake.lock
generated
|
|
@ -5,11 +5,11 @@
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1778716662,
|
"lastModified": 1782949081,
|
||||||
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -20,11 +20,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780243769,
|
"lastModified": 1784497964,
|
||||||
"narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=",
|
"narHash": "sha256-vlHUuqAcbcH2RKmHbPiuQzbv1pnzzavXnI62RD0bqCU=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "331800de5053fcebacf6813adb5db9c9dca22a0c",
|
"rev": "241313f4e8e508cb9b13278c2b0fa25b9ca27163",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -36,11 +36,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777168982,
|
"lastModified": 1782614948,
|
||||||
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
|
"narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixpkgs.lib",
|
"repo": "nixpkgs.lib",
|
||||||
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
|
"rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -53,8 +53,7 @@
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"rust-overlay": "rust-overlay",
|
"rust-overlay": "rust-overlay"
|
||||||
"ttp": "ttp"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-overlay": {
|
"rust-overlay": {
|
||||||
|
|
@ -64,11 +63,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780543271,
|
"lastModified": 1784526465,
|
||||||
"narHash": "sha256-oPJ7eJN1sM37v92Rp/eyQL7/rUm0BOvXEBAoq/zN0cM=",
|
"narHash": "sha256-L37teKC6oINWG4PGZLIqbphMWvSQ0PEz+aWxAk+rIDw=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "c30ca201c5093540cf792f6982f81ba1aa0f3514",
|
"rev": "58c6334db52d51fc5dd8877c90b01f00cf8a696b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -76,22 +75,6 @@
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"ttp": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1780494955,
|
|
||||||
"narHash": "sha256-i2VRRF6yNips3c4JHgfvmvMxb0HTkTCn69lmsKZLHRw=",
|
|
||||||
"ref": "refs/heads/main",
|
|
||||||
"rev": "23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2",
|
|
||||||
"revCount": 125,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.methanium.net/tensamin/ttp.git"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.methanium.net/tensamin/ttp.git"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|
|
||||||
73
flake.nix
73
flake.nix
|
|
@ -8,10 +8,6 @@
|
||||||
url = "github:oxalica/rust-overlay";
|
url = "github:oxalica/rust-overlay";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
ttp = {
|
|
||||||
url = "git+https://git.methanium.net/tensamin/ttp.git";
|
|
||||||
flake = false;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs @ {
|
outputs = inputs @ {
|
||||||
|
|
@ -19,7 +15,6 @@
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
flake-parts,
|
flake-parts,
|
||||||
rust-overlay,
|
rust-overlay,
|
||||||
ttp,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
flake-parts.lib.mkFlake {inherit inputs;} {
|
flake-parts.lib.mkFlake {inherit inputs;} {
|
||||||
|
|
@ -47,9 +42,10 @@
|
||||||
packages = {
|
packages = {
|
||||||
default = self'.packages.iota;
|
default = self'.packages.iota;
|
||||||
iota = pkgs.rustPlatform.buildRustPackage {
|
iota = pkgs.rustPlatform.buildRustPackage {
|
||||||
pname = "iota";
|
pname = "iota-daemon";
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
cargoBuildFlags = ["-p" "iota-daemon"];
|
||||||
cargoLock = {
|
cargoLock = {
|
||||||
lockFile = ./Cargo.lock;
|
lockFile = ./Cargo.lock;
|
||||||
allowBuiltinFetchGit = true;
|
allowBuiltinFetchGit = true;
|
||||||
|
|
@ -57,15 +53,9 @@
|
||||||
nativeBuildInputs = with pkgs; [cmake perl pkg-config];
|
nativeBuildInputs = with pkgs; [cmake perl pkg-config];
|
||||||
buildInputs = with pkgs; [openssl sqlite];
|
buildInputs = with pkgs; [openssl sqlite];
|
||||||
dontUseCmakeConfigure = true;
|
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 = ''
|
postInstall = ''
|
||||||
mv $out/bin/iota-core $out/bin/iota
|
|
||||||
for f in $out/bin/*; do
|
for f in $out/bin/*; do
|
||||||
if [ "$(basename "$f")" != "iota" ]; then
|
if [ "$(basename "$f")" != "iota-daemon" ]; then
|
||||||
rm "$f"
|
rm "$f"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
@ -95,8 +85,7 @@
|
||||||
then cfg.settingsFile
|
then cfg.settingsFile
|
||||||
else pkgs.writeText "iota-config.json" (builtins.toJSON cfg.settings);
|
else pkgs.writeText "iota-config.json" (builtins.toJSON cfg.settings);
|
||||||
|
|
||||||
descriptionText = "Tensamin Iota";
|
descriptionText = "Tensamin Iota daemon";
|
||||||
#+ lib.optionalString cfg.useTmux " (attach TUI: tmux -S ${cfg.dataDir}/tmux.sock attach -t iota)";
|
|
||||||
in {
|
in {
|
||||||
options.services.iota = {
|
options.services.iota = {
|
||||||
enable = lib.mkEnableOption "Enable the Iota service.";
|
enable = lib.mkEnableOption "Enable the Iota service.";
|
||||||
|
|
@ -132,12 +121,6 @@
|
||||||
description = "Whether to open the firewall for ports used by Iota.";
|
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 {
|
bindAddress = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "0.0.0.0";
|
default = "0.0.0.0";
|
||||||
|
|
@ -150,12 +133,6 @@
|
||||||
description = "The Iota package to use.";
|
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 {
|
settings = lib.mkOption {
|
||||||
type = lib.types.attrs;
|
type = lib.types.attrs;
|
||||||
default = {};
|
default = {};
|
||||||
|
|
@ -181,19 +158,7 @@
|
||||||
|
|
||||||
users.groups.iota = {};
|
users.groups.iota = {};
|
||||||
|
|
||||||
systemd.services.iota = let
|
systemd.services.iota = {
|
||||||
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 {
|
|
||||||
description = descriptionText;
|
description = descriptionText;
|
||||||
wantedBy = ["multi-user.target"];
|
wantedBy = ["multi-user.target"];
|
||||||
after = ["network.target"];
|
after = ["network.target"];
|
||||||
|
|
@ -205,29 +170,7 @@
|
||||||
Group = "iota";
|
Group = "iota";
|
||||||
WorkingDirectory = cfg.dataDir;
|
WorkingDirectory = cfg.dataDir;
|
||||||
|
|
||||||
ExecStart =
|
ExecStart = "${cfg.package}/bin/iota-daemon";
|
||||||
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";
|
|
||||||
|
|
||||||
ExecStartPre = [
|
ExecStartPre = [
|
||||||
("+"
|
("+"
|
||||||
|
|
@ -245,6 +188,8 @@
|
||||||
|
|
||||||
Restart = "always";
|
Restart = "always";
|
||||||
RestartSec = "5s";
|
RestartSec = "5s";
|
||||||
|
RuntimeDirectory = "iota";
|
||||||
|
RuntimeDirectoryMode = "0750";
|
||||||
|
|
||||||
AmbientCapabilities = ["CAP_NET_BIND_SERVICE"];
|
AmbientCapabilities = ["CAP_NET_BIND_SERVICE"];
|
||||||
CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"];
|
CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"];
|
||||||
|
|
@ -262,8 +207,8 @@
|
||||||
LockPersonality = true;
|
LockPersonality = true;
|
||||||
MemoryDenyWriteExecute = true;
|
MemoryDenyWriteExecute = true;
|
||||||
Environment = [
|
Environment = [
|
||||||
"TTP_BIND=${cfg.ttpBind}"
|
|
||||||
"BIND_ADDRESS=${cfg.bindAddress}"
|
"BIND_ADDRESS=${cfg.bindAddress}"
|
||||||
|
"IOTA_SOCKET=/run/iota/iota.sock"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
// lib.optionalAttrs (cfg.environmentFiles != []) {
|
// lib.optionalAttrs (cfg.environmentFiles != []) {
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,26 @@ name = "iota-cli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
legacy-commands = [
|
||||||
|
"dep:iota-logger",
|
||||||
|
"dep:iota-storage",
|
||||||
|
"dep:iota-util",
|
||||||
|
"dep:mtp",
|
||||||
|
"dep:omikron-connector",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iota-logger = { path = "../iota-logger" }
|
iota-logger = { path = "../iota-logger", optional = true }
|
||||||
iota-state = { path = "../iota-state" }
|
iota-state = { path = "../iota-state" }
|
||||||
iota-storage = { path = "../iota-storage" }
|
iota-storage = { path = "../iota-storage", optional = true }
|
||||||
iota-terms = { path = "../iota-terms" }
|
iota-terms = { path = "../iota-terms" }
|
||||||
iota-util = { path = "../iota-util" }
|
iota-util = { path = "../iota-util", optional = true }
|
||||||
omikron-connector = { path = "../omikron-connector" }
|
iota-ipc = { path = "../iota-ipc" }
|
||||||
|
omikron-connector = { path = "../omikron-connector", optional = true }
|
||||||
|
|
||||||
|
mtp = { git = "https://git.methanium.net/Methanium/mtp.git", optional = true }
|
||||||
|
|
||||||
mtp = { git = "https://git.methanium.net/Methanium/mtp.git" }
|
|
||||||
|
|
||||||
actix-web = { version = "4", features = ["rustls-0_23"] }
|
actix-web = { version = "4", features = ["rustls-0_23"] }
|
||||||
actix-web-actors = "4"
|
actix-web-actors = "4"
|
||||||
|
|
@ -56,7 +66,6 @@ sysinfo = "0.38.3"
|
||||||
tokio = { version = "1.50.0", features = ["full"] }
|
tokio = { version = "1.50.0", features = ["full"] }
|
||||||
tokio-tungstenite = { version = "*", features = ["native-tls"] }
|
tokio-tungstenite = { version = "*", features = ["native-tls"] }
|
||||||
tungstenite = "*"
|
tungstenite = "*"
|
||||||
uuid = { version = "*", features = ["v4"] }
|
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
warp = "*"
|
warp = "*"
|
||||||
x448 = { version = "*" }
|
x448 = { version = "*" }
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
use crossterm::event::{KeyCode, KeyEvent};
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
use iota_logger::{log, log_command, log_cv};
|
#[cfg(feature = "legacy-commands")]
|
||||||
|
use iota_logger::{log, log_cv};
|
||||||
|
#[cfg(feature = "legacy-commands")]
|
||||||
use iota_state::{ACTIVE_TASKS, RELOAD, SHUTDOWN};
|
use iota_state::{ACTIVE_TASKS, RELOAD, SHUTDOWN};
|
||||||
|
#[cfg(feature = "legacy-commands")]
|
||||||
use iota_storage::users::{user_manager, user_profile::UserProfile};
|
use iota_storage::users::{user_manager, user_profile::UserProfile};
|
||||||
|
#[cfg(feature = "legacy-commands")]
|
||||||
use iota_storage::util::config_util::modify_config;
|
use iota_storage::util::config_util::modify_config;
|
||||||
|
#[cfg(feature = "legacy-commands")]
|
||||||
use iota_util::file_util;
|
use iota_util::file_util;
|
||||||
|
#[cfg(feature = "legacy-commands")]
|
||||||
use mtp::codec::{CommunicationType, CommunicationValue};
|
use mtp::codec::{CommunicationType, CommunicationValue};
|
||||||
|
#[cfg(feature = "legacy-commands")]
|
||||||
use omikron_connector::omikron_connection::OMIKRON_CONNECTION;
|
use omikron_connector::omikron_connection::OMIKRON_CONNECTION;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
Frame,
|
Frame,
|
||||||
|
|
@ -13,7 +20,6 @@ use ratatui::{
|
||||||
text::{Line, Span},
|
text::{Line, Span},
|
||||||
widgets::{Block, Borders, Paragraph},
|
widgets::{Block, Borders, Paragraph},
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
|
|
@ -25,11 +31,12 @@ use tokio::time::Instant;
|
||||||
use crate::{
|
use crate::{
|
||||||
elements::elements::{Element, InteractableElement, JoinableElement},
|
elements::elements::{Element, InteractableElement, JoinableElement},
|
||||||
interaction_result::InteractionResult,
|
interaction_result::InteractionResult,
|
||||||
ui::FPS,
|
ipc_client::IpcClient,
|
||||||
util::borders::draw_block_joins,
|
util::borders::draw_block_joins,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ConsoleCard {
|
pub struct ConsoleCard {
|
||||||
|
ipc: Arc<IpcClient>,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
|
@ -44,8 +51,9 @@ pub struct ConsoleCard {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConsoleCard {
|
impl ConsoleCard {
|
||||||
pub fn new(title: &str, content: &str) -> Self {
|
pub fn new(title: &str, content: &str, ipc: Arc<IpcClient>) -> Self {
|
||||||
ConsoleCard {
|
ConsoleCard {
|
||||||
|
ipc,
|
||||||
focused: false,
|
focused: false,
|
||||||
title: title.to_string(),
|
title: title.to_string(),
|
||||||
content: content.to_string(),
|
content: content.to_string(),
|
||||||
|
|
@ -290,22 +298,17 @@ impl InteractableElement for ConsoleCard {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
if self.content.is_empty() {
|
if self.content.is_empty() {
|
||||||
log!("");
|
|
||||||
return InteractionResult::Handled;
|
return InteractionResult::Handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
let command = self.content.clone();
|
let command = self.content.clone();
|
||||||
let id = Uuid::new_v4();
|
let ipc = self.ipc.clone();
|
||||||
let id = id.to_string();
|
let seq = std::time::SystemTime::now()
|
||||||
let id = id.split_at(8).0;
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
let task_id = format!("command_{}_{}", command, id);
|
.unwrap_or_default()
|
||||||
ACTIVE_TASKS.insert(task_id.clone());
|
.as_millis() as u64;
|
||||||
|
|
||||||
log_command!("{}", command);
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
run_command(&command).await;
|
let _ = ipc.send_command(seq, command).await;
|
||||||
ACTIVE_TASKS.remove(&task_id);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
self.content.clear();
|
self.content.clear();
|
||||||
|
|
@ -379,6 +382,7 @@ impl InteractableElement for ConsoleCard {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "legacy-commands")]
|
||||||
pub async fn run_command(command: &str) {
|
pub async fn run_command(command: &str) {
|
||||||
let parts = command.split(" ").collect::<Vec<&str>>();
|
let parts = command.split(" ").collect::<Vec<&str>>();
|
||||||
|
|
||||||
|
|
@ -503,6 +507,7 @@ pub async fn run_command(command: &str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "legacy-commands")]
|
||||||
pub async fn ping(time: u64) {
|
pub async fn ping(time: u64) {
|
||||||
let conn = OMIKRON_CONNECTION.clone();
|
let conn = OMIKRON_CONNECTION.clone();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{any::Any, sync::Arc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use crossterm::event::KeyEvent;
|
use crossterm::event::KeyEvent;
|
||||||
use iota_state::APP_STATE;
|
use iota_state::ClientState;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
Frame,
|
Frame,
|
||||||
layout::Rect,
|
layout::Rect,
|
||||||
|
|
@ -34,11 +34,29 @@ impl GRAPHS {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_graph(&self) -> Vec<(f64, f64)> {
|
pub fn get_graph(&self, state: &ClientState) -> Vec<(f64, f64)> {
|
||||||
match self {
|
match self {
|
||||||
GRAPHS::Ram => APP_STATE.lock().unwrap().with_width(28).ram.clone(),
|
GRAPHS::Ram => state
|
||||||
GRAPHS::Cpu => APP_STATE.lock().unwrap().with_width(28).cpu.clone(),
|
.app
|
||||||
GRAPHS::Ping => APP_STATE.lock().unwrap().with_width(28).ping.clone(),
|
.lock()
|
||||||
|
.unwrap_or_else(|error| error.into_inner())
|
||||||
|
.with_width(28)
|
||||||
|
.ram
|
||||||
|
.clone(),
|
||||||
|
GRAPHS::Cpu => state
|
||||||
|
.app
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|error| error.into_inner())
|
||||||
|
.with_width(28)
|
||||||
|
.cpu
|
||||||
|
.clone(),
|
||||||
|
GRAPHS::Ping => state
|
||||||
|
.app
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|error| error.into_inner())
|
||||||
|
.with_width(28)
|
||||||
|
.ping
|
||||||
|
.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,6 +72,7 @@ impl GRAPHS {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub struct GraphCard {
|
pub struct GraphCard {
|
||||||
ui: Arc<UI>,
|
ui: Arc<UI>,
|
||||||
|
state: ClientState,
|
||||||
graph_type: GRAPHS,
|
graph_type: GRAPHS,
|
||||||
|
|
||||||
focused: bool,
|
focused: bool,
|
||||||
|
|
@ -66,9 +85,10 @@ pub struct GraphCard {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphCard {
|
impl GraphCard {
|
||||||
pub fn new(ui: Arc<UI>, graph_type: GRAPHS, title: String) -> Self {
|
pub fn new(ui: Arc<UI>, state: ClientState, graph_type: GRAPHS, title: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ui,
|
ui,
|
||||||
|
state,
|
||||||
graph_type,
|
graph_type,
|
||||||
focused: false,
|
focused: false,
|
||||||
title,
|
title,
|
||||||
|
|
@ -93,7 +113,7 @@ impl Element for GraphCard {
|
||||||
|
|
||||||
fn render(&self, f: &mut Frame, r: Rect) {
|
fn render(&self, f: &mut Frame, r: Rect) {
|
||||||
if self.open {
|
if self.open {
|
||||||
let graph = self.graph_type.get_graph();
|
let graph = self.graph_type.get_graph(&self.state);
|
||||||
let unit = self.graph_type.get_unit();
|
let unit = self.graph_type.get_unit();
|
||||||
let min_x = graph.first().map(|(x, _)| *x).unwrap_or(0.0);
|
let min_x = graph.first().map(|(x, _)| *x).unwrap_or(0.0);
|
||||||
let max_x = graph.last().map(|(x, _)| *x).unwrap_or(100.0);
|
let max_x = graph.last().map(|(x, _)| *x).unwrap_or(100.0);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
use crate::app_state::APP_STATE;
|
|
||||||
use crate::elements::elements::{Element, InteractableElement, JoinableElement};
|
use crate::elements::elements::{Element, InteractableElement, JoinableElement};
|
||||||
use crate::interaction_result::InteractionResult;
|
use crate::interaction_result::InteractionResult;
|
||||||
use crate::util::borders::draw_block_joins;
|
use crate::util::borders::draw_block_joins;
|
||||||
use crossterm::event::{KeyCode, KeyEvent};
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
use iota_logger::PrintType;
|
use iota_state::{ClientState, UiLogEntry};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
Frame,
|
Frame,
|
||||||
layout::Rect,
|
layout::Rect,
|
||||||
|
|
@ -12,60 +11,9 @@ use ratatui::{
|
||||||
widgets::{Block, Borders, Paragraph},
|
widgets::{Block, Borders, Paragraph},
|
||||||
};
|
};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct UiLogEntry {
|
|
||||||
pub timestamp_ms: u128,
|
|
||||||
pub sender: PrintType,
|
|
||||||
pub message: String,
|
|
||||||
pub is_error: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiLogEntry {
|
|
||||||
pub fn format_timestamp(&self) -> String {
|
|
||||||
let secs = (self.timestamp_ms / 1000) as i64;
|
|
||||||
let hours = (secs / 3600) % 24;
|
|
||||||
let minutes = (secs / 60) % 60;
|
|
||||||
let seconds = secs % 60;
|
|
||||||
format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<LogEntry> for UiLogEntry {
|
|
||||||
fn from(entry: LogEntry) -> Self {
|
|
||||||
Self {
|
|
||||||
timestamp_ms: entry.timestamp_ms,
|
|
||||||
sender: entry.sender,
|
|
||||||
message: entry.message,
|
|
||||||
is_error: entry.is_error,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct LogEntry {
|
|
||||||
pub timestamp_ms: u128,
|
|
||||||
pub sender: PrintType,
|
|
||||||
pub message: String,
|
|
||||||
pub is_error: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LogEntry {
|
|
||||||
pub fn new(sender: PrintType, message: String, is_error: bool) -> Self {
|
|
||||||
Self {
|
|
||||||
timestamp_ms: SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_millis(),
|
|
||||||
sender,
|
|
||||||
message,
|
|
||||||
is_error,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LogCard {
|
pub struct LogCard {
|
||||||
|
state: ClientState,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
scroll_offset: usize,
|
scroll_offset: usize,
|
||||||
|
|
@ -76,8 +24,9 @@ pub struct LogCard {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogCard {
|
impl LogCard {
|
||||||
pub fn new() -> Self {
|
pub fn new(state: ClientState) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
state,
|
||||||
focused: false,
|
focused: false,
|
||||||
selected: false,
|
selected: false,
|
||||||
scroll_offset: 0,
|
scroll_offset: 0,
|
||||||
|
|
@ -89,21 +38,17 @@ impl LogCard {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_logs(&self) -> Vec<UiLogEntry> {
|
fn get_logs(&self) -> Vec<UiLogEntry> {
|
||||||
let state = APP_STATE.lock().unwrap();
|
let state = self
|
||||||
|
.state
|
||||||
|
.app
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|error| error.into_inner());
|
||||||
state
|
state
|
||||||
.get_logs()
|
.get_logs()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| UiLogEntry {
|
.map(|e| UiLogEntry {
|
||||||
timestamp_ms: e.timestamp_ms,
|
timestamp_ms: e.timestamp_ms,
|
||||||
sender: match e.sender.as_str() {
|
sender: e.sender.clone(),
|
||||||
"Call" => PrintType::Call,
|
|
||||||
"Client" => PrintType::Client,
|
|
||||||
"Iota" => PrintType::Iota,
|
|
||||||
"Omikron" => PrintType::Omikron,
|
|
||||||
"Omega" => PrintType::Omega,
|
|
||||||
"Command" => PrintType::Command,
|
|
||||||
_ => PrintType::General,
|
|
||||||
},
|
|
||||||
message: e.message.clone(),
|
message: e.message.clone(),
|
||||||
is_error: e.is_error,
|
is_error: e.is_error,
|
||||||
})
|
})
|
||||||
|
|
@ -196,12 +141,24 @@ impl LogCard {
|
||||||
line.push_str(×tamp);
|
line.push_str(×tamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push((line, entry.sender.prefix_color(), entry.is_error));
|
result.push((line, Self::sender_color(&entry.sender), entry.is_error));
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sender_color(sender: &str) -> Color {
|
||||||
|
match sender {
|
||||||
|
"Call" => Color::Magenta,
|
||||||
|
"Client" => Color::Green,
|
||||||
|
"Iota" => Color::Yellow,
|
||||||
|
"Omikron" => Color::Blue,
|
||||||
|
"Omega" => Color::Cyan,
|
||||||
|
"Command" => Color::LightGreen,
|
||||||
|
_ => Color::LightCyan,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn build_all_lines(
|
fn build_all_lines(
|
||||||
&self,
|
&self,
|
||||||
entries: Vec<UiLogEntry>,
|
entries: Vec<UiLogEntry>,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
use crate::ui::UI;
|
use crate::ui::UI;
|
||||||
use crossterm::event::{Event, KeyEvent, KeyEventKind, KeyModifiers, poll, read};
|
use crossterm::event::{Event, KeyEvent, KeyEventKind, KeyModifiers, poll, read};
|
||||||
use iota_state::{RELOAD, SHUTDOWN, UNIQUE};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub fn setup_input_handler(ui: Arc<UI>) {
|
pub fn setup_input_handler(ui: Arc<UI>) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
if *SHUTDOWN.read().await {
|
if ui.is_shutdown() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,7 +25,6 @@ pub fn setup_input_handler(ui: Arc<UI>) {
|
||||||
match event_result {
|
match event_result {
|
||||||
Ok(Some(key_event)) => {
|
Ok(Some(key_event)) => {
|
||||||
handle_input(key_event, ui.clone()).await;
|
handle_input(key_event, ui.clone()).await;
|
||||||
UNIQUE.store(true, Ordering::Relaxed);
|
|
||||||
}
|
}
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
@ -43,11 +40,11 @@ pub async fn handle_input(key: KeyEvent, ui: Arc<UI>) {
|
||||||
match (key.code, key.modifiers) {
|
match (key.code, key.modifiers) {
|
||||||
(crossterm::event::KeyCode::Char('q'), KeyModifiers::CONTROL)
|
(crossterm::event::KeyCode::Char('q'), KeyModifiers::CONTROL)
|
||||||
| (crossterm::event::KeyCode::Char('c'), KeyModifiers::CONTROL) => {
|
| (crossterm::event::KeyCode::Char('c'), KeyModifiers::CONTROL) => {
|
||||||
*SHUTDOWN.write().await = true;
|
ui.request_shutdown();
|
||||||
}
|
}
|
||||||
(crossterm::event::KeyCode::Char('r'), KeyModifiers::CONTROL) => {
|
(crossterm::event::KeyCode::Char('r'), KeyModifiers::CONTROL) => {
|
||||||
*RELOAD.write().await = true;
|
let _ = ui.send_restart().await;
|
||||||
*SHUTDOWN.write().await = true;
|
ui.request_shutdown();
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
ui.handle_input(key).await;
|
ui.handle_input(key).await;
|
||||||
|
|
|
||||||
83
iota-cli/src/ipc_client.rs
Normal file
83
iota-cli/src/ipc_client.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
use iota_ipc::{ClientMessage, DaemonMessage, read_msg, write_msg};
|
||||||
|
use iota_state::{ClientState, UiLogEntry};
|
||||||
|
use std::io::Result;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::net::UnixStream;
|
||||||
|
use tokio::net::unix::OwnedWriteHalf;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
/* The TUI owns this cache. IPC updates replace daemon snapshots and append
|
||||||
|
* logs, so rendering never reaches into daemon-owned storage or connections. */
|
||||||
|
pub struct IpcClient {
|
||||||
|
state: ClientState,
|
||||||
|
writer: Mutex<OwnedWriteHalf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IpcClient {
|
||||||
|
pub async fn connect(path: impl AsRef<Path>) -> Result<Arc<Self>> {
|
||||||
|
let stream = UnixStream::connect(path).await?;
|
||||||
|
let (mut reader, writer) = stream.into_split();
|
||||||
|
let client = Arc::new(Self {
|
||||||
|
state: ClientState::new(),
|
||||||
|
writer: Mutex::new(writer),
|
||||||
|
});
|
||||||
|
let reader_client = client.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Ok(message) = read_msg::<_, DaemonMessage>(&mut reader).await {
|
||||||
|
reader_client.apply(message).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client.send(ClientMessage::Subscribe).await?;
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(&self) -> ClientState {
|
||||||
|
self.state.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_command(&self, seq: u64, line: String) -> Result<()> {
|
||||||
|
self.send(ClientMessage::Command { seq, line }).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send(&self, message: ClientMessage) -> Result<()> {
|
||||||
|
let mut writer = self.writer.lock().await;
|
||||||
|
write_msg(&mut *writer, &message).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn apply(&self, message: DaemonMessage) {
|
||||||
|
let mut state = self
|
||||||
|
.state
|
||||||
|
.app
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|error| error.into_inner());
|
||||||
|
match message {
|
||||||
|
DaemonMessage::LogEntry(entry) => state.push_log(UiLogEntry {
|
||||||
|
timestamp_ms: entry.timestamp_ms,
|
||||||
|
sender: entry.sender,
|
||||||
|
message: entry.message,
|
||||||
|
is_error: entry.is_error,
|
||||||
|
}),
|
||||||
|
DaemonMessage::StateUpdate(snapshot) => {
|
||||||
|
state.cpu = snapshot.cpu;
|
||||||
|
state.ram = snapshot.ram;
|
||||||
|
state.ping = snapshot.ping;
|
||||||
|
state.net_up = snapshot.net_up;
|
||||||
|
state.net_down = snapshot.net_down;
|
||||||
|
state.sys_info = snapshot.sys_info;
|
||||||
|
}
|
||||||
|
DaemonMessage::CommandResult {
|
||||||
|
success, message, ..
|
||||||
|
} => state.push_log(UiLogEntry {
|
||||||
|
timestamp_ms: std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_millis(),
|
||||||
|
sender: "Command".into(),
|
||||||
|
message,
|
||||||
|
is_error: !success,
|
||||||
|
}),
|
||||||
|
DaemonMessage::Pong { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,5 +18,6 @@ pub mod util {
|
||||||
}
|
}
|
||||||
pub mod app_state;
|
pub mod app_state;
|
||||||
pub mod input_handler;
|
pub mod input_handler;
|
||||||
|
pub mod ipc_client;
|
||||||
pub mod interaction_result;
|
pub mod interaction_result;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
|
||||||
|
|
@ -36,22 +36,23 @@ impl MainScreen {
|
||||||
vec![Some(1), Some(4)],
|
vec![Some(1), Some(4)],
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut log_card = LogCard::new();
|
let state = ui.client_state();
|
||||||
|
let mut log_card = LogCard::new(state.clone());
|
||||||
log_card.set_borders(Borders::TOP.union(Borders::RIGHT).union(Borders::LEFT));
|
log_card.set_borders(Borders::TOP.union(Borders::RIGHT).union(Borders::LEFT));
|
||||||
let mut console_card = ConsoleCard::new("Console", "");
|
let mut console_card = ConsoleCard::new("Console", "", ui.ipc());
|
||||||
console_card.set_joins(Borders::TOP);
|
console_card.set_joins(Borders::TOP);
|
||||||
|
|
||||||
elements.push(Box::new(log_card));
|
elements.push(Box::new(log_card));
|
||||||
elements.push(Box::new(console_card));
|
elements.push(Box::new(console_card));
|
||||||
|
|
||||||
let mut ram_graph = GraphCard::new(ui.clone(), GRAPHS::Ram, "RAM".into());
|
let mut ram_graph = GraphCard::new(ui.clone(), state.clone(), GRAPHS::Ram, "RAM".into());
|
||||||
ram_graph.set_borders(Borders::TOP.union(Borders::LEFT).union(Borders::RIGHT));
|
ram_graph.set_borders(Borders::TOP.union(Borders::LEFT).union(Borders::RIGHT));
|
||||||
elements.push(Box::new(ram_graph));
|
elements.push(Box::new(ram_graph));
|
||||||
let mut cpu_graph = GraphCard::new(ui.clone(), GRAPHS::Cpu, "CPU".into());
|
let mut cpu_graph = GraphCard::new(ui.clone(), state.clone(), GRAPHS::Cpu, "CPU".into());
|
||||||
cpu_graph.set_borders(Borders::TOP.union(Borders::LEFT).union(Borders::RIGHT));
|
cpu_graph.set_borders(Borders::TOP.union(Borders::LEFT).union(Borders::RIGHT));
|
||||||
cpu_graph.set_joins(Borders::TOP);
|
cpu_graph.set_joins(Borders::TOP);
|
||||||
elements.push(Box::new(cpu_graph));
|
elements.push(Box::new(cpu_graph));
|
||||||
let mut ping_graph = GraphCard::new(ui.clone(), GRAPHS::Ping, "Ping".into());
|
let mut ping_graph = GraphCard::new(ui.clone(), state, GRAPHS::Ping, "Ping".into());
|
||||||
ping_graph.set_joins(Borders::TOP);
|
ping_graph.set_joins(Borders::TOP);
|
||||||
elements.push(Box::new(ping_graph));
|
elements.push(Box::new(ping_graph));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
input_handler::setup_input_handler, interaction_result::InteractionResult,
|
input_handler::setup_input_handler, interaction_result::InteractionResult,
|
||||||
screens::screens::Screen,
|
ipc_client::IpcClient, screens::screens::Screen,
|
||||||
};
|
};
|
||||||
use crossterm::event::KeyEvent;
|
use crossterm::event::KeyEvent;
|
||||||
use iota_state::{ACTIVE_TASKS, SHUTDOWN, UNIQUE};
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use ratatui::{Terminal, backend::CrosstermBackend, init};
|
use ratatui::{Terminal, backend::CrosstermBackend, init};
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
io::Stdout,
|
io::Stdout,
|
||||||
sync::{Arc, Mutex, atomic::Ordering},
|
sync::{
|
||||||
|
Arc, Mutex,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tokio::{sync::RwLock, time::Instant};
|
use tokio::{sync::RwLock, time::Instant};
|
||||||
|
|
@ -19,14 +21,15 @@ use tokio::{sync::RwLock, time::Instant};
|
||||||
pub static FPS: Lazy<RwLock<(f64, f64)>> = Lazy::new(|| RwLock::new((0.0, 0.0)));
|
pub static FPS: Lazy<RwLock<(f64, f64)>> = Lazy::new(|| RwLock::new((0.0, 0.0)));
|
||||||
|
|
||||||
pub struct UI {
|
pub struct UI {
|
||||||
|
ipc: Arc<IpcClient>,
|
||||||
|
shutdown: AtomicBool,
|
||||||
pub terminal: Arc<Mutex<Terminal<CrosstermBackend<Stdout>>>>,
|
pub terminal: Arc<Mutex<Terminal<CrosstermBackend<Stdout>>>>,
|
||||||
screen_stack: Arc<RwLock<Vec<Box<dyn Screen>>>>,
|
screen_stack: Arc<RwLock<Vec<Box<dyn Screen>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_tui() -> Arc<UI> {
|
pub fn start_tui(ipc: Arc<IpcClient>) -> Arc<UI> {
|
||||||
let ui = Arc::new(UI::new());
|
let ui = Arc::new(UI::new(ipc));
|
||||||
let uic = ui.clone();
|
let uic = ui.clone();
|
||||||
ACTIVE_TASKS.insert("UI Renderer".to_string());
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut last_render = Instant::now();
|
let mut last_render = Instant::now();
|
||||||
|
|
||||||
|
|
@ -39,11 +42,11 @@ pub fn start_tui() -> Arc<UI> {
|
||||||
let mut skipped = 0;
|
let mut skipped = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if *SHUTDOWN.read().await {
|
if uic.is_shutdown() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if skipped > 5 || UNIQUE.load(Ordering::Relaxed) {
|
if skipped > 5 {
|
||||||
uic.render().await;
|
uic.render().await;
|
||||||
|
|
||||||
skip_samples.push_back(skipped);
|
skip_samples.push_back(skipped);
|
||||||
|
|
@ -88,27 +91,47 @@ pub fn start_tui() -> Arc<UI> {
|
||||||
*FPS.write().await = (avg_fps, avg_skips_percentage);
|
*FPS.write().await = (avg_fps, avg_skips_percentage);
|
||||||
|
|
||||||
last_render = Instant::now();
|
last_render = Instant::now();
|
||||||
UNIQUE.store(false, Ordering::Relaxed);
|
|
||||||
} else {
|
} else {
|
||||||
skipped += 1;
|
skipped += 1;
|
||||||
}
|
}
|
||||||
tokio::time::sleep(Duration::from_millis(16)).await;
|
tokio::time::sleep(Duration::from_millis(16)).await;
|
||||||
}
|
}
|
||||||
ACTIVE_TASKS.remove("UI Renderer");
|
|
||||||
ratatui::restore();
|
ratatui::restore();
|
||||||
});
|
});
|
||||||
setup_input_handler(ui.clone());
|
setup_input_handler(ui.clone());
|
||||||
ui
|
ui
|
||||||
}
|
}
|
||||||
impl UI {
|
impl UI {
|
||||||
pub fn new() -> Self {
|
pub fn new(ipc: Arc<IpcClient>) -> Self {
|
||||||
let terminal = init();
|
let terminal = init();
|
||||||
Self {
|
Self {
|
||||||
|
ipc,
|
||||||
|
shutdown: AtomicBool::new(false),
|
||||||
terminal: Arc::new(Mutex::new(terminal)),
|
terminal: Arc::new(Mutex::new(terminal)),
|
||||||
screen_stack: Arc::new(RwLock::new(Vec::new())),
|
screen_stack: Arc::new(RwLock::new(Vec::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ipc(&self) -> Arc<IpcClient> {
|
||||||
|
self.ipc.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client_state(&self) -> iota_state::ClientState {
|
||||||
|
self.ipc.state()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_shutdown(&self) -> bool {
|
||||||
|
self.shutdown.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_shutdown(&self) {
|
||||||
|
self.shutdown.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_restart(&self) -> std::io::Result<()> {
|
||||||
|
self.ipc.send_command(0, "restart".into()).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn set_screen(&self, screen: Box<dyn Screen>) {
|
pub async fn set_screen(&self, screen: Box<dyn Screen>) {
|
||||||
self.screen_stack.write().await.push(screen);
|
self.screen_stack.write().await.push(screen);
|
||||||
}
|
}
|
||||||
|
|
@ -140,7 +163,7 @@ impl UI {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
|
|
||||||
if stack.is_empty() {
|
if stack.is_empty() {
|
||||||
*SHUTDOWN.write().await = true;
|
self.request_shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InteractionResult::Handled => {}
|
InteractionResult::Handled => {}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use tokio::time::{Duration, sleep};
|
||||||
use iota_state::{ACTIVE_TASKS, APP_STATE, AppState, RELOAD, SHUTDOWN};
|
use iota_state::{ACTIVE_TASKS, APP_STATE, AppState, RELOAD, SHUTDOWN};
|
||||||
|
|
||||||
use iota_cli::screens::main_screen::MainScreen;
|
use iota_cli::screens::main_screen::MainScreen;
|
||||||
use iota_cli::ui::start_tui;
|
use iota_cli::{ipc_client::IpcClient, ui::start_tui};
|
||||||
use iota_logger::{self as logger, language_creator};
|
use iota_logger::{self as logger, language_creator};
|
||||||
use iota_logger::{log, log_t};
|
use iota_logger::{log, log_t};
|
||||||
use iota_storage::users::user_manager;
|
use iota_storage::users::user_manager;
|
||||||
|
|
@ -22,7 +22,10 @@ async fn main() {
|
||||||
*RELOAD.write().await = false;
|
*RELOAD.write().await = false;
|
||||||
*SHUTDOWN.write().await = false;
|
*SHUTDOWN.write().await = false;
|
||||||
|
|
||||||
let ui = start_tui();
|
let ipc = IpcClient::connect("/run/iota/iota.sock")
|
||||||
|
.await
|
||||||
|
.expect("iota-daemon must be running before starting iota-core");
|
||||||
|
let ui = start_tui(ipc);
|
||||||
|
|
||||||
let (eula, tos_pp) = match consent_state::check(ui.clone()).await {
|
let (eula, tos_pp) = match consent_state::check(ui.clone()).await {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
|
|
|
||||||
15
iota-daemon-lib/Cargo.toml
Normal file
15
iota-daemon-lib/Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "iota-daemon-lib"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iota-ipc = { path = "../iota-ipc" }
|
||||||
|
iota-logger = { path = "../iota-logger" }
|
||||||
|
iota-state = { path = "../iota-state" }
|
||||||
|
iota-storage = { path = "../iota-storage" }
|
||||||
|
iota-util = { path = "../iota-util" }
|
||||||
|
omikron-connector = { path = "../omikron-connector" }
|
||||||
|
mtp = { git = "https://git.methanium.net/Methanium/mtp.git" }
|
||||||
|
sysinfo = "0.38.3"
|
||||||
|
tokio = { version = "1.50.0", features = ["full"] }
|
||||||
114
iota-daemon-lib/src/command_router.rs
Normal file
114
iota-daemon-lib/src/command_router.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
use crate::DaemonRuntime;
|
||||||
|
use iota_ipc::DaemonMessage;
|
||||||
|
use iota_logger::{log, log_command};
|
||||||
|
use iota_storage::users::user_manager;
|
||||||
|
use iota_storage::util::config_util::modify_config;
|
||||||
|
use mtp::codec::{CommunicationType, CommunicationValue};
|
||||||
|
use omikron_connector::omikron_connection::OMIKRON_CONNECTION;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CommandRouter {
|
||||||
|
runtime: Arc<DaemonRuntime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandRouter {
|
||||||
|
pub fn new(runtime: Arc<DaemonRuntime>) -> Self {
|
||||||
|
Self { runtime }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn route(&self, seq: u64, line: String) -> DaemonMessage {
|
||||||
|
log_command!("{}", line);
|
||||||
|
let result = self.execute(&line).await;
|
||||||
|
DaemonMessage::CommandResult {
|
||||||
|
seq,
|
||||||
|
success: result.is_ok(),
|
||||||
|
message: result.unwrap_or_else(|error| error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute(&self, line: &str) -> Result<String, String> {
|
||||||
|
let parts = line
|
||||||
|
.trim_start_matches('/')
|
||||||
|
.split_whitespace()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
match parts.as_slice() {
|
||||||
|
["tasks"] => Ok(self
|
||||||
|
.runtime
|
||||||
|
.state
|
||||||
|
.active_tasks
|
||||||
|
.iter()
|
||||||
|
.map(|task| task.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")),
|
||||||
|
["help"] => Ok(
|
||||||
|
"Available commands: tasks, ping, user, reconnect, regenerate, reload, shutdown"
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
["ping"] => self.ping(20).await,
|
||||||
|
["ping", seconds] => self.ping(seconds.parse::<u64>().unwrap_or(20)).await,
|
||||||
|
["user", "add", username] => {
|
||||||
|
let (user, _) = omikron_connector::user_ops::create_user(username).await;
|
||||||
|
user.map(|user| format!("Created user {}", user.user_id))
|
||||||
|
.ok_or_else(|| "User creation failed".into())
|
||||||
|
}
|
||||||
|
["user", "remove", username] => {
|
||||||
|
let user = user_manager::get_user_by_username(username)
|
||||||
|
.ok_or_else(|| "Username does not exist".to_string())?;
|
||||||
|
let message = CommunicationValue::new(CommunicationType::DeleteUser)
|
||||||
|
.with_sender(user.user_id as u64);
|
||||||
|
OMIKRON_CONNECTION
|
||||||
|
.send_message(&message)
|
||||||
|
.await
|
||||||
|
.map_err(|error| error.to_string())?;
|
||||||
|
user_manager::remove_user(user.user_id);
|
||||||
|
Ok(format!("Removed user {}", user.user_id))
|
||||||
|
}
|
||||||
|
["user", "list"] => Ok(user_manager::get_users()
|
||||||
|
.into_iter()
|
||||||
|
.map(|user| format!("{} ({})", user.username, user.user_id))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")),
|
||||||
|
["reconnect"] => {
|
||||||
|
OMIKRON_CONNECTION.reconnect().await;
|
||||||
|
Ok("Reconnected to Omikron server".into())
|
||||||
|
}
|
||||||
|
["regenerate", "keys"] => {
|
||||||
|
modify_config(|config| {
|
||||||
|
config.public_key = None;
|
||||||
|
config.private_key = None;
|
||||||
|
config.iota_id = None;
|
||||||
|
});
|
||||||
|
OMIKRON_CONNECTION.reconnect().await;
|
||||||
|
Ok("Key pair regenerated and Omikron reconnection requested".into())
|
||||||
|
}
|
||||||
|
["reload"] | ["restart"] => {
|
||||||
|
*self.runtime.state.reload.write().await = true;
|
||||||
|
*self.runtime.state.shutdown.write().await = true;
|
||||||
|
Ok("Daemon restart requested".into())
|
||||||
|
}
|
||||||
|
["shutdown"] | ["stop"] => {
|
||||||
|
*self.runtime.state.shutdown.write().await = true;
|
||||||
|
Ok("Daemon shutdown requested".into())
|
||||||
|
}
|
||||||
|
_ => Err("Unknown command".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ping(&self, seconds: u64) -> Result<String, String> {
|
||||||
|
let response = OMIKRON_CONNECTION
|
||||||
|
.await_response(
|
||||||
|
&CommunicationValue::new(CommunicationType::Ping),
|
||||||
|
Some(Duration::from_secs(seconds)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match response {
|
||||||
|
Ok(value) => {
|
||||||
|
log!("{}", iota_logger::format_cv(&value));
|
||||||
|
Ok("Ping response received".into())
|
||||||
|
}
|
||||||
|
Err(error) => Err(format!("Ping error: {error:?}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
72
iota-daemon-lib/src/daemon_state.rs
Normal file
72
iota-daemon-lib/src/daemon_state.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
use iota_ipc::StateSnapshot;
|
||||||
|
use iota_state::DaemonState;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use sysinfo::{RefreshKind, System};
|
||||||
|
|
||||||
|
/* This wrapper exposes daemon state as IPC-safe snapshots while preserving a
|
||||||
|
* single owned state instance for all daemon subsystems. */
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct DaemonRuntime {
|
||||||
|
pub state: Arc<DaemonState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DaemonRuntime {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
state: Arc::new(DaemonState::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snapshot(&self) -> StateSnapshot {
|
||||||
|
let state = self
|
||||||
|
.state
|
||||||
|
.app
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|error| error.into_inner());
|
||||||
|
StateSnapshot {
|
||||||
|
cpu: state.cpu.clone(),
|
||||||
|
ram: state.ram.clone(),
|
||||||
|
ping: state.ping.clone(),
|
||||||
|
net_up: state.net_up.clone(),
|
||||||
|
net_down: state.net_down.clone(),
|
||||||
|
sys_info: state.sys_info.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_system_monitor(&self) {
|
||||||
|
let runtime = self.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
runtime.state.active_tasks.insert("System monitor".into());
|
||||||
|
let mut system = System::new_with_specifics(RefreshKind::everything());
|
||||||
|
let mut counter = 0.0;
|
||||||
|
loop {
|
||||||
|
if *runtime.state.shutdown.read().await {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
system.refresh_cpu_all();
|
||||||
|
system.refresh_memory();
|
||||||
|
let cpu = system.global_cpu_usage() as f64;
|
||||||
|
let total_memory = system.total_memory();
|
||||||
|
let ram = if total_memory == 0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
system.used_memory() as f64 / total_memory as f64 * 100.0
|
||||||
|
};
|
||||||
|
{
|
||||||
|
let mut state = runtime
|
||||||
|
.state
|
||||||
|
.app
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|error| error.into_inner());
|
||||||
|
state.push_cpu((counter, cpu));
|
||||||
|
state.push_ram((counter, ram));
|
||||||
|
state.sys_info = format!("CPU: {cpu:.1}% RAM: {ram:.1}%");
|
||||||
|
}
|
||||||
|
counter += 1.0;
|
||||||
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||||
|
}
|
||||||
|
runtime.state.active_tasks.remove("System monitor");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
113
iota-daemon-lib/src/ipc_server.rs
Normal file
113
iota-daemon-lib/src/ipc_server.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
use crate::{CommandRouter, DaemonRuntime};
|
||||||
|
use iota_ipc::{ClientMessage, DaemonMessage, read_msg, write_msg};
|
||||||
|
use std::io::Result;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::{env, os::fd::FromRawFd};
|
||||||
|
use tokio::net::{UnixListener, UnixStream};
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
pub struct IpcServer {
|
||||||
|
path: PathBuf,
|
||||||
|
runtime: Arc<DaemonRuntime>,
|
||||||
|
messages: broadcast::Sender<DaemonMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IpcServer {
|
||||||
|
pub fn new(
|
||||||
|
path: impl Into<PathBuf>,
|
||||||
|
runtime: Arc<DaemonRuntime>,
|
||||||
|
messages: broadcast::Sender<DaemonMessage>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
path: path.into(),
|
||||||
|
runtime,
|
||||||
|
messages,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(self) -> Result<()> {
|
||||||
|
if let Some(parent) = self.path.parent() {
|
||||||
|
tokio::fs::create_dir_all(parent).await?;
|
||||||
|
}
|
||||||
|
let listener = match activated_listener()? {
|
||||||
|
Some(listener) => listener,
|
||||||
|
None => {
|
||||||
|
remove_stale_socket(&self.path).await?;
|
||||||
|
UnixListener::bind(&self.path)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loop {
|
||||||
|
let (stream, _) = listener.accept().await?;
|
||||||
|
let runtime = self.runtime.clone();
|
||||||
|
let messages = self.messages.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let _ = handle_client(stream, runtime, messages).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* systemd hands the first socket-activated file descriptor to the service as
|
||||||
|
* descriptor 3. Manual launches continue to bind the configured socket path. */
|
||||||
|
fn activated_listener() -> Result<Option<UnixListener>> {
|
||||||
|
let listen_fds = env::var("LISTEN_FDS")
|
||||||
|
.ok()
|
||||||
|
.and_then(|value| value.parse::<u32>().ok());
|
||||||
|
let listen_pid = env::var("LISTEN_PID")
|
||||||
|
.ok()
|
||||||
|
.and_then(|value| value.parse::<u32>().ok());
|
||||||
|
if listen_fds != Some(1) || listen_pid != Some(std::process::id()) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let listener = unsafe { std::os::unix::net::UnixListener::from_raw_fd(3) };
|
||||||
|
UnixListener::from_std(listener).map(Some)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remove_stale_socket(path: &Path) -> Result<()> {
|
||||||
|
match tokio::fs::symlink_metadata(path).await {
|
||||||
|
Ok(_) => tokio::fs::remove_file(path).await,
|
||||||
|
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
|
||||||
|
Err(error) => Err(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_client(
|
||||||
|
stream: UnixStream,
|
||||||
|
runtime: Arc<DaemonRuntime>,
|
||||||
|
messages: broadcast::Sender<DaemonMessage>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let (mut reader, mut writer) = stream.into_split();
|
||||||
|
let mut outgoing = messages.subscribe();
|
||||||
|
let initial = DaemonMessage::StateUpdate(runtime.snapshot());
|
||||||
|
write_msg(&mut writer, &initial).await?;
|
||||||
|
let writer_task = tokio::spawn(async move {
|
||||||
|
while let Ok(message) = outgoing.recv().await {
|
||||||
|
if write_msg(&mut writer, &message).await.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let router = CommandRouter::new(runtime.clone());
|
||||||
|
loop {
|
||||||
|
match read_msg::<_, ClientMessage>(&mut reader).await {
|
||||||
|
Ok(ClientMessage::Command { seq, line }) => {
|
||||||
|
let result = router.route(seq, line).await;
|
||||||
|
let _ = messages.send(result);
|
||||||
|
}
|
||||||
|
Ok(ClientMessage::Subscribe) => {
|
||||||
|
let _ = messages.send(DaemonMessage::StateUpdate(runtime.snapshot()));
|
||||||
|
}
|
||||||
|
Ok(ClientMessage::Ping { seq }) => {
|
||||||
|
let _ = messages.send(DaemonMessage::Pong { seq });
|
||||||
|
}
|
||||||
|
Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => break,
|
||||||
|
Err(error) => {
|
||||||
|
writer_task.abort();
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer_task.abort();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
8
iota-daemon-lib/src/lib.rs
Normal file
8
iota-daemon-lib/src/lib.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
pub mod command_router;
|
||||||
|
pub mod daemon_state;
|
||||||
|
pub mod ipc_server;
|
||||||
|
pub mod log_broadcaster;
|
||||||
|
|
||||||
|
pub use command_router::CommandRouter;
|
||||||
|
pub use daemon_state::DaemonRuntime;
|
||||||
|
pub use ipc_server::IpcServer;
|
||||||
21
iota-daemon-lib/src/log_broadcaster.rs
Normal file
21
iota-daemon-lib/src/log_broadcaster.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
use iota_ipc::{DaemonMessage, LogEntry};
|
||||||
|
use iota_logger::subscribe;
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
/* The daemon adapts logger output to the wire protocol so the logger stays
|
||||||
|
* independent from both the socket implementation and TUI state. */
|
||||||
|
pub fn spawn(message_tx: broadcast::Sender<DaemonMessage>) {
|
||||||
|
let Some(mut logs) = subscribe() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Ok(entry) = logs.recv().await {
|
||||||
|
let _ = message_tx.send(DaemonMessage::LogEntry(LogEntry {
|
||||||
|
timestamp_ms: entry.timestamp_ms,
|
||||||
|
sender: entry.sender,
|
||||||
|
message: entry.message,
|
||||||
|
is_error: entry.is_error,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
14
iota-daemon/Cargo.toml
Normal file
14
iota-daemon/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "iota-daemon"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iota-daemon-lib = { path = "../iota-daemon-lib" }
|
||||||
|
iota-ipc = { path = "../iota-ipc" }
|
||||||
|
iota-logger = { path = "../iota-logger" }
|
||||||
|
iota-state = { path = "../iota-state" }
|
||||||
|
iota-storage = { path = "../iota-storage" }
|
||||||
|
omikron-connector = { path = "../omikron-connector" }
|
||||||
|
web-server = { path = "../web-server" }
|
||||||
|
tokio = { version = "1.50.0", features = ["full"] }
|
||||||
58
iota-daemon/src/main.rs
Normal file
58
iota-daemon/src/main.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
use iota_daemon_lib::{DaemonRuntime, IpcServer, log_broadcaster};
|
||||||
|
use iota_logger::{self as logger, log, log_t};
|
||||||
|
use iota_storage::users::user_manager;
|
||||||
|
use iota_storage::util::config_util::CONFIG;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
fn socket_path() -> PathBuf {
|
||||||
|
std::env::var_os("IOTA_SOCKET")
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|| PathBuf::from("/run/iota/iota.sock"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
|
async fn main() {
|
||||||
|
logger::startup();
|
||||||
|
iota_storage::util::config_util::load_config();
|
||||||
|
if user_manager::load_users().await.is_err() {
|
||||||
|
log_t!("user_load_failed");
|
||||||
|
}
|
||||||
|
let runtime = Arc::new(DaemonRuntime::new());
|
||||||
|
runtime.spawn_system_monitor();
|
||||||
|
let (messages, _) = broadcast::channel(512);
|
||||||
|
log_broadcaster::spawn(messages.clone());
|
||||||
|
let state_updates = runtime.clone();
|
||||||
|
let state_messages = messages.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
if *state_updates.state.shutdown.read().await {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let _ = state_messages.send(iota_ipc::DaemonMessage::StateUpdate(
|
||||||
|
state_updates.snapshot(),
|
||||||
|
));
|
||||||
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let port = CONFIG.load().port;
|
||||||
|
if !web_server::start(port).await {
|
||||||
|
log!("Failed to start the MTP web server on port {}", port);
|
||||||
|
}
|
||||||
|
let _ = omikron_connector::omikron_connection::get_omikron_connection().await;
|
||||||
|
|
||||||
|
let server = IpcServer::new(socket_path(), runtime.clone(), messages);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(error) = server.run().await {
|
||||||
|
eprintln!("iota-daemon IPC server failed: {error}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
log!("iota-daemon started");
|
||||||
|
while !*runtime.state.shutdown.read().await {
|
||||||
|
tokio::time::sleep(Duration::from_millis(250)).await;
|
||||||
|
}
|
||||||
|
log!("iota-daemon stopping");
|
||||||
|
}
|
||||||
9
iota-ipc/Cargo.toml
Normal file
9
iota-ipc/Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "iota-ipc"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
tokio = { version = "1.53.1", features = ["io-util", "macros", "rt"] }
|
||||||
5
iota-ipc/src/lib.rs
Normal file
5
iota-ipc/src/lib.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod protocol;
|
||||||
|
pub mod transport;
|
||||||
|
|
||||||
|
pub use protocol::{ClientMessage, DaemonMessage, LogEntry, StateSnapshot};
|
||||||
|
pub use transport::{read_msg, write_msg};
|
||||||
42
iota-ipc/src/protocol.rs
Normal file
42
iota-ipc/src/protocol.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
|
||||||
|
pub enum ClientMessage {
|
||||||
|
Command { seq: u64, line: String },
|
||||||
|
Subscribe,
|
||||||
|
Ping { seq: u64 },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
|
||||||
|
pub enum DaemonMessage {
|
||||||
|
LogEntry(LogEntry),
|
||||||
|
StateUpdate(StateSnapshot),
|
||||||
|
CommandResult {
|
||||||
|
seq: u64,
|
||||||
|
success: bool,
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
|
Pong {
|
||||||
|
seq: u64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct LogEntry {
|
||||||
|
pub timestamp_ms: u128,
|
||||||
|
pub sender: String,
|
||||||
|
pub message: String,
|
||||||
|
pub is_error: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
|
pub struct StateSnapshot {
|
||||||
|
pub cpu: Vec<(f64, f64)>,
|
||||||
|
pub ram: Vec<(f64, f64)>,
|
||||||
|
pub ping: Vec<(f64, f64)>,
|
||||||
|
pub net_up: Vec<(f64, f64)>,
|
||||||
|
pub net_down: Vec<(f64, f64)>,
|
||||||
|
pub sys_info: String,
|
||||||
|
}
|
||||||
59
iota-ipc/src/transport.rs
Normal file
59
iota-ipc/src/transport.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use std::io::{Error, ErrorKind, Result};
|
||||||
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||||
|
|
||||||
|
const MAX_MESSAGE_SIZE: usize = 1024 * 1024;
|
||||||
|
|
||||||
|
/* Length-prefixing preserves message boundaries on a byte stream and bounds
|
||||||
|
* allocations before JSON is deserialized. */
|
||||||
|
pub async fn write_msg<W, T>(writer: &mut W, message: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
W: AsyncWrite + Unpin,
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
let payload =
|
||||||
|
serde_json::to_vec(message).map_err(|error| Error::new(ErrorKind::InvalidData, error))?;
|
||||||
|
let len = u32::try_from(payload.len())
|
||||||
|
.map_err(|_| Error::new(ErrorKind::InvalidData, "IPC message is too large"))?;
|
||||||
|
writer.write_u32(len).await?;
|
||||||
|
writer.write_all(&payload).await?;
|
||||||
|
writer.flush().await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read_msg<R, T>(reader: &mut R) -> Result<T>
|
||||||
|
where
|
||||||
|
R: AsyncRead + Unpin,
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
let len = reader.read_u32().await? as usize;
|
||||||
|
if len > MAX_MESSAGE_SIZE {
|
||||||
|
return Err(Error::new(
|
||||||
|
ErrorKind::InvalidData,
|
||||||
|
"IPC message exceeds limit",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut payload = vec![0; len];
|
||||||
|
reader.read_exact(&mut payload).await?;
|
||||||
|
serde_json::from_slice(&payload).map_err(|error| Error::new(ErrorKind::InvalidData, error))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{read_msg, write_msg};
|
||||||
|
use crate::protocol::ClientMessage;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn round_trips_framed_messages() {
|
||||||
|
let (mut writer, mut reader) = tokio::io::duplex(1024);
|
||||||
|
let message = ClientMessage::Command {
|
||||||
|
seq: 4,
|
||||||
|
line: "help".into(),
|
||||||
|
};
|
||||||
|
write_msg(&mut writer, &message)
|
||||||
|
.await
|
||||||
|
.expect("write succeeds");
|
||||||
|
let received: ClientMessage = read_msg(&mut reader).await.expect("read succeeds");
|
||||||
|
assert!(matches!(received, ClientMessage::Command { seq: 4, line } if line == "help"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,3 +12,4 @@ mtp = { git = "https://git.methanium.net/Methanium/mtp.git" }
|
||||||
ratatui = "0.30.0"
|
ratatui = "0.30.0"
|
||||||
json = "0.12.4"
|
json = "0.12.4"
|
||||||
once_cell = "1.21.4"
|
once_cell = "1.21.4"
|
||||||
|
tokio = { version = "1.50.0", features = ["sync"] }
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,13 @@ use std::{
|
||||||
use mtp::codec::{CommunicationValue, DataTypeId, DataValue, Version};
|
use mtp::codec::{CommunicationValue, DataTypeId, DataValue, Version};
|
||||||
use ratatui::style::Color;
|
use ratatui::style::Color;
|
||||||
|
|
||||||
use iota_state::{APP_STATE, UNIQUE, UiLogEntry};
|
use iota_state::{UNIQUE, UiLogEntry};
|
||||||
|
use tokio::sync::broadcast;
|
||||||
pub mod language_creator;
|
pub mod language_creator;
|
||||||
pub mod language_manager;
|
pub mod language_manager;
|
||||||
|
|
||||||
static LOGGER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new();
|
static LOGGER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new();
|
||||||
|
static LOG_BROADCASTER: OnceLock<broadcast::Sender<UiLogEntry>> = OnceLock::new();
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
|
@ -51,9 +53,15 @@ struct LogMessage {
|
||||||
message: Option<String>,
|
message: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The logger owns file persistence while consumers receive rendered entries
|
||||||
|
* through a process-local broadcast subscription. */
|
||||||
pub fn startup() {
|
pub fn startup() {
|
||||||
let (tx, rx) = mpsc::channel::<LogMessage>();
|
let (tx, rx) = mpsc::channel::<LogMessage>();
|
||||||
LOGGER.set(tx).expect("Logger already initialized");
|
if LOGGER.set(tx).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let (broadcast_tx, _) = broadcast::channel(512);
|
||||||
|
let _ = LOG_BROADCASTER.set(broadcast_tx.clone());
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let working_dir = iota_util::file_util::get_directory();
|
let working_dir = iota_util::file_util::get_directory();
|
||||||
|
|
@ -106,12 +114,15 @@ pub fn startup() {
|
||||||
is_error: msg.is_error,
|
is_error: msg.is_error,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut state = APP_STATE.lock().unwrap();
|
let _ = broadcast_tx.send(entry);
|
||||||
state.push_log(entry);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn subscribe() -> Option<broadcast::Receiver<UiLogEntry>> {
|
||||||
|
LOG_BROADCASTER.get().map(broadcast::Sender::subscribe)
|
||||||
|
}
|
||||||
|
|
||||||
fn format_timestamp_inline(timestamp_ms: u128) -> String {
|
fn format_timestamp_inline(timestamp_ms: u128) -> String {
|
||||||
let secs = (timestamp_ms / 1000) as i64;
|
let secs = (timestamp_ms / 1000) as i64;
|
||||||
let hours = (secs / 3600) % 24;
|
let hours = (secs / 3600) % 24;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@ name = "iota-state"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["legacy-globals"]
|
||||||
|
legacy-globals = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dashmap = "6.1.0"
|
dashmap = "6.1.0"
|
||||||
once_cell = "1.21.3"
|
once_cell = "1.21.3"
|
||||||
|
|
@ -10,3 +14,4 @@ tokio = { version = "1.50.0", features = ["full"] }
|
||||||
json = "*"
|
json = "*"
|
||||||
sysinfo = "0.38.3"
|
sysinfo = "0.38.3"
|
||||||
mtp = { git = "https://git.methanium.net/Methanium/mtp.git" }
|
mtp = { git = "https://git.methanium.net/Methanium/mtp.git" }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,67 @@
|
||||||
use dashmap::DashSet;
|
use dashmap::DashSet;
|
||||||
use json::{JsonValue, object};
|
use json::{JsonValue, object};
|
||||||
|
#[cfg(feature = "legacy-globals")]
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::sync::{Arc, LazyLock, Mutex, atomic::AtomicBool};
|
#[cfg(feature = "legacy-globals")]
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
use std::sync::{Arc, Mutex, atomic::AtomicBool};
|
||||||
|
#[cfg(feature = "legacy-globals")]
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
#[cfg(feature = "legacy-globals")]
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
#[cfg(feature = "legacy-globals")]
|
||||||
use sysinfo::{RefreshKind, System};
|
use sysinfo::{RefreshKind, System};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
/* Process-owned daemon state and TUI-local state must be separate because IPC,
|
||||||
|
* rather than shared memory, is the boundary between the two binaries. */
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DaemonState {
|
||||||
|
pub app: Arc<Mutex<AppState>>,
|
||||||
|
pub shutdown: Arc<RwLock<bool>>,
|
||||||
|
pub reload: Arc<RwLock<bool>>,
|
||||||
|
pub active_tasks: Arc<DashSet<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DaemonState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
app: Arc::new(Mutex::new(AppState::new())),
|
||||||
|
shutdown: Arc::new(RwLock::new(false)),
|
||||||
|
reload: Arc::new(RwLock::new(false)),
|
||||||
|
active_tasks: Arc::new(DashSet::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DaemonState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The TUI keeps only the daemon data it renders. This state is never shared
|
||||||
|
* with the daemon and is populated from daemon IPC messages. */
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ClientState {
|
||||||
|
pub app: Arc<Mutex<AppState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
app: Arc::new(Mutex::new(AppState::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ClientState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const MAX_POINTS: usize = 1000;
|
pub const MAX_POINTS: usize = 1000;
|
||||||
pub const MAX_LOGS: usize = 100;
|
pub const MAX_LOGS: usize = 100;
|
||||||
|
|
||||||
|
|
@ -148,13 +202,22 @@ impl AppState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "legacy-globals")]
|
||||||
|
#[deprecated(note = "use DaemonState or ClientState")]
|
||||||
pub static APP_STATE: LazyLock<Arc<Mutex<AppState>>> =
|
pub static APP_STATE: LazyLock<Arc<Mutex<AppState>>> =
|
||||||
LazyLock::new(|| Arc::new(Mutex::new(AppState::new())));
|
LazyLock::new(|| Arc::new(Mutex::new(AppState::new())));
|
||||||
|
|
||||||
|
#[cfg(feature = "legacy-globals")]
|
||||||
|
#[deprecated(note = "use DaemonState")]
|
||||||
pub static SHUTDOWN: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(false));
|
pub static SHUTDOWN: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(false));
|
||||||
|
#[cfg(feature = "legacy-globals")]
|
||||||
|
#[deprecated(note = "use DaemonState")]
|
||||||
pub static RELOAD: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(true));
|
pub static RELOAD: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(true));
|
||||||
|
#[cfg(feature = "legacy-globals")]
|
||||||
|
#[deprecated(note = "use DaemonState")]
|
||||||
pub static ACTIVE_TASKS: Lazy<DashSet<String>> = Lazy::new(|| DashSet::new());
|
pub static ACTIVE_TASKS: Lazy<DashSet<String>> = Lazy::new(|| DashSet::new());
|
||||||
|
|
||||||
|
#[cfg(feature = "legacy-globals")]
|
||||||
pub fn setup() {
|
pub fn setup() {
|
||||||
ACTIVE_TASKS.insert("System info loader".to_string());
|
ACTIVE_TASKS.insert("System info loader".to_string());
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,12 @@ pub fn decrypt_challenge(encrypted: &str, keyring: &Keyring) -> Result<String, S
|
||||||
String::from_utf8(pt).map_err(|e| format!("utf8 decode error: {}", e))
|
String::from_utf8(pt).map_err(|e| format!("utf8 decode error: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn export(data: &[u8], format: DataFormat) -> String {
|
pub fn export(data: &[u8], format: DataFormat) -> Result<String, String> {
|
||||||
match format {
|
match format {
|
||||||
DataFormat::Raw => String::from_utf8_lossy(data).to_string(),
|
DataFormat::Raw => {
|
||||||
DataFormat::Base64 => STANDARD.encode(data),
|
String::from_utf8(data.to_vec()).map_err(|error| format!("utf8 decode error: {error}"))
|
||||||
DataFormat::Hex => hex::encode(data),
|
}
|
||||||
|
DataFormat::Base64 => Ok(STANDARD.encode(data)),
|
||||||
|
DataFormat::Hex => Ok(hex::encode(data)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
iota/Cargo.toml
Normal file
8
iota/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "iota"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iota-cli = { path = "../iota-cli" }
|
||||||
|
tokio = { version = "1.50.0", features = ["full"] }
|
||||||
29
iota/src/main.rs
Normal file
29
iota/src/main.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
use iota_cli::{ipc_client::IpcClient, screens::main_screen::MainScreen, ui::start_tui};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn socket_path() -> PathBuf {
|
||||||
|
std::env::var_os("IOTA_SOCKET")
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|| PathBuf::from("/run/iota/iota.sock"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
|
async fn main() {
|
||||||
|
let path = socket_path();
|
||||||
|
let ipc = match IpcClient::connect(&path).await {
|
||||||
|
Ok(client) => client,
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!(
|
||||||
|
"Cannot connect to iota-daemon at {}: {error}",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let ui = start_tui(ipc);
|
||||||
|
ui.set_screen(Box::new(MainScreen::new(ui.clone()).await))
|
||||||
|
.await;
|
||||||
|
while !ui.is_shutdown() {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
systemd/iota-daemon.service
Normal file
16
systemd/iota-daemon.service
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Tensamin Iota daemon
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
Requires=iota-daemon.socket
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/iota-daemon
|
||||||
|
Restart=on-failure
|
||||||
|
RuntimeDirectory=iota
|
||||||
|
RuntimeDirectoryMode=0750
|
||||||
|
Environment=IOTA_SOCKET=/run/iota/iota.sock
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
12
systemd/iota-daemon.socket
Normal file
12
systemd/iota-daemon.socket
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Tensamin Iota daemon IPC socket
|
||||||
|
|
||||||
|
[Socket]
|
||||||
|
ListenStream=/run/iota/iota.sock
|
||||||
|
SocketMode=0660
|
||||||
|
SocketUser=iota
|
||||||
|
SocketGroup=iota
|
||||||
|
RemoveOnStop=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sockets.target
|
||||||
Loading…
Reference in a new issue