mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
better tui
This commit is contained in:
3
Makefile
3
Makefile
@@ -140,6 +140,9 @@ install-tunnel: core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox core
|
|||||||
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
||||||
$(call cp,core/startos/start-tunneld.service,$(DESTDIR)/lib/systemd/system/start-tunneld.service)
|
$(call cp,core/startos/start-tunneld.service,$(DESTDIR)/lib/systemd/system/start-tunneld.service)
|
||||||
|
|
||||||
|
$(call mkdir,$(DESTDIR)/usr/lib/startos/scripts)
|
||||||
|
$(call cp,build/lib/scripts/forward-port,$(DESTDIR)/usr/lib/startos/scripts/forward-port)
|
||||||
|
|
||||||
core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox: $(CORE_SRC) $(ENVIRONMENT_FILE) web/dist/static/start-tunnel/index.html
|
core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox: $(CORE_SRC) $(ENVIRONMENT_FILE) web/dist/static/start-tunnel/index.html
|
||||||
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-tunnelbox.sh
|
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-tunnelbox.sh
|
||||||
|
|
||||||
|
|||||||
1226
core/Cargo.lock
generated
1226
core/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
|
edition = "2021"
|
||||||
name = "models"
|
name = "models"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@@ -14,14 +14,15 @@ axum = "0.8.4"
|
|||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
ed25519-dalek = { version = "2.0.0", features = ["serde"] }
|
ed25519-dalek = { version = "2.0.0", features = ["serde"] }
|
||||||
gpt = "4.1.0"
|
|
||||||
lazy_static = "1.4"
|
|
||||||
lettre = { version = "0.11", default-features = false }
|
|
||||||
mbrman = "0.6.0"
|
|
||||||
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
|
gpt = "4.1.0"
|
||||||
ipnet = "2.8.0"
|
ipnet = "2.8.0"
|
||||||
|
lazy_static = "1.4"
|
||||||
|
lettre = { version = "0.11", default-features = false }
|
||||||
|
mbrman = "0.6.0"
|
||||||
|
miette = "7.6.0"
|
||||||
num_enum = "0.7.1"
|
num_enum = "0.7.1"
|
||||||
openssl = { version = "0.10.57", features = ["vendored"] }
|
openssl = { version = "0.10.57", features = ["vendored"] }
|
||||||
patch-db = { version = "*", path = "../../patch-db/patch-db", features = [
|
patch-db = { version = "*", path = "../../patch-db/patch-db", features = [
|
||||||
@@ -35,11 +36,11 @@ rustls = "0.23"
|
|||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
ssh-key = "0.6.2"
|
ssh-key = "0.6.2"
|
||||||
ts-rs = "9"
|
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
torut = "0.2.1"
|
torut = "0.2.1"
|
||||||
tracing = "0.1.39"
|
tracing = "0.1.39"
|
||||||
|
ts-rs = "9"
|
||||||
typeid = "1"
|
typeid = "1"
|
||||||
yasi = { version = "0.1.6", features = ["serde", "ts-rs"] }
|
yasi = { version = "0.1.6", features = ["serde", "ts-rs"] }
|
||||||
zbus = "5"
|
zbus = "5"
|
||||||
|
|||||||
@@ -4,18 +4,18 @@ description = "The core of StartOS"
|
|||||||
documentation = "https://docs.rs/start-os"
|
documentation = "https://docs.rs/start-os"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
keywords = [
|
keywords = [
|
||||||
"self-hosted",
|
|
||||||
"raspberry-pi",
|
|
||||||
"privacy",
|
|
||||||
"bitcoin",
|
"bitcoin",
|
||||||
"full-node",
|
"full-node",
|
||||||
"lightning",
|
"lightning",
|
||||||
|
"privacy",
|
||||||
|
"raspberry-pi",
|
||||||
|
"self-hosted",
|
||||||
]
|
]
|
||||||
|
license = "MIT"
|
||||||
name = "start-os"
|
name = "start-os"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/Start9Labs/start-os"
|
repository = "https://github.com/Start9Labs/start-os"
|
||||||
version = "0.4.0-alpha.12" # VERSION_BUMP
|
version = "0.4.0-alpha.12" # VERSION_BUMP
|
||||||
license = "MIT"
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "startos"
|
name = "startos"
|
||||||
@@ -44,58 +44,58 @@ path = "src/main.rs"
|
|||||||
[features]
|
[features]
|
||||||
arti = [
|
arti = [
|
||||||
"arti-client",
|
"arti-client",
|
||||||
"tor-rtcompat",
|
|
||||||
"tor-keymgr",
|
|
||||||
"tor-proto",
|
|
||||||
"tor-hscrypto",
|
|
||||||
"tor-llcrypto",
|
|
||||||
"tor-cell",
|
|
||||||
"tor-hsservice",
|
|
||||||
"safelog",
|
|
||||||
"models/arti",
|
"models/arti",
|
||||||
|
"safelog",
|
||||||
|
"tor-cell",
|
||||||
|
"tor-hscrypto",
|
||||||
|
"tor-hsservice",
|
||||||
|
"tor-keymgr",
|
||||||
|
"tor-llcrypto",
|
||||||
|
"tor-proto",
|
||||||
|
"tor-rtcompat",
|
||||||
]
|
]
|
||||||
cli = ["cli-startd", "cli-registry", "cli-tunnel"]
|
cli = ["cli-registry", "cli-startd", "cli-tunnel"]
|
||||||
cli-container = ["procfs", "pty-process"]
|
cli-container = ["procfs", "pty-process"]
|
||||||
cli-registry = []
|
cli-registry = []
|
||||||
cli-startd = []
|
cli-startd = []
|
||||||
cli-tunnel = []
|
cli-tunnel = []
|
||||||
default = ["cli", "startd", "registry", "cli-container", "tunnel"]
|
console = ["console-subscriber", "tokio/tracing"]
|
||||||
|
default = ["cli", "cli-container", "registry", "startd", "tunnel"]
|
||||||
dev = ["backtrace-on-stack-overflow"]
|
dev = ["backtrace-on-stack-overflow"]
|
||||||
docker = []
|
docker = []
|
||||||
registry = []
|
registry = []
|
||||||
startd = []
|
startd = []
|
||||||
test = []
|
test = []
|
||||||
tunnel = []
|
tunnel = []
|
||||||
console = ["console-subscriber", "tokio/tracing"]
|
|
||||||
unstable = ["backtrace-on-stack-overflow"]
|
unstable = ["backtrace-on-stack-overflow"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
aes = { version = "0.7.5", features = ["ctr"] }
|
||||||
arti-client = { version = "0.33", features = [
|
arti-client = { version = "0.33", features = [
|
||||||
"compression",
|
"compression",
|
||||||
|
"ephemeral-keystore",
|
||||||
"experimental-api",
|
"experimental-api",
|
||||||
|
"onion-service-client",
|
||||||
|
"onion-service-service",
|
||||||
"rustls",
|
"rustls",
|
||||||
"static",
|
"static",
|
||||||
"tokio",
|
"tokio",
|
||||||
"ephemeral-keystore",
|
|
||||||
"onion-service-client",
|
|
||||||
"onion-service-service",
|
|
||||||
], default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
], default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
aes = { version = "0.7.5", features = ["ctr"] }
|
|
||||||
async-acme = { version = "0.6.0", git = "https://github.com/dr-bonez/async-acme.git", features = [
|
async-acme = { version = "0.6.0", git = "https://github.com/dr-bonez/async-acme.git", features = [
|
||||||
"use_rustls",
|
"use_rustls",
|
||||||
"use_tokio",
|
"use_tokio",
|
||||||
] }
|
] }
|
||||||
async-compression = { version = "0.4.32", features = [
|
async-compression = { version = "0.4.32", features = [
|
||||||
"gzip",
|
|
||||||
"brotli",
|
"brotli",
|
||||||
"zstd",
|
"gzip",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"zstd",
|
||||||
] }
|
] }
|
||||||
async-stream = "0.3.5"
|
async-stream = "0.3.5"
|
||||||
async-trait = "0.1.74"
|
async-trait = "0.1.74"
|
||||||
axum = { version = "0.8.4", features = ["ws"] }
|
axum = { version = "0.8.4", features = ["ws"] }
|
||||||
barrage = "0.2.3"
|
|
||||||
backtrace-on-stack-overflow = { version = "0.3.0", optional = true }
|
backtrace-on-stack-overflow = { version = "0.3.0", optional = true }
|
||||||
|
barrage = "0.2.3"
|
||||||
base32 = "0.5.0"
|
base32 = "0.5.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
base64ct = "1.6.0"
|
base64ct = "1.6.0"
|
||||||
@@ -115,14 +115,14 @@ der = { version = "0.7.9", features = ["derive", "pem"] }
|
|||||||
digest = "0.10.7"
|
digest = "0.10.7"
|
||||||
divrem = "1.0.0"
|
divrem = "1.0.0"
|
||||||
dns-lookup = "2.1.0"
|
dns-lookup = "2.1.0"
|
||||||
ed25519 = { version = "2.2.3", features = ["pkcs8", "pem", "alloc"] }
|
ed25519 = { version = "2.2.3", features = ["alloc", "pem", "pkcs8"] }
|
||||||
ed25519-dalek = { version = "2.2.0", features = [
|
ed25519-dalek = { version = "2.2.0", features = [
|
||||||
|
"digest",
|
||||||
|
"hazmat",
|
||||||
|
"pkcs8",
|
||||||
|
"rand_core",
|
||||||
"serde",
|
"serde",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
"rand_core",
|
|
||||||
"digest",
|
|
||||||
"pkcs8",
|
|
||||||
"hazmat",
|
|
||||||
] }
|
] }
|
||||||
ed25519-dalek-v1 = { package = "ed25519-dalek", version = "1" }
|
ed25519-dalek-v1 = { package = "ed25519-dalek", version = "1" }
|
||||||
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
||||||
@@ -139,14 +139,14 @@ hickory-server = "0.25.2"
|
|||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
http = "1.0.0"
|
http = "1.0.0"
|
||||||
http-body-util = "0.1"
|
http-body-util = "0.1"
|
||||||
hyper = { version = "1.5", features = ["server", "http1", "http2"] }
|
hyper = { version = "1.5", features = ["http1", "http2", "server"] }
|
||||||
hyper-util = { version = "0.1.10", features = [
|
hyper-util = { version = "0.1.10", features = [
|
||||||
|
"http1",
|
||||||
|
"http2",
|
||||||
"server",
|
"server",
|
||||||
"server-auto",
|
"server-auto",
|
||||||
"server-graceful",
|
"server-graceful",
|
||||||
"service",
|
"service",
|
||||||
"http1",
|
|
||||||
"http2",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
] }
|
] }
|
||||||
id-pool = { version = "0.2.2", default-features = false, features = [
|
id-pool = { version = "0.2.2", default-features = false, features = [
|
||||||
@@ -172,18 +172,19 @@ lazy_async_pool = "0.3.3"
|
|||||||
lazy_format = "2.0"
|
lazy_format = "2.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
lettre = { version = "0.11.18", default-features = false, features = [
|
lettre = { version = "0.11.18", default-features = false, features = [
|
||||||
"smtp-transport",
|
|
||||||
"pool",
|
|
||||||
"hostname",
|
|
||||||
"builder",
|
|
||||||
"tokio1-rustls",
|
|
||||||
"rustls-platform-verifier",
|
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
|
"builder",
|
||||||
|
"hostname",
|
||||||
|
"pool",
|
||||||
|
"rustls-platform-verifier",
|
||||||
|
"smtp-transport",
|
||||||
|
"tokio1-rustls",
|
||||||
] }
|
] }
|
||||||
libc = "0.2.149"
|
libc = "0.2.149"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
mio = "1"
|
|
||||||
mbrman = "0.6.0"
|
mbrman = "0.6.0"
|
||||||
|
miette = { version = "7.6.0", features = ["fancy"] }
|
||||||
|
mio = "1"
|
||||||
models = { version = "*", path = "../models" }
|
models = { version = "*", path = "../models" }
|
||||||
new_mime_guess = "4"
|
new_mime_guess = "4"
|
||||||
nix = { version = "0.30.1", features = [
|
nix = { version = "0.30.1", features = [
|
||||||
@@ -197,8 +198,8 @@ nix = { version = "0.30.1", features = [
|
|||||||
] }
|
] }
|
||||||
nom = "8.0.0"
|
nom = "8.0.0"
|
||||||
num = "0.4.1"
|
num = "0.4.1"
|
||||||
num_enum = "0.7.0"
|
|
||||||
num_cpus = "1.16.0"
|
num_cpus = "1.16.0"
|
||||||
|
num_enum = "0.7.0"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
openssh-keys = "0.6.2"
|
openssh-keys = "0.6.2"
|
||||||
openssl = { version = "0.10.57", features = ["vendored"] }
|
openssl = { version = "0.10.57", features = ["vendored"] }
|
||||||
@@ -215,14 +216,14 @@ proptest = "1.3.1"
|
|||||||
proptest-derive = "0.5.0"
|
proptest-derive = "0.5.0"
|
||||||
pty-process = { version = "0.5.1", optional = true }
|
pty-process = { version = "0.5.1", optional = true }
|
||||||
qrcode = "0.14.1"
|
qrcode = "0.14.1"
|
||||||
|
r3bl_tui = "0.7.6"
|
||||||
rand = "0.9.2"
|
rand = "0.9.2"
|
||||||
regex = "1.10.2"
|
regex = "1.10.2"
|
||||||
reqwest = { version = "0.12.4", features = ["stream", "json", "socks"] }
|
reqwest = { version = "0.12.4", features = ["json", "socks", "stream"] }
|
||||||
reqwest_cookie_store = "0.8.0"
|
reqwest_cookie_store = "0.8.0"
|
||||||
rpassword = "7.2.0"
|
rpassword = "7.2.0"
|
||||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" }
|
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" }
|
||||||
rust-argon2 = "2.0.0"
|
rust-argon2 = "2.0.0"
|
||||||
rustyline-async = "0.4.7"
|
|
||||||
safelog = { version = "0.4.8", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
safelog = { version = "0.4.8", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
semver = { version = "1.0.20", features = ["serde"] }
|
semver = { version = "1.0.20", features = ["serde"] }
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
@@ -230,7 +231,7 @@ serde_cbor = { package = "ciborium", version = "0.2.1" }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_toml = { package = "toml", version = "0.8.2" }
|
serde_toml = { package = "toml", version = "0.8.2" }
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
serde_with = { version = "3.4.0", features = ["macros", "json"] }
|
serde_with = { version = "3.4.0", features = ["json", "macros"] }
|
||||||
serde_yaml = { package = "serde_yml", version = "0.0.12" }
|
serde_yaml = { package = "serde_yml", version = "0.0.12" }
|
||||||
sha-crypt = "0.5.0"
|
sha-crypt = "0.5.0"
|
||||||
sha2 = "0.10.2"
|
sha2 = "0.10.2"
|
||||||
@@ -238,20 +239,20 @@ shell-words = "1"
|
|||||||
signal-hook = "0.3.17"
|
signal-hook = "0.3.17"
|
||||||
simple-logging = "2.0.2"
|
simple-logging = "2.0.2"
|
||||||
socket2 = { version = "0.6.0", features = ["all"] }
|
socket2 = { version = "0.6.0", features = ["all"] }
|
||||||
socks5-impl = { version = "0.7.2", features = ["server", "client"] }
|
socks5-impl = { version = "0.7.2", features = ["client", "server"] }
|
||||||
sqlx = { version = "0.8.6", features = [
|
sqlx = { version = "0.8.6", features = [
|
||||||
"runtime-tokio-rustls",
|
|
||||||
"postgres",
|
"postgres",
|
||||||
|
"runtime-tokio-rustls",
|
||||||
], default-features = false }
|
], default-features = false }
|
||||||
sscanf = "0.4.1"
|
sscanf = "0.4.1"
|
||||||
ssh-key = { version = "0.6.2", features = ["ed25519"] }
|
ssh-key = { version = "0.6.2", features = ["ed25519"] }
|
||||||
tar = "0.4.40"
|
tar = "0.4.40"
|
||||||
termion = "4.0.5"
|
termion = "4.0.5"
|
||||||
thiserror = "2.0.12"
|
|
||||||
textwrap = "0.16.1"
|
textwrap = "0.16.1"
|
||||||
|
thiserror = "2.0.12"
|
||||||
tokio = { version = "1.38.1", features = ["full"] }
|
tokio = { version = "1.38.1", features = ["full"] }
|
||||||
tokio-rustls = "0.26.0"
|
tokio-rustls = "0.26.0"
|
||||||
tokio-stream = { version = "0.1.14", features = ["io-util", "sync", "net"] }
|
tokio-stream = { version = "0.1.14", features = ["io-util", "net", "sync"] }
|
||||||
tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" }
|
tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" }
|
||||||
tokio-tungstenite = { version = "0.26.2", features = ["native-tls", "url"] }
|
tokio-tungstenite = { version = "0.26.2", features = ["native-tls", "url"] }
|
||||||
tokio-util = { version = "0.7.9", features = ["io"] }
|
tokio-util = { version = "0.7.9", features = ["io"] }
|
||||||
@@ -268,8 +269,8 @@ tor-llcrypto = { version = "0.33", features = [
|
|||||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
tor-proto = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
tor-proto = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
tor-rtcompat = { version = "0.33", features = [
|
tor-rtcompat = { version = "0.33", features = [
|
||||||
"tokio",
|
|
||||||
"rustls",
|
"rustls",
|
||||||
|
"tokio",
|
||||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
torut = "0.2.1"
|
torut = "0.2.1"
|
||||||
tower-service = "0.3.3"
|
tower-service = "0.3.3"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use clap::builder::ValueParserFactory;
|
|||||||
use clap::{CommandFactory, FromArgMatches, Parser, value_parser};
|
use clap::{CommandFactory, FromArgMatches, Parser, value_parser};
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use exver::VersionRange;
|
use exver::VersionRange;
|
||||||
use futures::{AsyncWriteExt, StreamExt};
|
use futures::StreamExt;
|
||||||
use imbl_value::{InternedString, json};
|
use imbl_value::{InternedString, json};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::{FromStrParser, VersionString};
|
use models::{FromStrParser, VersionString};
|
||||||
@@ -15,7 +15,6 @@ use reqwest::Url;
|
|||||||
use reqwest::header::{CONTENT_LENGTH, HeaderMap};
|
use reqwest::header::{CONTENT_LENGTH, HeaderMap};
|
||||||
use rpc_toolkit::HandlerArgs;
|
use rpc_toolkit::HandlerArgs;
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rustyline_async::ReadlineEvent;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
|
use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
|
||||||
@@ -34,6 +33,7 @@ use crate::upload::upload;
|
|||||||
use crate::util::Never;
|
use crate::util::Never;
|
||||||
use crate::util::io::open_file;
|
use crate::util::io::open_file;
|
||||||
use crate::util::net::WebSocketExt;
|
use crate::util::net::WebSocketExt;
|
||||||
|
use crate::util::tui::choose;
|
||||||
|
|
||||||
pub const PKG_ARCHIVE_DIR: &str = "package-data/archive";
|
pub const PKG_ARCHIVE_DIR: &str = "package-data/archive";
|
||||||
pub const PKG_PUBLIC_DIR: &str = "package-data/public";
|
pub const PKG_PUBLIC_DIR: &str = "package-data/public";
|
||||||
@@ -483,47 +483,19 @@ pub async fn cli_install(
|
|||||||
let version = if packages.best.len() == 1 {
|
let version = if packages.best.len() == 1 {
|
||||||
packages.best.pop_first().map(|(k, _)| k).unwrap()
|
packages.best.pop_first().map(|(k, _)| k).unwrap()
|
||||||
} else {
|
} else {
|
||||||
println!(
|
let versions = packages.best.keys().collect::<Vec<_>>();
|
||||||
"Multiple flavors of {id} found. Please select one of the following versions to install:"
|
let version = choose(
|
||||||
);
|
&format!(
|
||||||
let version;
|
concat!(
|
||||||
loop {
|
"Multiple flavors of {id} found. ",
|
||||||
let (mut read, mut output) = rustyline_async::Readline::new("> ".into())
|
"Please select one of the following versions to install:"
|
||||||
.with_kind(ErrorKind::Filesystem)?;
|
),
|
||||||
for (idx, version) in packages.best.keys().enumerate() {
|
id = id
|
||||||
output
|
),
|
||||||
.write_all(format!(" {}) {}\n", idx + 1, version).as_bytes())
|
&versions,
|
||||||
.await?;
|
)
|
||||||
read.add_history_entry(version.to_string());
|
.await?;
|
||||||
}
|
(*version).clone()
|
||||||
if let ReadlineEvent::Line(line) = read.readline().await? {
|
|
||||||
let trimmed = line.trim();
|
|
||||||
match trimmed.parse() {
|
|
||||||
Ok(v) => {
|
|
||||||
if let Some((k, _)) = packages.best.remove_entry(&v) {
|
|
||||||
version = k;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => match trimmed.parse::<usize>() {
|
|
||||||
Ok(i) if (1..=packages.best.len()).contains(&i) => {
|
|
||||||
version = packages.best.keys().nth(i - 1).unwrap().clone();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
eprintln!("invalid selection: {trimmed}");
|
|
||||||
println!("Please select one of the following versions to install:");
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
eyre!("Could not determine precise version to install"),
|
|
||||||
ErrorKind::InvalidRequest,
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
version
|
|
||||||
};
|
};
|
||||||
ctx.call_remote::<RpcContext>(
|
ctx.call_remote::<RpcContext>(
|
||||||
&method.join("."),
|
&method.join("."),
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ use imbl_value::Value;
|
|||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{
|
use rpc_toolkit::{
|
||||||
CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler, from_fn, from_fn_async,
|
CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler, from_fn, from_fn_async,
|
||||||
from_fn_blocking,
|
from_fn_async_local, from_fn_blocking,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
@@ -392,7 +392,7 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
|||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"install",
|
"install",
|
||||||
from_fn_async(install::cli_install)
|
from_fn_async_local(install::cli_install)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Install a package from a marketplace or via sideloading"),
|
.with_about("Install a package from a marketplace or via sideloading"),
|
||||||
)
|
)
|
||||||
@@ -513,13 +513,6 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
|||||||
backup::package_backup::<C>()
|
backup::package_backup::<C>()
|
||||||
.with_about("Commands for restoring package(s) from backup"),
|
.with_about("Commands for restoring package(s) from backup"),
|
||||||
)
|
)
|
||||||
.subcommand("connect", from_fn_async(service::connect_rpc).no_cli())
|
|
||||||
.subcommand(
|
|
||||||
"connect",
|
|
||||||
from_fn_async(service::connect_rpc_cli)
|
|
||||||
.no_display()
|
|
||||||
.with_about("Connect to a LXC container"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"attach",
|
"attach",
|
||||||
from_fn_async(service::attach)
|
from_fn_async(service::attach)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use imbl_value::{InOMap, InternedString};
|
|||||||
use models::{FromStrParser, InvalidId, PackageId};
|
use models::{FromStrParser, InvalidId, PackageId};
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{GenericRpcMethod, RpcRequest, RpcResponse};
|
use rpc_toolkit::{GenericRpcMethod, RpcRequest, RpcResponse};
|
||||||
use rustyline_async::{ReadlineEvent, SharedWriter};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
@@ -470,115 +469,6 @@ pub async fn connect(ctx: &RpcContext, container: &LxcContainer) -> Result<Guid,
|
|||||||
Ok(guid)
|
Ok(guid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn connect_cli(ctx: &CliContext, guid: Guid) -> Result<(), Error> {
|
|
||||||
use futures::SinkExt;
|
|
||||||
use tokio_tungstenite::tungstenite::Message;
|
|
||||||
|
|
||||||
let mut ws = ctx.ws_continuation(guid).await?;
|
|
||||||
let (mut input, mut output) =
|
|
||||||
rustyline_async::Readline::new("> ".into()).with_kind(ErrorKind::Filesystem)?;
|
|
||||||
|
|
||||||
async fn handle_message(
|
|
||||||
msg: Option<Result<Message, tokio_tungstenite::tungstenite::Error>>,
|
|
||||||
output: &mut SharedWriter,
|
|
||||||
) -> Result<bool, Error> {
|
|
||||||
match msg {
|
|
||||||
None => return Ok(true),
|
|
||||||
Some(Ok(Message::Text(txt))) => match serde_json::from_str::<RpcResponse>(&txt) {
|
|
||||||
Ok(RpcResponse { result: Ok(a), .. }) => {
|
|
||||||
output
|
|
||||||
.write_all(
|
|
||||||
(serde_json::to_string(&a).with_kind(ErrorKind::Serialization)? + "\n")
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
Ok(RpcResponse { result: Err(e), .. }) => {
|
|
||||||
let e: Error = e.into();
|
|
||||||
tracing::error!("{e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Error Parsing RPC response: {e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(Ok(_)) => (),
|
|
||||||
Some(Err(e)) => {
|
|
||||||
return Err(Error::new(e, ErrorKind::Network));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
line = input.readline() => {
|
|
||||||
let line = line.with_kind(ErrorKind::Filesystem)?;
|
|
||||||
if let ReadlineEvent::Line(line) = line {
|
|
||||||
input.add_history_entry(line.clone());
|
|
||||||
if serde_json::from_str::<RpcRequest>(&line).is_ok() {
|
|
||||||
ws.send(Message::Text(line.into()))
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Network)?;
|
|
||||||
} else {
|
|
||||||
match shell_words::split(&line) {
|
|
||||||
Ok(command) => {
|
|
||||||
if let Some((method, rest)) = command.split_first() {
|
|
||||||
let mut params = InOMap::new();
|
|
||||||
for arg in rest {
|
|
||||||
if let Some((name, value)) = arg.split_once('=') {
|
|
||||||
params.insert(InternedString::intern(name), if value.is_empty() {
|
|
||||||
Value::Null
|
|
||||||
} else if let Ok(v) = serde_json::from_str(value) {
|
|
||||||
v
|
|
||||||
} else {
|
|
||||||
Value::String(Arc::new(value.into()))
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
tracing::error!("argument without a value: {arg}");
|
|
||||||
tracing::debug!("help: set the value of {arg} with `{arg}=...`");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ws.send(Message::Text(match serde_json::to_string(&RpcRequest {
|
|
||||||
id: None,
|
|
||||||
method: GenericRpcMethod::new(method.into()),
|
|
||||||
params: Value::Object(params),
|
|
||||||
}) {
|
|
||||||
Ok(a) => a.into(),
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Error Serializing Request: {e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
})).await.with_kind(ErrorKind::Network)?;
|
|
||||||
if handle_message(ws.next().await, &mut output).await? {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("{e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ws.send(Message::Close(None)).await.with_kind(ErrorKind::Network)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msg = ws.next() => {
|
|
||||||
if handle_message(msg, &mut output).await? {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn stats(ctx: RpcContext) -> Result<BTreeMap<PackageId, Option<ServiceStats>>, Error> {
|
pub async fn stats(ctx: RpcContext) -> Result<BTreeMap<PackageId, Option<ServiceStats>>, Error> {
|
||||||
let ids = ctx.db.peek().await.as_public().as_package_data().keys()?;
|
let ids = ctx.db.peek().await.as_public().as_package_data().keys()?;
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use ts_rs::TS;
|
|||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType};
|
use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType};
|
||||||
|
use crate::net::host::all_hosts;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::util::io::{TmpDir, write_file_atomic};
|
use crate::util::io::{TmpDir, write_file_atomic};
|
||||||
@@ -132,7 +133,37 @@ pub async fn remove_tunnel(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.db
|
||||||
|
.mutate(|db| {
|
||||||
|
for host in all_hosts(db) {
|
||||||
|
let host = host?;
|
||||||
|
host.as_public_domains_mut()
|
||||||
|
.mutate(|p| Ok(p.retain(|_, v| v.gateway != id)))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
|
|
||||||
ctx.net_controller.net_iface.delete_iface(&id).await?;
|
ctx.net_controller.net_iface.delete_iface(&id).await?;
|
||||||
|
|
||||||
|
ctx.db
|
||||||
|
.mutate(|db| {
|
||||||
|
for host in all_hosts(db) {
|
||||||
|
let host = host?;
|
||||||
|
host.as_bindings_mut().mutate(|b| {
|
||||||
|
Ok(b.values_mut().for_each(|v| {
|
||||||
|
v.net.private_disabled.remove(&id);
|
||||||
|
v.net.public_enabled.remove(&id);
|
||||||
|
}))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use itertools::Itertools;
|
|||||||
use models::{ActionId, HostId, ImageId, PackageId};
|
use models::{ActionId, HostId, ImageId, PackageId};
|
||||||
use nix::sys::signal::Signal;
|
use nix::sys::signal::Signal;
|
||||||
use persistent_container::{PersistentContainer, Subcontainer};
|
use persistent_container::{PersistentContainer, Subcontainer};
|
||||||
use rpc_toolkit::{CallRemoteHandler, Empty, HandlerArgs, HandlerFor, from_fn_async};
|
use rpc_toolkit::{HandlerArgs, HandlerFor};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use service_actor::ServiceActor;
|
use service_actor::ServiceActor;
|
||||||
use start_stop::StartStop;
|
use start_stop::StartStop;
|
||||||
@@ -707,57 +707,6 @@ pub async fn rebuild(ctx: RpcContext, RebuildParams { id }: RebuildParams) -> Re
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
|
||||||
pub struct ConnectParams {
|
|
||||||
pub id: PackageId,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect_rpc(
|
|
||||||
ctx: RpcContext,
|
|
||||||
ConnectParams { id }: ConnectParams,
|
|
||||||
) -> Result<Guid, Error> {
|
|
||||||
let id_ref = &id;
|
|
||||||
crate::lxc::connect(
|
|
||||||
&ctx,
|
|
||||||
ctx.services
|
|
||||||
.get(&id)
|
|
||||||
.await
|
|
||||||
.as_ref()
|
|
||||||
.or_not_found(lazy_format!("service for {id_ref}"))?
|
|
||||||
.seed
|
|
||||||
.persistent_container
|
|
||||||
.lxc_container
|
|
||||||
.get()
|
|
||||||
.or_not_found(lazy_format!("container for {id_ref}"))?,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect_rpc_cli(
|
|
||||||
HandlerArgs {
|
|
||||||
context,
|
|
||||||
parent_method,
|
|
||||||
method,
|
|
||||||
params,
|
|
||||||
inherited_params,
|
|
||||||
raw_params,
|
|
||||||
}: HandlerArgs<CliContext, ConnectParams>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let ctx = context.clone();
|
|
||||||
let guid = CallRemoteHandler::<CliContext, _, _>::new(from_fn_async(connect_rpc))
|
|
||||||
.handle_async(HandlerArgs {
|
|
||||||
context,
|
|
||||||
parent_method,
|
|
||||||
method,
|
|
||||||
params: rpc_toolkit::util::Flat(params, Empty {}),
|
|
||||||
inherited_params,
|
|
||||||
raw_params,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
crate::lxc::connect_cli(&ctx, guid).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, TS)]
|
#[derive(Deserialize, Serialize, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AttachParams {
|
pub struct AttachParams {
|
||||||
|
|||||||
@@ -366,15 +366,16 @@ pub async fn add_forward(
|
|||||||
ctx: TunnelContext,
|
ctx: TunnelContext,
|
||||||
AddPortForwardParams { source, target }: AddPortForwardParams,
|
AddPortForwardParams { source, target }: AddPortForwardParams,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let rc = ctx.forward.add_forward(source, target).await?;
|
||||||
|
ctx.active_forwards.mutate(|m| {
|
||||||
|
m.insert(source, rc);
|
||||||
|
});
|
||||||
|
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| db.as_port_forwards_mut().insert(&source, &target))
|
.mutate(|db| db.as_port_forwards_mut().insert(&source, &target))
|
||||||
.await
|
.await
|
||||||
.result?;
|
.result?;
|
||||||
|
|
||||||
let rc = ctx.forward.add_forward(source, target).await?;
|
|
||||||
ctx.active_forwards.mutate(|m| {
|
|
||||||
m.insert(source, rc);
|
|
||||||
});
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,6 +141,12 @@ pub fn auth_api<C: Context>() -> ParentHandler<C> {
|
|||||||
.with_about("Set user interface password")
|
.with_about("Set user interface password")
|
||||||
.no_display(),
|
.no_display(),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
"reset-password",
|
||||||
|
from_fn_async(reset_password)
|
||||||
|
.with_about("Reset user interface password")
|
||||||
|
.no_display(),
|
||||||
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"key",
|
"key",
|
||||||
ParentHandler::<C>::new()
|
ParentHandler::<C>::new()
|
||||||
@@ -286,3 +292,32 @@ pub async fn set_password_cli(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn reset_password(
|
||||||
|
HandlerArgs {
|
||||||
|
context,
|
||||||
|
parent_method,
|
||||||
|
method,
|
||||||
|
..
|
||||||
|
}: HandlerArgs<CliContext>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
println!("Generating a random password...");
|
||||||
|
let params = SetPasswordParams {
|
||||||
|
password: base32::encode(
|
||||||
|
base32::Alphabet::Rfc4648Lower { padding: false },
|
||||||
|
&rand::random::<[u8; 10]>(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
context
|
||||||
|
.call_remote::<TunnelContext>(
|
||||||
|
&parent_method.iter().chain(method.iter()).join("."),
|
||||||
|
to_value(¶ms)?,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("Your new password is:");
|
||||||
|
println!("{}", params.password);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use axum::extract::ws;
|
use axum::extract::ws;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use clap::builder::ValueParserFactory;
|
|
||||||
use imbl::{HashMap, OrdMap};
|
use imbl::{HashMap, OrdMap};
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::{FromStrParser, GatewayId};
|
use models::GatewayId;
|
||||||
use patch_db::Dump;
|
use patch_db::Dump;
|
||||||
use patch_db::json_ptr::{JsonPointer, ROOT};
|
use patch_db::json_ptr::{JsonPointer, ROOT};
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
@@ -31,20 +30,27 @@ use crate::tunnel::wg::WgServer;
|
|||||||
use crate::util::net::WebSocketExt;
|
use crate::util::net::WebSocketExt;
|
||||||
use crate::util::serde::{HandlerExtSerde, apply_expr};
|
use crate::util::serde::{HandlerExtSerde, apply_expr};
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Serialize, HasModel)]
|
#[derive(Default, Deserialize, Serialize, HasModel, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
pub struct TunnelDatabase {
|
pub struct TunnelDatabase {
|
||||||
pub webserver: WebserverInfo,
|
pub webserver: WebserverInfo,
|
||||||
pub sessions: Sessions,
|
pub sessions: Sessions,
|
||||||
pub password: Option<String>,
|
pub password: Option<String>,
|
||||||
|
#[ts(as = "std::collections::HashMap::<AnyVerifyingKey, SignerInfo>")]
|
||||||
pub auth_pubkeys: HashMap<AnyVerifyingKey, SignerInfo>,
|
pub auth_pubkeys: HashMap<AnyVerifyingKey, SignerInfo>,
|
||||||
|
#[ts(as = "std::collections::BTreeMap::<AnyVerifyingKey, SignerInfo>")]
|
||||||
pub gateways: OrdMap<GatewayId, NetworkInterfaceInfo>,
|
pub gateways: OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
pub wg: WgServer,
|
pub wg: WgServer,
|
||||||
pub port_forwards: PortForwards,
|
pub port_forwards: PortForwards,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
#[test]
|
||||||
|
fn export_bindings_tunnel_db() {
|
||||||
|
TunnelDatabase::export_all_to("bindings/tunnel").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
||||||
pub struct PortForwards(pub BTreeMap<SocketAddrV4, SocketAddrV4>);
|
pub struct PortForwards(pub BTreeMap<SocketAddrV4, SocketAddrV4>);
|
||||||
impl Map for PortForwards {
|
impl Map for PortForwards {
|
||||||
type Key = SocketAddrV4;
|
type Key = SocketAddrV4;
|
||||||
|
|||||||
@@ -1,31 +1,36 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::io::Write;
|
|
||||||
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use hickory_client::proto::rr::rdata::cert;
|
||||||
use imbl_value::{InternedString, json};
|
use imbl_value::{InternedString, json};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
use openssl::x509::X509;
|
use openssl::x509::X509;
|
||||||
use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
use rpc_toolkit::{
|
||||||
|
Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async, from_fn_async_local,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
use tokio_rustls::rustls::ServerConfig;
|
use tokio_rustls::rustls::ServerConfig;
|
||||||
use tokio_rustls::rustls::crypto::CryptoProvider;
|
use tokio_rustls::rustls::crypto::CryptoProvider;
|
||||||
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
||||||
use tokio_rustls::rustls::server::ClientHello;
|
use tokio_rustls::rustls::server::ClientHello;
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::net::ssl::SANInfo;
|
use crate::net::ssl::SANInfo;
|
||||||
use crate::net::tls::TlsHandler;
|
use crate::net::tls::TlsHandler;
|
||||||
use crate::net::web_server::Accept;
|
use crate::net::web_server::Accept;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::tunnel::auth::SetPasswordParams;
|
||||||
use crate::tunnel::context::TunnelContext;
|
use crate::tunnel::context::TunnelContext;
|
||||||
use crate::tunnel::db::TunnelDatabase;
|
use crate::tunnel::db::TunnelDatabase;
|
||||||
use crate::util::serde::{HandlerExtSerde, Pem};
|
use crate::util::serde::{HandlerExtSerde, Pem, display_serializable};
|
||||||
|
use crate::util::tui::{choose, parse_as, prompt, prompt_multiline};
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
|
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
pub struct WebserverInfo {
|
pub struct WebserverInfo {
|
||||||
@@ -34,7 +39,7 @@ pub struct WebserverInfo {
|
|||||||
pub certificate: Option<TunnelCertData>,
|
pub certificate: Option<TunnelCertData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TunnelCertData {
|
pub struct TunnelCertData {
|
||||||
pub key: Pem<PKey<Private>>,
|
pub key: Pem<PKey<Private>>,
|
||||||
@@ -91,7 +96,7 @@ pub fn web_api<C: Context>() -> ParentHandler<C> {
|
|||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"init",
|
"init",
|
||||||
from_fn_async(init_web)
|
from_fn_async_local(init_web)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Initialize the webserver"),
|
.with_about("Initialize the webserver"),
|
||||||
)
|
)
|
||||||
@@ -122,7 +127,7 @@ pub fn web_api<C: Context>() -> ParentHandler<C> {
|
|||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"import-certificate",
|
"import-certificate",
|
||||||
from_fn_async(import_certificate_cli)
|
from_fn_async_local(import_certificate_cli)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Import a certificate to use for the webserver"),
|
.with_about("Import a certificate to use for the webserver"),
|
||||||
)
|
)
|
||||||
@@ -132,6 +137,22 @@ pub fn web_api<C: Context>() -> ParentHandler<C> {
|
|||||||
.with_about("Generate a self signed certificaet to use for the webserver")
|
.with_about("Generate a self signed certificaet to use for the webserver")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
"get-certificate",
|
||||||
|
from_fn_async(get_certificate)
|
||||||
|
.with_display_serializable()
|
||||||
|
.with_custom_display_fn(|HandlerArgs { params, .. }, res| {
|
||||||
|
if let Some(format) = params.format {
|
||||||
|
return display_serializable(format, res);
|
||||||
|
}
|
||||||
|
if let Some(res) = res {
|
||||||
|
println!("{res}");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.with_about("Get the certificate for the webserver")
|
||||||
|
.with_call_remote::<CliContext>(),
|
||||||
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"enable",
|
"enable",
|
||||||
from_fn_async(enable_web)
|
from_fn_async(enable_web)
|
||||||
@@ -178,58 +199,61 @@ pub async fn import_certificate_cli(
|
|||||||
..
|
..
|
||||||
}: HandlerArgs<CliContext>,
|
}: HandlerArgs<CliContext>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
println!("Please paste in your PEM encoded private key: ");
|
|
||||||
let mut stdin_lines = BufReader::new(tokio::io::stdin()).lines();
|
|
||||||
let mut key_string = String::new();
|
let mut key_string = String::new();
|
||||||
|
let key: Pem<PKey<Private>> =
|
||||||
while let Some(line) = stdin_lines.next_line().await? {
|
prompt_multiline("Please paste in your PEM encoded private key: ", |line| {
|
||||||
key_string.push_str(&line);
|
key_string.push_str(&line);
|
||||||
key_string.push_str("\n");
|
key_string.push_str("\n");
|
||||||
if line.trim().starts_with("-----END") {
|
if line.trim().starts_with("-----END") {
|
||||||
break;
|
return key_string.parse().map(Some).map_err(|e| {
|
||||||
}
|
key_string.truncate(0);
|
||||||
}
|
e
|
||||||
|
});
|
||||||
let key: Pem<PKey<Private>> = key_string.parse()?;
|
}
|
||||||
|
Ok(None)
|
||||||
println!("Please paste in your PEM encoded certificate (or certificate chain): ");
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut chain = Vec::<X509>::new();
|
let mut chain = Vec::<X509>::new();
|
||||||
|
let mut cert_string = String::new();
|
||||||
loop {
|
prompt_multiline(
|
||||||
let mut cert_string = String::new();
|
concat!(
|
||||||
|
"Please paste in your PEM encoded certificate",
|
||||||
while let Some(line) = stdin_lines.next_line().await? {
|
" (or certificate chain):"
|
||||||
|
),
|
||||||
|
|line| {
|
||||||
cert_string.push_str(&line);
|
cert_string.push_str(&line);
|
||||||
cert_string.push_str("\n");
|
cert_string.push_str("\n");
|
||||||
if line.trim().starts_with("-----END") {
|
if line.trim().starts_with("-----END") {
|
||||||
break;
|
let cert = cert_string.parse::<Pem<X509>>();
|
||||||
|
cert_string.truncate(0);
|
||||||
|
let cert = cert?;
|
||||||
|
|
||||||
|
let key = cert.0.public_key()?;
|
||||||
|
|
||||||
|
if let Some(prev) = chain.last() {
|
||||||
|
if !prev.verify(&key)? {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!(concat!(
|
||||||
|
"Invalid Fullchain: ",
|
||||||
|
"Previous cert was not signed by this certificate's key"
|
||||||
|
)),
|
||||||
|
ErrorKind::InvalidSignature,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_root = cert.0.verify(&key)?;
|
||||||
|
|
||||||
|
chain.push(cert.0);
|
||||||
|
|
||||||
|
if is_root { Ok(Some(())) } else { Ok(None) }
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
)
|
||||||
let cert: Pem<X509> = cert_string.parse()?;
|
.await?;
|
||||||
|
|
||||||
let key = cert.0.public_key()?;
|
|
||||||
|
|
||||||
if let Some(prev) = chain.last() {
|
|
||||||
if !prev.verify(&key)? {
|
|
||||||
return Err(Error::new(
|
|
||||||
eyre!(
|
|
||||||
"Invalid Fullchain: Previous cert was not signed by this certificate's key"
|
|
||||||
),
|
|
||||||
ErrorKind::InvalidSignature,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_root = cert.0.verify(&key)?;
|
|
||||||
|
|
||||||
chain.push(cert.0);
|
|
||||||
|
|
||||||
if is_root {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context
|
context
|
||||||
.call_remote::<TunnelContext>(
|
.call_remote::<TunnelContext>(
|
||||||
@@ -274,6 +298,17 @@ pub async fn generate_certificate(
|
|||||||
Ok(Pem(cert))
|
Ok(Pem(cert))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_certificate(ctx: TunnelContext) -> Result<Option<Pem<Vec<X509>>>, Error> {
|
||||||
|
ctx.db
|
||||||
|
.peek()
|
||||||
|
.await
|
||||||
|
.as_webserver()
|
||||||
|
.as_certificate()
|
||||||
|
.de()?
|
||||||
|
.map(|cert_data| Ok(cert_data.cert))
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Parser)]
|
#[derive(Debug, Deserialize, Serialize, Parser)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SetListenParams {
|
pub struct SetListenParams {
|
||||||
@@ -371,27 +406,115 @@ pub async fn reset_web(ctx: TunnelContext) -> Result<(), Error> {
|
|||||||
.result
|
.result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_valid_domain(domain: &str) -> bool {
|
||||||
|
if domain.is_empty() || domain.len() > 253 || domain.starts_with('.') || domain.ends_with('.') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let labels: Vec<&str> = domain.split('.').collect();
|
||||||
|
|
||||||
|
for label in labels {
|
||||||
|
if label.is_empty() || label.len() > 63 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !label.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if label.chars().next().map_or(true, |c| c == '-')
|
||||||
|
|| label.chars().next_back().map_or(true, |c| c == '-')
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
|
pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
|
||||||
|
let mut password = None;
|
||||||
loop {
|
loop {
|
||||||
match ctx
|
match ctx
|
||||||
.call_remote::<TunnelContext>("web.enable", json!({}))
|
.call_remote::<TunnelContext>("web.enable", json!({}))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("Webserver Initialized");
|
let listen = from_value::<SocketAddr>(
|
||||||
|
ctx.call_remote::<TunnelContext>("web.get-listen", json!({}))
|
||||||
|
.await?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
println!("✅ Success! ✅");
|
||||||
|
println!(
|
||||||
|
"The webserver is running. Below is your URL{} and SSL certificate.",
|
||||||
|
if password.is_some() {
|
||||||
|
", password,"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
);
|
||||||
|
println!();
|
||||||
|
println!("🌐 URL");
|
||||||
|
println!("https://{listen}");
|
||||||
|
if listen.ip().is_unspecified() {
|
||||||
|
println!(concat!(
|
||||||
|
"Note: this is the unspecified address. ",
|
||||||
|
"This means you can use any IP address available to this device to connect. ",
|
||||||
|
"Using the above address as-is will only work from this device."
|
||||||
|
));
|
||||||
|
} else if listen.ip().is_loopback() {
|
||||||
|
println!(concat!(
|
||||||
|
"Note: this is a loopback address. ",
|
||||||
|
"This is only recommended if you are planning to run a proxy in front of the web ui. ",
|
||||||
|
"Using the above address as-is will only work from this device."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
if let Some(password) = password {
|
||||||
|
println!("🔒 Password");
|
||||||
|
println!("{password}");
|
||||||
|
println!();
|
||||||
|
println!(concat!(
|
||||||
|
"If you lose or forget your password, you can reset it using the command: ",
|
||||||
|
"start-tunnel auth reset-password"
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
println!(concat!(
|
||||||
|
"Your password was set up previously. ",
|
||||||
|
"If you don't remember it, you can reset it using the command: ",
|
||||||
|
"start-tunnel auth reset-password"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
let cert = from_value::<Pem<Vec<X509>>>(
|
||||||
|
ctx.call_remote::<TunnelContext>("web.get-certificate", json!({}))
|
||||||
|
.await?,
|
||||||
|
)?;
|
||||||
|
println!("📝 SSL Certificate:");
|
||||||
|
println!("{cert}");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
println!(concat!(
|
||||||
|
"If you haven't already, ",
|
||||||
|
"trust the certificate in your system keychain and/or browser."
|
||||||
|
));
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err(e) if e.kind == ErrorKind::ParseNetAddress => {
|
Err(e) if e.kind == ErrorKind::ParseNetAddress => {
|
||||||
println!("A listen address has not been set yet. Setting one up now...");
|
println!("Select the IP address at which to host the web interface:");
|
||||||
|
|
||||||
let available_ips = from_value::<Vec<IpAddr>>(
|
let available_ips = from_value::<Vec<IpAddr>>(
|
||||||
ctx.call_remote::<TunnelContext>("web.get-available-ips", json!({}))
|
ctx.call_remote::<TunnelContext>("web.get-available-ips", json!({}))
|
||||||
.await?,
|
.await?,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let suggested_addr = available_ips
|
let suggested_addrs = available_ips
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|ip| match ip {
|
.filter(|ip| match ip {
|
||||||
IpAddr::V4(ipv4) => !ipv4.is_private() && !ipv4.is_loopback(),
|
IpAddr::V4(ipv4) => !ipv4.is_private() && !ipv4.is_loopback(),
|
||||||
IpAddr::V6(ipv6) => {
|
IpAddr::V6(ipv6) => {
|
||||||
!ipv6.is_loopback()
|
!ipv6.is_loopback()
|
||||||
@@ -399,148 +522,132 @@ pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
|
|||||||
&& !ipv6.is_unicast_link_local()
|
&& !ipv6.is_unicast_link_local()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|ip| SocketAddr::new(ip, 8443))
|
.chain([Ipv6Addr::UNSPECIFIED.into()])
|
||||||
.unwrap_or_else(|| SocketAddr::from((Ipv6Addr::UNSPECIFIED, 8443)));
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let (mut readline, _writer) = rustyline_async::Readline::new(format!(
|
let ip = if suggested_addrs.len() > 16 {
|
||||||
"Listen Address [{}]: ",
|
prompt(
|
||||||
suggested_addr
|
&format!("Listen Address [{}]: ", suggested_addrs[0]),
|
||||||
))
|
parse_as::<IpAddr>("IP Address"),
|
||||||
.with_kind(ErrorKind::Filesystem)?;
|
Some(suggested_addrs[0]),
|
||||||
|
)
|
||||||
let listen: SocketAddr = loop {
|
.await?
|
||||||
match readline.readline().await.with_kind(ErrorKind::Filesystem)? {
|
} else {
|
||||||
rustyline_async::ReadlineEvent::Line(l) if !l.trim().is_empty() => {
|
*choose("Listen Address:", &suggested_addrs).await?
|
||||||
match l.trim().parse() {
|
|
||||||
Ok(addr) => break addr,
|
|
||||||
Err(_) => {
|
|
||||||
println!(
|
|
||||||
"Invalid socket address. Please enter in format IP:PORT (e.g., 0.0.0.0:8443)"
|
|
||||||
);
|
|
||||||
readline.clear_history();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rustyline_async::ReadlineEvent::Line(_) => {
|
|
||||||
break suggested_addr;
|
|
||||||
}
|
|
||||||
_ => return Err(Error::new(eyre!("Aborted"), ErrorKind::Unknown)),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
println!(concat!(
|
||||||
|
"Enter the port at which to host the web interface. ",
|
||||||
|
"The recommended default is 8443. ",
|
||||||
|
"If you change the default, choose an uncommon port to avoid conflicts: "
|
||||||
|
));
|
||||||
|
let port = prompt("Port [8443]: ", parse_as::<u16>("port"), Some(8443)).await?;
|
||||||
|
|
||||||
|
let listen = SocketAddr::new(ip, port);
|
||||||
|
|
||||||
ctx.call_remote::<TunnelContext>(
|
ctx.call_remote::<TunnelContext>(
|
||||||
"web.set-listen",
|
"web.set-listen",
|
||||||
to_value(&SetListenParams { listen })?,
|
to_value(&SetListenParams { listen })?,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
println!();
|
||||||
}
|
}
|
||||||
Err(e) if e.kind == ErrorKind::OpenSsl => {
|
Err(e) if e.kind == ErrorKind::OpenSsl => {
|
||||||
println!(
|
enum Choice {
|
||||||
"StartTunnel has not been set up with an SSL Certificate yet. Setting one up now..."
|
Generate,
|
||||||
);
|
Provide,
|
||||||
println!("[1]: Generate a Self Signed Certificate");
|
}
|
||||||
println!("[2]: Provide your own certificate and key");
|
impl std::fmt::Display for Choice {
|
||||||
let (mut readline, mut writer) =
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
rustyline_async::Readline::new("What would you like to do? [1-2]: ".into())
|
match self {
|
||||||
.with_kind(ErrorKind::Filesystem)?;
|
Self::Generate => write!(f, "Generate a Self Signed Certificate"),
|
||||||
readline.add_history_entry("1".into());
|
Self::Provide => write!(f, "Provide your own certificate and key"),
|
||||||
readline.add_history_entry("2".into());
|
|
||||||
let self_signed;
|
|
||||||
loop {
|
|
||||||
match readline.readline().await.with_kind(ErrorKind::Filesystem)? {
|
|
||||||
rustyline_async::ReadlineEvent::Line(l)
|
|
||||||
if l.trim_matches(|c: char| c.is_whitespace() || c == '"') == "1" =>
|
|
||||||
{
|
|
||||||
self_signed = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
rustyline_async::ReadlineEvent::Line(l)
|
|
||||||
if l.trim_matches(|c: char| c.is_whitespace() || c == '"') == "2" =>
|
|
||||||
{
|
|
||||||
self_signed = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
rustyline_async::ReadlineEvent::Line(_) => {
|
|
||||||
readline.clear_history();
|
|
||||||
readline.add_history_entry("1".into());
|
|
||||||
readline.add_history_entry("2".into());
|
|
||||||
writeln!(writer, "Invalid response. Enter either \"1\" or \"2\".")?;
|
|
||||||
}
|
|
||||||
_ => return Err(Error::new(eyre!("Aborted"), ErrorKind::Unknown)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self_signed {
|
let options = vec![Choice::Generate, Choice::Provide];
|
||||||
let listen = from_value::<Option<SocketAddr>>(
|
let choice = choose(
|
||||||
ctx.call_remote::<TunnelContext>("web.get-listen", json!({}))
|
concat!(
|
||||||
.await?,
|
"Select whether to autogenerate a self-signed SSL certificate ",
|
||||||
)?
|
"or provide your own certificate and key:"
|
||||||
.filter(|a| !a.ip().is_unspecified());
|
),
|
||||||
writeln!(
|
&options,
|
||||||
writer,
|
)
|
||||||
"Enter the name(s) to sign the certificate for, separated by commas."
|
.await?;
|
||||||
)?;
|
|
||||||
readline.clear_history();
|
|
||||||
let default_prompt = if let Some(listen) = listen {
|
|
||||||
format!("Subject Alternative Name(s) [{}]: ", listen.ip())
|
|
||||||
} else {
|
|
||||||
"Subject Alternative Name(s): ".to_string()
|
|
||||||
};
|
|
||||||
readline
|
|
||||||
.update_prompt(&default_prompt)
|
|
||||||
.with_kind(ErrorKind::Filesystem)?;
|
|
||||||
let mut saninfo = Vec::new();
|
|
||||||
loop {
|
|
||||||
match readline.readline().await.with_kind(ErrorKind::Filesystem)? {
|
|
||||||
rustyline_async::ReadlineEvent::Line(l) if !l.trim().is_empty() => {
|
|
||||||
saninfo.extend(l.split(",").map(|h| h.trim().into()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
rustyline_async::ReadlineEvent::Line(_) => {
|
|
||||||
saninfo.extend(
|
|
||||||
listen
|
|
||||||
.map(|l| l.ip())
|
|
||||||
.as_ref()
|
|
||||||
.map(InternedString::from_display),
|
|
||||||
);
|
|
||||||
readline.clear_history();
|
|
||||||
if !saninfo.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return Err(Error::new(eyre!("Aborted"), ErrorKind::Unknown)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.call_remote::<TunnelContext>(
|
match choice {
|
||||||
"web.generate-certificate",
|
Choice::Generate => {
|
||||||
to_value(&GenerateCertParams { subject: saninfo })?,
|
let listen = from_value::<Option<SocketAddr>>(
|
||||||
)
|
ctx.call_remote::<TunnelContext>("web.get-listen", json!({}))
|
||||||
.await?;
|
.await?,
|
||||||
} else {
|
)?
|
||||||
drop((readline, writer));
|
.filter(|a| !a.ip().is_unspecified());
|
||||||
import_certificate_cli(HandlerArgs {
|
|
||||||
context: ctx.clone(),
|
let default_prompt = if let Some(listen) = listen {
|
||||||
parent_method: vec!["web", "import-certificate"].into(),
|
format!("Subject Alternative Name(s) [{}]: ", listen.ip())
|
||||||
method: VecDeque::new(),
|
} else {
|
||||||
params: Empty {},
|
"Subject Alternative Name(s): ".to_string()
|
||||||
inherited_params: Empty {},
|
};
|
||||||
raw_params: json!({}),
|
|
||||||
})
|
println!(
|
||||||
.await?;
|
"List all IP addresses and domains for which to sign the certificate, separated by commas."
|
||||||
|
);
|
||||||
|
let san_info = prompt(
|
||||||
|
&default_prompt,
|
||||||
|
|s| {
|
||||||
|
s.split(",")
|
||||||
|
.map(|s| {
|
||||||
|
let s = s.trim();
|
||||||
|
if let Ok(ip) = s.parse::<IpAddr>() {
|
||||||
|
Ok(InternedString::from_display(&ip))
|
||||||
|
} else if is_valid_domain(s) {
|
||||||
|
Ok(s.into())
|
||||||
|
} else {
|
||||||
|
Err(format!("{s} is not a valid ip address or domain"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
listen.map(|l| vec![InternedString::from_display(&l.ip())]),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
ctx.call_remote::<TunnelContext>(
|
||||||
|
"web.generate-certificate",
|
||||||
|
to_value(&GenerateCertParams { subject: san_info })?,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Choice::Provide => {
|
||||||
|
import_certificate_cli(HandlerArgs {
|
||||||
|
context: ctx.clone(),
|
||||||
|
parent_method: vec!["web", "import-certificate"].into(),
|
||||||
|
method: VecDeque::new(),
|
||||||
|
params: Empty {},
|
||||||
|
inherited_params: Empty {},
|
||||||
|
raw_params: json!({}),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
}
|
}
|
||||||
Err(e) if e.kind == ErrorKind::Authorization => {
|
Err(e) if e.kind == ErrorKind::Authorization => {
|
||||||
println!("A password has not been setup yet. Setting one up now...");
|
println!("Generating a random password...");
|
||||||
|
let params = SetPasswordParams {
|
||||||
|
password: base32::encode(
|
||||||
|
base32::Alphabet::Rfc4648 { padding: false },
|
||||||
|
&rand::random::<[u8; 10]>(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
ctx.call_remote::<TunnelContext>("auth.set-password", to_value(¶ms)?)
|
||||||
|
.await?;
|
||||||
|
|
||||||
super::auth::set_password_cli(HandlerArgs {
|
password = Some(params.password);
|
||||||
context: ctx.clone(),
|
|
||||||
parent_method: vec!["auth", "set-password"].into(),
|
println!();
|
||||||
method: VecDeque::new(),
|
|
||||||
params: Empty {},
|
|
||||||
inherited_params: Empty {},
|
|
||||||
raw_params: json!({}),
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use ipnet::Ipv4Net;
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
use ts_rs::TS;
|
||||||
use x25519_dalek::{PublicKey, StaticSecret};
|
use x25519_dalek::{PublicKey, StaticSecret};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -15,7 +16,7 @@ use crate::util::serde::Base64;
|
|||||||
|
|
||||||
pub const WIREGUARD_INTERFACE_NAME: &str = "wg-start-tunnel";
|
pub const WIREGUARD_INTERFACE_NAME: &str = "wg-start-tunnel";
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, HasModel)]
|
#[derive(Deserialize, Serialize, HasModel, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
pub struct WgServer {
|
pub struct WgServer {
|
||||||
@@ -64,8 +65,10 @@ impl WgServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Serialize)]
|
#[derive(Default, Deserialize, Serialize, TS)]
|
||||||
pub struct WgSubnetMap(pub BTreeMap<Ipv4Net, WgSubnetConfig>);
|
pub struct WgSubnetMap(
|
||||||
|
#[ts(as = "BTreeMap::<String, WgSubnetConfig>")] pub BTreeMap<Ipv4Net, WgSubnetConfig>,
|
||||||
|
);
|
||||||
impl Map for WgSubnetMap {
|
impl Map for WgSubnetMap {
|
||||||
type Key = Ipv4Net;
|
type Key = Ipv4Net;
|
||||||
type Value = WgSubnetConfig;
|
type Value = WgSubnetConfig;
|
||||||
@@ -77,7 +80,7 @@ impl Map for WgSubnetMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Serialize, HasModel)]
|
#[derive(Default, Deserialize, Serialize, HasModel, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
pub struct WgSubnetConfig {
|
pub struct WgSubnetConfig {
|
||||||
@@ -93,8 +96,7 @@ impl WgSubnetConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Serialize)]
|
#[derive(Default, Deserialize, Serialize, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct WgSubnetClients(pub BTreeMap<Ipv4Addr, WgConfig>);
|
pub struct WgSubnetClients(pub BTreeMap<Ipv4Addr, WgConfig>);
|
||||||
impl Map for WgSubnetClients {
|
impl Map for WgSubnetClients {
|
||||||
type Key = Ipv4Addr;
|
type Key = Ipv4Addr;
|
||||||
@@ -143,7 +145,7 @@ impl Base64<WgKey> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize, HasModel)]
|
#[derive(Clone, Deserialize, Serialize, HasModel, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
pub struct WgConfig {
|
pub struct WgConfig {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use axum::middleware::FromFn;
|
||||||
use futures::future::{BoxFuture, FusedFuture, abortable, pending};
|
use futures::future::{BoxFuture, FusedFuture, abortable, pending};
|
||||||
use futures::stream::{AbortHandle, Abortable, BoxStream};
|
use futures::stream::{AbortHandle, Abortable, BoxStream};
|
||||||
use futures::{Future, FutureExt, Stream, StreamExt};
|
use futures::{Future, FutureExt, Stream, StreamExt};
|
||||||
|
use rpc_toolkit::from_fn_blocking;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
|
use tokio::task::LocalSet;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
@@ -158,6 +161,31 @@ impl<'a> Until<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn make_send<F, Fut, T>(f: F) -> Result<T, Error>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Fut + Send + 'static,
|
||||||
|
Fut: Future<Output = Result<T, Error>> + 'static,
|
||||||
|
T: Send + 'static,
|
||||||
|
{
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let local = LocalSet::new();
|
||||||
|
|
||||||
|
local.block_on(&rt, async move { f().await })
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
Error::new(
|
||||||
|
eyre!("Task running non-Send future panicked: {}", e),
|
||||||
|
ErrorKind::Unknown,
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_cancellable() {
|
async fn test_cancellable() {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ pub mod rpc_client;
|
|||||||
pub mod serde;
|
pub mod serde;
|
||||||
pub mod squashfs;
|
pub mod squashfs;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
|
pub mod tui;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, ::serde::Deserialize, ::serde::Serialize)]
|
#[derive(Clone, Copy, Debug, ::serde::Deserialize, ::serde::Serialize)]
|
||||||
pub enum Never {}
|
pub enum Never {}
|
||||||
|
|||||||
139
core/startos/src/util/tui.rs
Normal file
139
core/startos/src/util/tui.rs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
use std::io::Write;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use r3bl_tui::{DefaultIoDevices, ReadlineAsyncContext, ReadlineEvent};
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
fn map_miette(m: miette::Error) -> Error {
|
||||||
|
Error::new(eyre!("{m}"), ErrorKind::Filesystem)
|
||||||
|
}
|
||||||
|
fn noninteractive_err() -> Error {
|
||||||
|
Error::new(
|
||||||
|
eyre!("Terminal must be in interactive mode for this wizard"),
|
||||||
|
ErrorKind::Filesystem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_as<'a, T>(what: &'a str) -> impl Fn(&str) -> Result<T, String> + 'a
|
||||||
|
where
|
||||||
|
T: FromStr,
|
||||||
|
{
|
||||||
|
move |s| {
|
||||||
|
s.parse::<T>()
|
||||||
|
.map_err(|_| format!("Please enter a valid {what}."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn prompt<T, E: std::fmt::Display, Parse: FnMut(&str) -> Result<T, E>>(
|
||||||
|
prompt: &str,
|
||||||
|
mut parse: Parse,
|
||||||
|
default: Option<T>,
|
||||||
|
) -> Result<T, Error> {
|
||||||
|
let mut rl_ctx = ReadlineAsyncContext::try_new(Some(prompt))
|
||||||
|
.await
|
||||||
|
.map_err(map_miette)?
|
||||||
|
.ok_or_else(noninteractive_err)?;
|
||||||
|
let res = loop {
|
||||||
|
match rl_ctx.read_line().await.map_err(map_miette)? {
|
||||||
|
ReadlineEvent::Line(l) => {
|
||||||
|
let l = l.trim();
|
||||||
|
if !l.is_empty() {
|
||||||
|
match parse(l) {
|
||||||
|
Ok(a) => break a,
|
||||||
|
Err(e) => {
|
||||||
|
writeln!(&mut rl_ctx.shared_writer, "{e}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(default) = default {
|
||||||
|
break default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReadlineEvent::Eof | ReadlineEvent::Interrupted => {
|
||||||
|
return Err(Error::new(eyre!("Aborted"), ErrorKind::Cancelled));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rl_ctx.request_shutdown(None).await.map_err(map_miette)?;
|
||||||
|
rl_ctx.await_shutdown().await;
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn prompt_multiline<
|
||||||
|
T,
|
||||||
|
E: std::fmt::Display,
|
||||||
|
HandleLine: FnMut(String) -> Result<Option<T>, E>,
|
||||||
|
>(
|
||||||
|
prompt: &str,
|
||||||
|
mut handle_line: HandleLine,
|
||||||
|
) -> Result<T, Error> {
|
||||||
|
println!("{prompt}");
|
||||||
|
let mut rl_ctx = ReadlineAsyncContext::try_new(None::<&str>)
|
||||||
|
.await
|
||||||
|
.map_err(map_miette)?
|
||||||
|
.ok_or_else(noninteractive_err)?;
|
||||||
|
let res = loop {
|
||||||
|
match rl_ctx.read_line().await.map_err(map_miette)? {
|
||||||
|
ReadlineEvent::Line(l) => match handle_line(l) {
|
||||||
|
Ok(Some(a)) => break a,
|
||||||
|
Ok(None) => (),
|
||||||
|
Err(e) => writeln!(&mut rl_ctx.shared_writer, "{e}")?,
|
||||||
|
},
|
||||||
|
ReadlineEvent::Eof | ReadlineEvent::Interrupted => {
|
||||||
|
return Err(Error::new(eyre!("Aborted"), ErrorKind::Cancelled));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rl_ctx.request_shutdown(None).await.map_err(map_miette)?;
|
||||||
|
rl_ctx.await_shutdown().await;
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn choose<'t, T: std::fmt::Display>(
|
||||||
|
prompt: &str,
|
||||||
|
choices: &'t [T],
|
||||||
|
) -> Result<&'t T, Error> {
|
||||||
|
let mut io = DefaultIoDevices::default();
|
||||||
|
let style = r3bl_tui::readline_async::StyleSheet::default();
|
||||||
|
let string_choices = choices
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| c.to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let choice = r3bl_tui::readline_async::choose(
|
||||||
|
prompt,
|
||||||
|
string_choices.clone(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
r3bl_tui::HowToChoose::Single,
|
||||||
|
style,
|
||||||
|
(
|
||||||
|
&mut io.output_device,
|
||||||
|
&mut io.input_device,
|
||||||
|
io.maybe_shared_writer,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(map_miette)?;
|
||||||
|
if choice.len() < 1 {
|
||||||
|
return Err(Error::new(eyre!("Aborted"), ErrorKind::Cancelled));
|
||||||
|
}
|
||||||
|
let (idx, _) = string_choices
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, s)| s.as_str() == choice[0].as_str())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::new(
|
||||||
|
eyre!("selected choice does not appear in input"),
|
||||||
|
ErrorKind::Incoherent,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let choice = &choices[idx];
|
||||||
|
println!("{prompt} {choice}");
|
||||||
|
Ok(&choice)
|
||||||
|
}
|
||||||
1
debian/start-tunnel/postinst
vendored
1
debian/start-tunnel/postinst
vendored
@@ -7,3 +7,4 @@ if [ -n "$DPKG_MAINTSCRIPT_PACKAGE" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
$SYSTEMCTL enable start-tunneld.service
|
$SYSTEMCTL enable start-tunneld.service
|
||||||
|
$SYSTEMCTL restart start-tunneld.service
|
||||||
3
sdk/base/lib/osBindings/tunnel/AnyVerifyingKey.ts
Normal file
3
sdk/base/lib/osBindings/tunnel/AnyVerifyingKey.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type AnyVerifyingKey = string
|
||||||
3
sdk/base/lib/osBindings/tunnel/Base64.ts
Normal file
3
sdk/base/lib/osBindings/tunnel/Base64.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type Base64 = string
|
||||||
3
sdk/base/lib/osBindings/tunnel/Pem.ts
Normal file
3
sdk/base/lib/osBindings/tunnel/Pem.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type Pem = string
|
||||||
3
sdk/base/lib/osBindings/tunnel/PortForwards.ts
Normal file
3
sdk/base/lib/osBindings/tunnel/PortForwards.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type PortForwards = { [key: string]: string }
|
||||||
7
sdk/base/lib/osBindings/tunnel/Session.ts
Normal file
7
sdk/base/lib/osBindings/tunnel/Session.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type Session = {
|
||||||
|
loggedIn: string
|
||||||
|
lastActive: string
|
||||||
|
userAgent: string | null
|
||||||
|
}
|
||||||
4
sdk/base/lib/osBindings/tunnel/Sessions.ts
Normal file
4
sdk/base/lib/osBindings/tunnel/Sessions.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { Session } from "./Session"
|
||||||
|
|
||||||
|
export type Sessions = { [key: string]: Session }
|
||||||
3
sdk/base/lib/osBindings/tunnel/SignerInfo.ts
Normal file
3
sdk/base/lib/osBindings/tunnel/SignerInfo.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type SignerInfo = { name: string }
|
||||||
4
sdk/base/lib/osBindings/tunnel/TunnelCertData.ts
Normal file
4
sdk/base/lib/osBindings/tunnel/TunnelCertData.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { Pem } from "./Pem"
|
||||||
|
|
||||||
|
export type TunnelCertData = { key: Pem; cert: Pem }
|
||||||
17
sdk/base/lib/osBindings/tunnel/TunnelDatabase.ts
Normal file
17
sdk/base/lib/osBindings/tunnel/TunnelDatabase.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { AnyVerifyingKey } from "./AnyVerifyingKey"
|
||||||
|
import type { PortForwards } from "./PortForwards"
|
||||||
|
import type { Sessions } from "./Sessions"
|
||||||
|
import type { SignerInfo } from "./SignerInfo"
|
||||||
|
import type { WebserverInfo } from "./WebserverInfo"
|
||||||
|
import type { WgServer } from "./WgServer"
|
||||||
|
|
||||||
|
export type TunnelDatabase = {
|
||||||
|
webserver: WebserverInfo
|
||||||
|
sessions: Sessions
|
||||||
|
password: string | null
|
||||||
|
authPubkeys: { [key: AnyVerifyingKey]: SignerInfo }
|
||||||
|
gateways: { [key: AnyVerifyingKey]: SignerInfo }
|
||||||
|
wg: WgServer
|
||||||
|
portForwards: PortForwards
|
||||||
|
}
|
||||||
8
sdk/base/lib/osBindings/tunnel/WebserverInfo.ts
Normal file
8
sdk/base/lib/osBindings/tunnel/WebserverInfo.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { TunnelCertData } from "./TunnelCertData"
|
||||||
|
|
||||||
|
export type WebserverInfo = {
|
||||||
|
enabled: boolean
|
||||||
|
listen: string | null
|
||||||
|
certificate: TunnelCertData | null
|
||||||
|
}
|
||||||
4
sdk/base/lib/osBindings/tunnel/WgConfig.ts
Normal file
4
sdk/base/lib/osBindings/tunnel/WgConfig.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { Base64 } from "./Base64"
|
||||||
|
|
||||||
|
export type WgConfig = { name: string; key: Base64; psk: Base64 }
|
||||||
5
sdk/base/lib/osBindings/tunnel/WgServer.ts
Normal file
5
sdk/base/lib/osBindings/tunnel/WgServer.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { Base64 } from "./Base64"
|
||||||
|
import type { WgSubnetMap } from "./WgSubnetMap"
|
||||||
|
|
||||||
|
export type WgServer = { port: number; key: Base64; subnets: WgSubnetMap }
|
||||||
4
sdk/base/lib/osBindings/tunnel/WgSubnetClients.ts
Normal file
4
sdk/base/lib/osBindings/tunnel/WgSubnetClients.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { WgConfig } from "./WgConfig"
|
||||||
|
|
||||||
|
export type WgSubnetClients = { [key: string]: WgConfig }
|
||||||
4
sdk/base/lib/osBindings/tunnel/WgSubnetConfig.ts
Normal file
4
sdk/base/lib/osBindings/tunnel/WgSubnetConfig.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { WgSubnetClients } from "./WgSubnetClients"
|
||||||
|
|
||||||
|
export type WgSubnetConfig = { name: string; clients: WgSubnetClients }
|
||||||
4
sdk/base/lib/osBindings/tunnel/WgSubnetMap.ts
Normal file
4
sdk/base/lib/osBindings/tunnel/WgSubnetMap.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { WgSubnetConfig } from "./WgSubnetConfig"
|
||||||
|
|
||||||
|
export type WgSubnetMap = { [key: string]: WgSubnetConfig }
|
||||||
Reference in New Issue
Block a user