mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-01 21:13:09 +00:00
chore: remove tor from startos core
Tor is being moved from a built-in OS feature to a service. This removes the Arti-based Tor client, onion address management, hidden service creation, and all related code from the core backend, frontend, and SDK. - Delete core/src/net/tor/ module (~2060 lines) - Remove OnionAddress, TorSecretKey, TorController from all consumers - Remove HostnameInfo::Onion and HostAddress::Onion variants - Remove onion CRUD RPC endpoints and tor subcommand - Remove tor key handling from account and backup/restore - Remove ~12 tor-related Cargo dependencies (arti-client, torut, etc.) - Remove tor UI components, API methods, mock data, and routes - Remove OnionHostname and tor patterns/regexes from SDK - Add v0_4_0_alpha_20 database migration to strip onion data - Bump version to 0.4.0-alpha.20
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"attribution": {
|
"attribution": {
|
||||||
"commit": ""
|
"commit": "",
|
||||||
|
"pr": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,6 +279,33 @@ Pending tasks for AI agents. Remove items when completed.
|
|||||||
| `sdk/base/lib/interfaces/Host.ts` | SDK `MultiHost.bindPort()` — no changes needed |
|
| `sdk/base/lib/interfaces/Host.ts` | SDK `MultiHost.bindPort()` — no changes needed |
|
||||||
| `core/src/db/model/public.rs` | Public DB model — port forward mapping |
|
| `core/src/db/model/public.rs` | Public DB model — port forward mapping |
|
||||||
|
|
||||||
|
- [ ] Remove Tor from StartOS core - @dr-bonez
|
||||||
|
|
||||||
|
**Goal**: Remove all built-in Tor functionality from StartOS. Tor will now be provided by a service
|
||||||
|
running on StartOS rather than being integrated into the core OS.
|
||||||
|
|
||||||
|
**Scope**: Remove the Arti-based Tor client, onion address management in the networking stack, Tor
|
||||||
|
hidden service creation in the vhost/net controller layers, and any Tor-specific configuration in the
|
||||||
|
database models. The core should no longer start, manage, or depend on a Tor daemon.
|
||||||
|
|
||||||
|
**Key areas to modify**:
|
||||||
|
|
||||||
|
| Area | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `core/src/net/tor/` | Remove the Tor module entirely (Arti client, hidden service management) |
|
||||||
|
| `core/src/net/net_controller.rs` | Remove Tor hidden service creation/teardown in `update()` |
|
||||||
|
| `core/src/net/host/address.rs` | Remove onion address CRUD RPC endpoints (`address.onion.add`, `address.onion.remove`) |
|
||||||
|
| `core/src/net/host/binding.rs` | Remove onion-related fields from `NetInfo` |
|
||||||
|
| `core/src/net/service_interface.rs` | Remove onion-related variants from `HostnameInfo` |
|
||||||
|
| `core/src/db/model/` | Remove Tor/onion fields from public and private DB models |
|
||||||
|
| `sdk/base/lib/interfaces/Host.ts` | Remove onion-related types and options from the SDK |
|
||||||
|
| `web/projects/ui/` | Remove onion address UI from interfaces pages |
|
||||||
|
| `Cargo.toml` / dependencies | Remove Arti and related Tor crate dependencies |
|
||||||
|
|
||||||
|
**Migration**: Existing onion address data in the database should be cleaned up during migration.
|
||||||
|
Services that previously relied on the OS-provided Tor integration will need to use the new Tor
|
||||||
|
service instead (service-level integration is out of scope for this task).
|
||||||
|
|
||||||
- [ ] Auto-configure port forwards via UPnP/NAT-PMP/PCP - @dr-bonez
|
- [ ] Auto-configure port forwards via UPnP/NAT-PMP/PCP - @dr-bonez
|
||||||
|
|
||||||
**Blocked by**: "Support preferred external ports besides 443" (must be implemented and tested
|
**Blocked by**: "Support preferred external ports besides 443" (must be implemented and tested
|
||||||
|
|||||||
2724
core/Cargo.lock
generated
2724
core/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ 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.19" # VERSION_BUMP
|
version = "0.4.0-alpha.20" # VERSION_BUMP
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "startos"
|
name = "startos"
|
||||||
@@ -42,17 +42,6 @@ name = "tunnelbox"
|
|||||||
path = "src/main/tunnelbox.rs"
|
path = "src/main/tunnelbox.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
arti = [
|
|
||||||
"arti-client",
|
|
||||||
"safelog",
|
|
||||||
"tor-cell",
|
|
||||||
"tor-hscrypto",
|
|
||||||
"tor-hsservice",
|
|
||||||
"tor-keymgr",
|
|
||||||
"tor-llcrypto",
|
|
||||||
"tor-proto",
|
|
||||||
"tor-rtcompat",
|
|
||||||
]
|
|
||||||
beta = []
|
beta = []
|
||||||
console = ["console-subscriber", "tokio/tracing"]
|
console = ["console-subscriber", "tokio/tracing"]
|
||||||
default = []
|
default = []
|
||||||
@@ -62,16 +51,6 @@ unstable = ["backtrace-on-stack-overflow"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes = { version = "0.7.5", features = ["ctr"] }
|
aes = { version = "0.7.5", features = ["ctr"] }
|
||||||
arti-client = { version = "0.33", features = [
|
|
||||||
"compression",
|
|
||||||
"ephemeral-keystore",
|
|
||||||
"experimental-api",
|
|
||||||
"onion-service-client",
|
|
||||||
"onion-service-service",
|
|
||||||
"rustls",
|
|
||||||
"static",
|
|
||||||
"tokio",
|
|
||||||
], default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
|
||||||
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",
|
||||||
@@ -100,7 +79,6 @@ console-subscriber = { version = "0.5.0", optional = true }
|
|||||||
const_format = "0.2.34"
|
const_format = "0.2.34"
|
||||||
cookie = "0.18.0"
|
cookie = "0.18.0"
|
||||||
cookie_store = "0.22.0"
|
cookie_store = "0.22.0"
|
||||||
curve25519-dalek = "4.1.3"
|
|
||||||
der = { version = "0.7.9", features = ["derive", "pem"] }
|
der = { version = "0.7.9", features = ["derive", "pem"] }
|
||||||
digest = "0.10.7"
|
digest = "0.10.7"
|
||||||
divrem = "1.0.0"
|
divrem = "1.0.0"
|
||||||
@@ -216,7 +194,6 @@ rpassword = "7.2.0"
|
|||||||
rust-argon2 = "3.0.0"
|
rust-argon2 = "3.0.0"
|
||||||
rust-i18n = "3.1.5"
|
rust-i18n = "3.1.5"
|
||||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git" }
|
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git" }
|
||||||
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"] }
|
||||||
serde_cbor = { package = "ciborium", version = "0.2.1" }
|
serde_cbor = { package = "ciborium", version = "0.2.1" }
|
||||||
@@ -244,23 +221,6 @@ 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"] }
|
||||||
tor-cell = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
|
||||||
tor-hscrypto = { version = "0.33", features = [
|
|
||||||
"full",
|
|
||||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
|
||||||
tor-hsservice = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
|
||||||
tor-keymgr = { version = "0.33", features = [
|
|
||||||
"ephemeral-keystore",
|
|
||||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
|
||||||
tor-llcrypto = { version = "0.33", features = [
|
|
||||||
"full",
|
|
||||||
], 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 = [
|
|
||||||
"rustls",
|
|
||||||
"tokio",
|
|
||||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
|
||||||
torut = "0.2.1"
|
|
||||||
tower-service = "0.3.3"
|
tower-service = "0.3.3"
|
||||||
tracing = "0.1.39"
|
tracing = "0.1.39"
|
||||||
tracing-error = "0.2.0"
|
tracing-error = "0.2.0"
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use openssl::x509::X509;
|
|||||||
use crate::db::model::DatabaseModel;
|
use crate::db::model::DatabaseModel;
|
||||||
use crate::hostname::{Hostname, generate_hostname, generate_id};
|
use crate::hostname::{Hostname, generate_hostname, generate_id};
|
||||||
use crate::net::ssl::{gen_nistp256, make_root_cert};
|
use crate::net::ssl::{gen_nistp256, make_root_cert};
|
||||||
use crate::net::tor::TorSecretKey;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
|
|
||||||
@@ -26,7 +25,6 @@ pub struct AccountInfo {
|
|||||||
pub server_id: String,
|
pub server_id: String,
|
||||||
pub hostname: Hostname,
|
pub hostname: Hostname,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub tor_keys: Vec<TorSecretKey>,
|
|
||||||
pub root_ca_key: PKey<Private>,
|
pub root_ca_key: PKey<Private>,
|
||||||
pub root_ca_cert: X509,
|
pub root_ca_cert: X509,
|
||||||
pub ssh_key: ssh_key::PrivateKey,
|
pub ssh_key: ssh_key::PrivateKey,
|
||||||
@@ -36,7 +34,6 @@ impl AccountInfo {
|
|||||||
pub fn new(password: &str, start_time: SystemTime) -> Result<Self, Error> {
|
pub fn new(password: &str, start_time: SystemTime) -> Result<Self, Error> {
|
||||||
let server_id = generate_id();
|
let server_id = generate_id();
|
||||||
let hostname = generate_hostname();
|
let hostname = generate_hostname();
|
||||||
let tor_key = vec![TorSecretKey::generate()];
|
|
||||||
let root_ca_key = gen_nistp256()?;
|
let root_ca_key = gen_nistp256()?;
|
||||||
let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?;
|
let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?;
|
||||||
let ssh_key = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::random(
|
let ssh_key = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::random(
|
||||||
@@ -48,7 +45,6 @@ impl AccountInfo {
|
|||||||
server_id,
|
server_id,
|
||||||
hostname,
|
hostname,
|
||||||
password: hash_password(password)?,
|
password: hash_password(password)?,
|
||||||
tor_keys: tor_key,
|
|
||||||
root_ca_key,
|
root_ca_key,
|
||||||
root_ca_cert,
|
root_ca_cert,
|
||||||
ssh_key,
|
ssh_key,
|
||||||
@@ -61,17 +57,6 @@ impl AccountInfo {
|
|||||||
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
||||||
let password = db.as_private().as_password().de()?;
|
let password = db.as_private().as_password().de()?;
|
||||||
let key_store = db.as_private().as_key_store();
|
let key_store = db.as_private().as_key_store();
|
||||||
let tor_addrs = db
|
|
||||||
.as_public()
|
|
||||||
.as_server_info()
|
|
||||||
.as_network()
|
|
||||||
.as_host()
|
|
||||||
.as_onions()
|
|
||||||
.de()?;
|
|
||||||
let tor_keys = tor_addrs
|
|
||||||
.into_iter()
|
|
||||||
.map(|tor_addr| key_store.as_onion().get_key(&tor_addr))
|
|
||||||
.collect::<Result<_, _>>()?;
|
|
||||||
let cert_store = key_store.as_local_certs();
|
let cert_store = key_store.as_local_certs();
|
||||||
let root_ca_key = cert_store.as_root_key().de()?.0;
|
let root_ca_key = cert_store.as_root_key().de()?.0;
|
||||||
let root_ca_cert = cert_store.as_root_cert().de()?.0;
|
let root_ca_cert = cert_store.as_root_cert().de()?.0;
|
||||||
@@ -82,7 +67,6 @@ impl AccountInfo {
|
|||||||
server_id,
|
server_id,
|
||||||
hostname,
|
hostname,
|
||||||
password,
|
password,
|
||||||
tor_keys,
|
|
||||||
root_ca_key,
|
root_ca_key,
|
||||||
root_ca_cert,
|
root_ca_cert,
|
||||||
ssh_key,
|
ssh_key,
|
||||||
@@ -97,17 +81,6 @@ impl AccountInfo {
|
|||||||
server_info
|
server_info
|
||||||
.as_pubkey_mut()
|
.as_pubkey_mut()
|
||||||
.ser(&self.ssh_key.public_key().to_openssh()?)?;
|
.ser(&self.ssh_key.public_key().to_openssh()?)?;
|
||||||
server_info
|
|
||||||
.as_network_mut()
|
|
||||||
.as_host_mut()
|
|
||||||
.as_onions_mut()
|
|
||||||
.ser(
|
|
||||||
&self
|
|
||||||
.tor_keys
|
|
||||||
.iter()
|
|
||||||
.map(|tor_key| tor_key.onion_address())
|
|
||||||
.collect(),
|
|
||||||
)?;
|
|
||||||
server_info.as_password_hash_mut().ser(&self.password)?;
|
server_info.as_password_hash_mut().ser(&self.password)?;
|
||||||
db.as_private_mut().as_password_mut().ser(&self.password)?;
|
db.as_private_mut().as_password_mut().ser(&self.password)?;
|
||||||
db.as_private_mut()
|
db.as_private_mut()
|
||||||
@@ -117,9 +90,6 @@ impl AccountInfo {
|
|||||||
.as_developer_key_mut()
|
.as_developer_key_mut()
|
||||||
.ser(Pem::new_ref(&self.developer_key))?;
|
.ser(Pem::new_ref(&self.developer_key))?;
|
||||||
let key_store = db.as_private_mut().as_key_store_mut();
|
let key_store = db.as_private_mut().as_key_store_mut();
|
||||||
for tor_key in &self.tor_keys {
|
|
||||||
key_store.as_onion_mut().insert_key(tor_key)?;
|
|
||||||
}
|
|
||||||
let cert_store = key_store.as_local_certs_mut();
|
let cert_store = key_store.as_local_certs_mut();
|
||||||
if cert_store.as_root_cert().de()?.0 != self.root_ca_cert {
|
if cert_store.as_root_cert().de()?.0 != self.root_ca_cert {
|
||||||
cert_store
|
cert_store
|
||||||
@@ -148,11 +118,5 @@ impl AccountInfo {
|
|||||||
self.hostname.no_dot_host_name(),
|
self.hostname.no_dot_host_name(),
|
||||||
self.hostname.local_domain_name(),
|
self.hostname.local_domain_name(),
|
||||||
]
|
]
|
||||||
.into_iter()
|
|
||||||
.chain(
|
|
||||||
self.tor_keys
|
|
||||||
.iter()
|
|
||||||
.map(|k| InternedString::from_display(&k.onion_address())),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ use ssh_key::private::Ed25519Keypair;
|
|||||||
|
|
||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::hostname::{Hostname, generate_hostname, generate_id};
|
use crate::hostname::{Hostname, generate_hostname, generate_id};
|
||||||
use crate::net::tor::TorSecretKey;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::crypto::ed25519_expand_key;
|
|
||||||
use crate::util::serde::{Base32, Base64, Pem};
|
use crate::util::serde::{Base32, Base64, Pem};
|
||||||
|
|
||||||
pub struct OsBackup {
|
pub struct OsBackup {
|
||||||
@@ -85,10 +83,6 @@ impl OsBackupV0 {
|
|||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
ssh_key::Algorithm::Ed25519,
|
ssh_key::Algorithm::Ed25519,
|
||||||
)?,
|
)?,
|
||||||
tor_keys: TorSecretKey::from_bytes(self.tor_key.0)
|
|
||||||
.ok()
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
developer_key: ed25519_dalek::SigningKey::generate(
|
developer_key: ed25519_dalek::SigningKey::generate(
|
||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
),
|
),
|
||||||
@@ -119,10 +113,6 @@ impl OsBackupV1 {
|
|||||||
root_ca_key: self.root_ca_key.0,
|
root_ca_key: self.root_ca_key.0,
|
||||||
root_ca_cert: self.root_ca_cert.0,
|
root_ca_cert: self.root_ca_cert.0,
|
||||||
ssh_key: ssh_key::PrivateKey::from(Ed25519Keypair::from_seed(&self.net_key.0)),
|
ssh_key: ssh_key::PrivateKey::from(Ed25519Keypair::from_seed(&self.net_key.0)),
|
||||||
tor_keys: TorSecretKey::from_bytes(ed25519_expand_key(&self.net_key.0))
|
|
||||||
.ok()
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
developer_key: ed25519_dalek::SigningKey::from_bytes(&self.net_key),
|
developer_key: ed25519_dalek::SigningKey::from_bytes(&self.net_key),
|
||||||
},
|
},
|
||||||
ui: self.ui,
|
ui: self.ui,
|
||||||
@@ -140,7 +130,6 @@ struct OsBackupV2 {
|
|||||||
root_ca_key: Pem<PKey<Private>>, // PEM Encoded OpenSSL Key
|
root_ca_key: Pem<PKey<Private>>, // PEM Encoded OpenSSL Key
|
||||||
root_ca_cert: Pem<X509>, // PEM Encoded OpenSSL X509 Certificate
|
root_ca_cert: Pem<X509>, // PEM Encoded OpenSSL X509 Certificate
|
||||||
ssh_key: Pem<ssh_key::PrivateKey>, // PEM Encoded OpenSSH Key
|
ssh_key: Pem<ssh_key::PrivateKey>, // PEM Encoded OpenSSH Key
|
||||||
tor_keys: Vec<TorSecretKey>, // Base64 Encoded Ed25519 Expanded Secret Key
|
|
||||||
compat_s9pk_key: Pem<ed25519_dalek::SigningKey>, // PEM Encoded ED25519 Key
|
compat_s9pk_key: Pem<ed25519_dalek::SigningKey>, // PEM Encoded ED25519 Key
|
||||||
ui: Value, // JSON Value
|
ui: Value, // JSON Value
|
||||||
}
|
}
|
||||||
@@ -154,7 +143,6 @@ impl OsBackupV2 {
|
|||||||
root_ca_key: self.root_ca_key.0,
|
root_ca_key: self.root_ca_key.0,
|
||||||
root_ca_cert: self.root_ca_cert.0,
|
root_ca_cert: self.root_ca_cert.0,
|
||||||
ssh_key: self.ssh_key.0,
|
ssh_key: self.ssh_key.0,
|
||||||
tor_keys: self.tor_keys,
|
|
||||||
developer_key: self.compat_s9pk_key.0,
|
developer_key: self.compat_s9pk_key.0,
|
||||||
},
|
},
|
||||||
ui: self.ui,
|
ui: self.ui,
|
||||||
@@ -167,7 +155,6 @@ impl OsBackupV2 {
|
|||||||
root_ca_key: Pem(backup.account.root_ca_key.clone()),
|
root_ca_key: Pem(backup.account.root_ca_key.clone()),
|
||||||
root_ca_cert: Pem(backup.account.root_ca_cert.clone()),
|
root_ca_cert: Pem(backup.account.root_ca_cert.clone()),
|
||||||
ssh_key: Pem(backup.account.ssh_key.clone()),
|
ssh_key: Pem(backup.account.ssh_key.clone()),
|
||||||
tor_keys: backup.account.tor_keys.clone(),
|
|
||||||
compat_s9pk_key: Pem(backup.account.developer_key.clone()),
|
compat_s9pk_key: Pem(backup.account.developer_key.clone()),
|
||||||
ui: backup.ui.clone(),
|
ui: backup.ui.clone(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ impl Public {
|
|||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.collect(),
|
||||||
onions: account.tor_keys.iter().map(|k| k.onion_address()).collect(),
|
|
||||||
public_domains: BTreeMap::new(),
|
public_domains: BTreeMap::new(),
|
||||||
private_domains: BTreeSet::new(),
|
private_domains: BTreeSet::new(),
|
||||||
hostname_info: BTreeMap::new(),
|
hostname_info: BTreeMap::new(),
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ pub enum ErrorKind {
|
|||||||
ParseUrl = 19,
|
ParseUrl = 19,
|
||||||
DiskNotAvailable = 20,
|
DiskNotAvailable = 20,
|
||||||
BlockDevice = 21,
|
BlockDevice = 21,
|
||||||
InvalidOnionAddress = 22,
|
// InvalidOnionAddress = 22,
|
||||||
Pack = 23,
|
Pack = 23,
|
||||||
ValidateS9pk = 24,
|
ValidateS9pk = 24,
|
||||||
DiskCorrupted = 25, // Remove
|
DiskCorrupted = 25, // Remove
|
||||||
Tor = 26,
|
// Tor = 26,
|
||||||
ConfigGen = 27,
|
ConfigGen = 27,
|
||||||
ParseNumber = 28,
|
ParseNumber = 28,
|
||||||
Database = 29,
|
Database = 29,
|
||||||
@@ -126,11 +126,11 @@ impl ErrorKind {
|
|||||||
ParseUrl => t!("error.parse-url"),
|
ParseUrl => t!("error.parse-url"),
|
||||||
DiskNotAvailable => t!("error.disk-not-available"),
|
DiskNotAvailable => t!("error.disk-not-available"),
|
||||||
BlockDevice => t!("error.block-device"),
|
BlockDevice => t!("error.block-device"),
|
||||||
InvalidOnionAddress => t!("error.invalid-onion-address"),
|
// InvalidOnionAddress => t!("error.invalid-onion-address"),
|
||||||
Pack => t!("error.pack"),
|
Pack => t!("error.pack"),
|
||||||
ValidateS9pk => t!("error.validate-s9pk"),
|
ValidateS9pk => t!("error.validate-s9pk"),
|
||||||
DiskCorrupted => t!("error.disk-corrupted"), // Remove
|
DiskCorrupted => t!("error.disk-corrupted"), // Remove
|
||||||
Tor => t!("error.tor"),
|
// Tor => t!("error.tor"),
|
||||||
ConfigGen => t!("error.config-gen"),
|
ConfigGen => t!("error.config-gen"),
|
||||||
ParseNumber => t!("error.parse-number"),
|
ParseNumber => t!("error.parse-number"),
|
||||||
Database => t!("error.database"),
|
Database => t!("error.database"),
|
||||||
@@ -370,17 +370,6 @@ impl From<reqwest::Error> for Error {
|
|||||||
Error::new(e, kind)
|
Error::new(e, kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "arti")]
|
|
||||||
impl From<arti_client::Error> for Error {
|
|
||||||
fn from(e: arti_client::Error) -> Self {
|
|
||||||
Error::new(e, ErrorKind::Tor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<torut::control::ConnError> for Error {
|
|
||||||
fn from(e: torut::control::ConnError) -> Self {
|
|
||||||
Error::new(e, ErrorKind::Tor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<zbus::Error> for Error {
|
impl From<zbus::Error> for Error {
|
||||||
fn from(e: zbus::Error) -> Self {
|
fn from(e: zbus::Error) -> Self {
|
||||||
Error::new(e, ErrorKind::DBus)
|
Error::new(e, ErrorKind::DBus)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use crate::context::{CliContext, RpcContext};
|
|||||||
use crate::db::model::DatabaseModel;
|
use crate::db::model::DatabaseModel;
|
||||||
use crate::net::acme::AcmeProvider;
|
use crate::net::acme::AcmeProvider;
|
||||||
use crate::net::host::{HostApiKind, all_hosts};
|
use crate::net::host::{HostApiKind, all_hosts};
|
||||||
use crate::net::tor::OnionAddress;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||||
|
|
||||||
@@ -21,9 +20,6 @@ use crate::util::serde::{HandlerExtSerde, display_serializable};
|
|||||||
#[serde(rename_all_fields = "camelCase")]
|
#[serde(rename_all_fields = "camelCase")]
|
||||||
#[serde(tag = "kind")]
|
#[serde(tag = "kind")]
|
||||||
pub enum HostAddress {
|
pub enum HostAddress {
|
||||||
Onion {
|
|
||||||
address: OnionAddress,
|
|
||||||
},
|
|
||||||
Domain {
|
Domain {
|
||||||
address: InternedString,
|
address: InternedString,
|
||||||
public: Option<PublicDomainConfig>,
|
public: Option<PublicDomainConfig>,
|
||||||
@@ -38,18 +34,7 @@ pub struct PublicDomainConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> {
|
fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> {
|
||||||
let mut onions = BTreeSet::<OnionAddress>::new();
|
|
||||||
let mut domains = BTreeSet::<InternedString>::new();
|
let mut domains = BTreeSet::<InternedString>::new();
|
||||||
let check_onion = |onions: &mut BTreeSet<OnionAddress>, onion: OnionAddress| {
|
|
||||||
if onions.contains(&onion) {
|
|
||||||
return Err(Error::new(
|
|
||||||
eyre!("onion address {onion} is already in use"),
|
|
||||||
ErrorKind::InvalidRequest,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
onions.insert(onion);
|
|
||||||
Ok(())
|
|
||||||
};
|
|
||||||
let check_domain = |domains: &mut BTreeSet<InternedString>, domain: InternedString| {
|
let check_domain = |domains: &mut BTreeSet<InternedString>, domain: InternedString| {
|
||||||
if domains.contains(&domain) {
|
if domains.contains(&domain) {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
@@ -68,9 +53,6 @@ fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> {
|
|||||||
not_in_use.push(host);
|
not_in_use.push(host);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for onion in host.as_onions().de()? {
|
|
||||||
check_onion(&mut onions, onion)?;
|
|
||||||
}
|
|
||||||
let public = host.as_public_domains().keys()?;
|
let public = host.as_public_domains().keys()?;
|
||||||
for domain in &public {
|
for domain in &public {
|
||||||
check_domain(&mut domains, domain.clone())?;
|
check_domain(&mut domains, domain.clone())?;
|
||||||
@@ -82,16 +64,11 @@ fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for host in not_in_use {
|
for host in not_in_use {
|
||||||
host.as_onions_mut()
|
|
||||||
.mutate(|o| Ok(o.retain(|o| !onions.contains(o))))?;
|
|
||||||
host.as_public_domains_mut()
|
host.as_public_domains_mut()
|
||||||
.mutate(|d| Ok(d.retain(|d, _| !domains.contains(d))))?;
|
.mutate(|d| Ok(d.retain(|d, _| !domains.contains(d))))?;
|
||||||
host.as_private_domains_mut()
|
host.as_private_domains_mut()
|
||||||
.mutate(|d| Ok(d.retain(|d| !domains.contains(d))))?;
|
.mutate(|d| Ok(d.retain(|d| !domains.contains(d))))?;
|
||||||
|
|
||||||
for onion in host.as_onions().de()? {
|
|
||||||
check_onion(&mut onions, onion)?;
|
|
||||||
}
|
|
||||||
let public = host.as_public_domains().keys()?;
|
let public = host.as_public_domains().keys()?;
|
||||||
for domain in &public {
|
for domain in &public {
|
||||||
check_domain(&mut domains, domain.clone())?;
|
check_domain(&mut domains, domain.clone())?;
|
||||||
@@ -159,29 +136,6 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
|||||||
)
|
)
|
||||||
.with_inherited(Kind::inheritance),
|
.with_inherited(Kind::inheritance),
|
||||||
)
|
)
|
||||||
.subcommand(
|
|
||||||
"onion",
|
|
||||||
ParentHandler::<C, Empty, Kind::Inheritance>::new()
|
|
||||||
.subcommand(
|
|
||||||
"add",
|
|
||||||
from_fn_async(add_onion::<Kind>)
|
|
||||||
.with_metadata("sync_db", Value::Bool(true))
|
|
||||||
.with_inherited(|_, a| a)
|
|
||||||
.no_display()
|
|
||||||
.with_about("about.add-address-to-host")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"remove",
|
|
||||||
from_fn_async(remove_onion::<Kind>)
|
|
||||||
.with_metadata("sync_db", Value::Bool(true))
|
|
||||||
.with_inherited(|_, a| a)
|
|
||||||
.no_display()
|
|
||||||
.with_about("about.remove-address-from-host")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.with_inherited(Kind::inheritance),
|
|
||||||
)
|
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"list",
|
"list",
|
||||||
from_fn_async(list_addresses::<Kind>)
|
from_fn_async(list_addresses::<Kind>)
|
||||||
@@ -199,9 +153,6 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
|||||||
table.add_row(row![bc => "ADDRESS", "PUBLIC", "ACME PROVIDER"]);
|
table.add_row(row![bc => "ADDRESS", "PUBLIC", "ACME PROVIDER"]);
|
||||||
for address in &res {
|
for address in &res {
|
||||||
match address {
|
match address {
|
||||||
HostAddress::Onion { address } => {
|
|
||||||
table.add_row(row![address, true, "N/A"]);
|
|
||||||
}
|
|
||||||
HostAddress::Domain {
|
HostAddress::Domain {
|
||||||
address,
|
address,
|
||||||
public: Some(PublicDomainConfig { gateway, acme }),
|
public: Some(PublicDomainConfig { gateway, acme }),
|
||||||
@@ -351,55 +302,6 @@ pub async fn remove_private_domain<Kind: HostApiKind>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser)]
|
|
||||||
pub struct OnionParams {
|
|
||||||
#[arg(help = "help.arg.onion-address")]
|
|
||||||
pub onion: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add_onion<Kind: HostApiKind>(
|
|
||||||
ctx: RpcContext,
|
|
||||||
OnionParams { onion }: OnionParams,
|
|
||||||
inheritance: Kind::Inheritance,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let onion = onion.parse::<OnionAddress>()?;
|
|
||||||
ctx.db
|
|
||||||
.mutate(|db| {
|
|
||||||
db.as_private().as_key_store().as_onion().get_key(&onion)?;
|
|
||||||
|
|
||||||
Kind::host_for(&inheritance, db)?
|
|
||||||
.as_onions_mut()
|
|
||||||
.mutate(|a| Ok(a.insert(onion)))?;
|
|
||||||
handle_duplicates(db)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.result?;
|
|
||||||
|
|
||||||
Kind::sync_host(&ctx, inheritance).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn remove_onion<Kind: HostApiKind>(
|
|
||||||
ctx: RpcContext,
|
|
||||||
OnionParams { onion }: OnionParams,
|
|
||||||
inheritance: Kind::Inheritance,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let onion = onion.parse::<OnionAddress>()?;
|
|
||||||
ctx.db
|
|
||||||
.mutate(|db| {
|
|
||||||
Kind::host_for(&inheritance, db)?
|
|
||||||
.as_onions_mut()
|
|
||||||
.mutate(|a| Ok(a.remove(&onion)))
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.result?;
|
|
||||||
|
|
||||||
Kind::sync_host(&ctx, inheritance).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list_addresses<Kind: HostApiKind>(
|
pub async fn list_addresses<Kind: HostApiKind>(
|
||||||
ctx: RpcContext,
|
ctx: RpcContext,
|
||||||
_: Empty,
|
_: Empty,
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ use crate::net::forward::AvailablePorts;
|
|||||||
use crate::net::host::address::{HostAddress, PublicDomainConfig, address_api};
|
use crate::net::host::address::{HostAddress, PublicDomainConfig, address_api};
|
||||||
use crate::net::host::binding::{BindInfo, BindOptions, binding};
|
use crate::net::host::binding::{BindInfo, BindOptions, binding};
|
||||||
use crate::net::service_interface::HostnameInfo;
|
use crate::net::service_interface::HostnameInfo;
|
||||||
use crate::net::tor::OnionAddress;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{HostId, PackageId};
|
use crate::{HostId, PackageId};
|
||||||
|
|
||||||
@@ -28,8 +27,6 @@ pub mod binding;
|
|||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct Host {
|
pub struct Host {
|
||||||
pub bindings: BTreeMap<u16, BindInfo>,
|
pub bindings: BTreeMap<u16, BindInfo>,
|
||||||
#[ts(type = "string[]")]
|
|
||||||
pub onions: BTreeSet<OnionAddress>,
|
|
||||||
pub public_domains: BTreeMap<InternedString, PublicDomainConfig>,
|
pub public_domains: BTreeMap<InternedString, PublicDomainConfig>,
|
||||||
pub private_domains: BTreeSet<InternedString>,
|
pub private_domains: BTreeSet<InternedString>,
|
||||||
/// COMPUTED: NetService::update
|
/// COMPUTED: NetService::update
|
||||||
@@ -46,19 +43,13 @@ impl Host {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
pub fn addresses<'a>(&'a self) -> impl Iterator<Item = HostAddress> + 'a {
|
pub fn addresses<'a>(&'a self) -> impl Iterator<Item = HostAddress> + 'a {
|
||||||
self.onions
|
self.public_domains
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.map(|(address, config)| HostAddress::Domain {
|
||||||
.map(|address| HostAddress::Onion { address })
|
address: address.clone(),
|
||||||
.chain(
|
public: Some(config.clone()),
|
||||||
self.public_domains
|
private: self.private_domains.contains(address),
|
||||||
.iter()
|
})
|
||||||
.map(|(address, config)| HostAddress::Domain {
|
|
||||||
address: address.clone(),
|
|
||||||
public: Some(config.clone()),
|
|
||||||
private: self.private_domains.contains(address),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.chain(
|
.chain(
|
||||||
self.private_domains
|
self.private_domains
|
||||||
.iter()
|
.iter()
|
||||||
@@ -112,22 +103,7 @@ pub fn host_for<'a>(
|
|||||||
.as_hosts_mut(),
|
.as_hosts_mut(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
let tor_key = if host_info(db, package_id)?.as_idx(host_id).is_none() {
|
host_info(db, package_id)?.upsert(host_id, || Ok(Host::new()))
|
||||||
Some(
|
|
||||||
db.as_private_mut()
|
|
||||||
.as_key_store_mut()
|
|
||||||
.as_onion_mut()
|
|
||||||
.new_key()?,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
host_info(db, package_id)?.upsert(host_id, || {
|
|
||||||
let mut h = Host::new();
|
|
||||||
h.onions
|
|
||||||
.insert(tor_key.or_not_found("generated tor key")?.onion_address());
|
|
||||||
Ok(h)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all_hosts(db: &mut DatabaseModel) -> impl Iterator<Item = Result<&mut Model<Host>, Error>> {
|
pub fn all_hosts(db: &mut DatabaseModel) -> impl Iterator<Item = Result<&mut Model<Host>, Error>> {
|
||||||
|
|||||||
@@ -3,28 +3,21 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::net::acme::AcmeCertStore;
|
use crate::net::acme::AcmeCertStore;
|
||||||
use crate::net::ssl::CertStore;
|
use crate::net::ssl::CertStore;
|
||||||
use crate::net::tor::OnionStore;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct KeyStore {
|
pub struct KeyStore {
|
||||||
pub onion: OnionStore,
|
|
||||||
pub local_certs: CertStore,
|
pub local_certs: CertStore,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub acme: AcmeCertStore,
|
pub acme: AcmeCertStore,
|
||||||
}
|
}
|
||||||
impl KeyStore {
|
impl KeyStore {
|
||||||
pub fn new(account: &AccountInfo) -> Result<Self, Error> {
|
pub fn new(account: &AccountInfo) -> Result<Self, Error> {
|
||||||
let mut res = Self {
|
Ok(Self {
|
||||||
onion: OnionStore::new(),
|
|
||||||
local_certs: CertStore::new(account)?,
|
local_certs: CertStore::new(account)?,
|
||||||
acme: AcmeCertStore::new(),
|
acme: AcmeCertStore::new(),
|
||||||
};
|
})
|
||||||
for tor_key in account.tor_keys.iter().cloned() {
|
|
||||||
res.onion.insert(tor_key);
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ pub mod socks;
|
|||||||
pub mod ssl;
|
pub mod ssl;
|
||||||
pub mod static_server;
|
pub mod static_server;
|
||||||
pub mod tls;
|
pub mod tls;
|
||||||
pub mod tor;
|
|
||||||
pub mod tunnel;
|
pub mod tunnel;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod vhost;
|
pub mod vhost;
|
||||||
@@ -23,7 +22,6 @@ pub mod wifi;
|
|||||||
|
|
||||||
pub fn net_api<C: Context>() -> ParentHandler<C> {
|
pub fn net_api<C: Context>() -> ParentHandler<C> {
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
.subcommand("tor", tor::tor_api::<C>().with_about("about.tor-commands"))
|
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"acme",
|
"acme",
|
||||||
acme::acme_api::<C>().with_about("about.setup-acme-certificate"),
|
acme::acme_api::<C>().with_about("about.setup-acme-certificate"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
|
|||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use imbl::{OrdMap, vector};
|
use imbl::vector;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use ipnet::IpNet;
|
use ipnet::IpNet;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@@ -19,24 +19,22 @@ use crate::net::dns::DnsController;
|
|||||||
use crate::net::forward::{InterfacePortForwardController, START9_BRIDGE_IFACE, add_iptables_rule};
|
use crate::net::forward::{InterfacePortForwardController, START9_BRIDGE_IFACE, add_iptables_rule};
|
||||||
use crate::net::gateway::{
|
use crate::net::gateway::{
|
||||||
AndFilter, DynInterfaceFilter, IdFilter, InterfaceFilter, NetworkInterfaceController, OrFilter,
|
AndFilter, DynInterfaceFilter, IdFilter, InterfaceFilter, NetworkInterfaceController, OrFilter,
|
||||||
PublicFilter, SecureFilter, TypeFilter,
|
PublicFilter, SecureFilter,
|
||||||
};
|
};
|
||||||
use crate::net::host::address::HostAddress;
|
use crate::net::host::address::HostAddress;
|
||||||
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
|
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
|
||||||
use crate::net::host::{Host, Hosts, host_for};
|
use crate::net::host::{Host, Hosts, host_for};
|
||||||
use crate::net::service_interface::{GatewayInfo, HostnameInfo, IpHostname, OnionHostname};
|
use crate::net::service_interface::{GatewayInfo, HostnameInfo, IpHostname};
|
||||||
use crate::net::socks::SocksController;
|
use crate::net::socks::SocksController;
|
||||||
use crate::net::tor::{OnionAddress, TorController, TorSecretKey};
|
|
||||||
use crate::net::utils::ipv6_is_local;
|
use crate::net::utils::ipv6_is_local;
|
||||||
use crate::net::vhost::{AlpnInfo, DynVHostTarget, ProxyTarget, VHostController};
|
use crate::net::vhost::{AlpnInfo, DynVHostTarget, ProxyTarget, VHostController};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::service::effects::callbacks::ServiceCallbacks;
|
use crate::service::effects::callbacks::ServiceCallbacks;
|
||||||
use crate::util::serde::MaybeUtf8String;
|
use crate::util::serde::MaybeUtf8String;
|
||||||
use crate::{GatewayId, HOST_IP, HostId, OptionExt, PackageId};
|
use crate::{HOST_IP, HostId, OptionExt, PackageId};
|
||||||
|
|
||||||
pub struct NetController {
|
pub struct NetController {
|
||||||
pub(crate) db: TypedPatchDb<Database>,
|
pub(crate) db: TypedPatchDb<Database>,
|
||||||
pub(super) tor: TorController,
|
|
||||||
pub(super) vhost: VHostController,
|
pub(super) vhost: VHostController,
|
||||||
pub(super) tls_client_config: Arc<TlsClientConfig>,
|
pub(super) tls_client_config: Arc<TlsClientConfig>,
|
||||||
pub(crate) net_iface: Arc<NetworkInterfaceController>,
|
pub(crate) net_iface: Arc<NetworkInterfaceController>,
|
||||||
@@ -54,8 +52,7 @@ impl NetController {
|
|||||||
socks_listen: SocketAddr,
|
socks_listen: SocketAddr,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let net_iface = Arc::new(NetworkInterfaceController::new(db.clone()));
|
let net_iface = Arc::new(NetworkInterfaceController::new(db.clone()));
|
||||||
let tor = TorController::new()?;
|
let socks = SocksController::new(socks_listen)?;
|
||||||
let socks = SocksController::new(socks_listen, tor.clone())?;
|
|
||||||
let crypto_provider = Arc::new(tokio_rustls::rustls::crypto::ring::default_provider());
|
let crypto_provider = Arc::new(tokio_rustls::rustls::crypto::ring::default_provider());
|
||||||
let tls_client_config = Arc::new(crate::net::tls::client_config(
|
let tls_client_config = Arc::new(crate::net::tls::client_config(
|
||||||
crypto_provider.clone(),
|
crypto_provider.clone(),
|
||||||
@@ -87,7 +84,6 @@ impl NetController {
|
|||||||
.await?;
|
.await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
db: db.clone(),
|
db: db.clone(),
|
||||||
tor,
|
|
||||||
vhost: VHostController::new(db.clone(), net_iface.clone(), crypto_provider),
|
vhost: VHostController::new(db.clone(), net_iface.clone(), crypto_provider),
|
||||||
tls_client_config,
|
tls_client_config,
|
||||||
dns: DnsController::init(db, &net_iface.watcher).await?,
|
dns: DnsController::init(db, &net_iface.watcher).await?,
|
||||||
@@ -168,7 +164,6 @@ struct HostBinds {
|
|||||||
forwards: BTreeMap<u16, (SocketAddrV4, DynInterfaceFilter, Arc<()>)>,
|
forwards: BTreeMap<u16, (SocketAddrV4, DynInterfaceFilter, Arc<()>)>,
|
||||||
vhosts: BTreeMap<(Option<InternedString>, u16), (ProxyTarget, Arc<()>)>,
|
vhosts: BTreeMap<(Option<InternedString>, u16), (ProxyTarget, Arc<()>)>,
|
||||||
private_dns: BTreeMap<InternedString, Arc<()>>,
|
private_dns: BTreeMap<InternedString, Arc<()>>,
|
||||||
tor: BTreeMap<OnionAddress, (OrdMap<u16, SocketAddr>, Vec<Arc<()>>)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NetServiceData {
|
pub struct NetServiceData {
|
||||||
@@ -260,8 +255,6 @@ impl NetServiceData {
|
|||||||
let mut forwards: BTreeMap<u16, (SocketAddrV4, DynInterfaceFilter)> = BTreeMap::new();
|
let mut forwards: BTreeMap<u16, (SocketAddrV4, DynInterfaceFilter)> = BTreeMap::new();
|
||||||
let mut vhosts: BTreeMap<(Option<InternedString>, u16), ProxyTarget> = BTreeMap::new();
|
let mut vhosts: BTreeMap<(Option<InternedString>, u16), ProxyTarget> = BTreeMap::new();
|
||||||
let mut private_dns: BTreeSet<InternedString> = BTreeSet::new();
|
let mut private_dns: BTreeSet<InternedString> = BTreeSet::new();
|
||||||
let mut tor: BTreeMap<OnionAddress, (TorSecretKey, OrdMap<u16, SocketAddr>)> =
|
|
||||||
BTreeMap::new();
|
|
||||||
let mut hostname_info: BTreeMap<u16, Vec<HostnameInfo>> = BTreeMap::new();
|
let mut hostname_info: BTreeMap<u16, Vec<HostnameInfo>> = BTreeMap::new();
|
||||||
let binds = self.binds.entry(id.clone()).or_default();
|
let binds = self.binds.entry(id.clone()).or_default();
|
||||||
|
|
||||||
@@ -308,29 +301,6 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
for address in host.addresses() {
|
for address in host.addresses() {
|
||||||
match address {
|
match address {
|
||||||
HostAddress::Onion { address } => {
|
|
||||||
let hostname = InternedString::from_display(&address);
|
|
||||||
if hostnames.insert(hostname.clone()) {
|
|
||||||
vhosts.insert(
|
|
||||||
(Some(hostname), external),
|
|
||||||
ProxyTarget {
|
|
||||||
filter: OrFilter(
|
|
||||||
TypeFilter(NetworkInterfaceType::Loopback),
|
|
||||||
IdFilter(GatewayId::from(InternedString::from(
|
|
||||||
START9_BRIDGE_IFACE,
|
|
||||||
))),
|
|
||||||
)
|
|
||||||
.into_dyn(),
|
|
||||||
acme: None,
|
|
||||||
addr,
|
|
||||||
add_x_forwarded_headers: ssl.add_x_forwarded_headers,
|
|
||||||
connect_ssl: connect_ssl
|
|
||||||
.clone()
|
|
||||||
.map(|_| ctrl.tls_client_config.clone()),
|
|
||||||
},
|
|
||||||
); // TODO: wrap onion ssl stream directly in tor ctrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HostAddress::Domain {
|
HostAddress::Domain {
|
||||||
address,
|
address,
|
||||||
public,
|
public,
|
||||||
@@ -615,66 +585,6 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TorHostnamePorts {
|
|
||||||
non_ssl: Option<u16>,
|
|
||||||
ssl: Option<u16>,
|
|
||||||
}
|
|
||||||
let mut tor_hostname_ports = BTreeMap::<u16, TorHostnamePorts>::new();
|
|
||||||
let mut tor_binds = OrdMap::<u16, SocketAddr>::new();
|
|
||||||
for (internal, info) in &host.bindings {
|
|
||||||
if !info.enabled {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
tor_binds.insert(
|
|
||||||
info.options.preferred_external_port,
|
|
||||||
SocketAddr::from((self.ip, *internal)),
|
|
||||||
);
|
|
||||||
if let (Some(ssl), Some(ssl_internal)) =
|
|
||||||
(&info.options.add_ssl, info.net.assigned_ssl_port)
|
|
||||||
{
|
|
||||||
tor_binds.insert(
|
|
||||||
ssl.preferred_external_port,
|
|
||||||
SocketAddr::from(([127, 0, 0, 1], ssl_internal)),
|
|
||||||
);
|
|
||||||
tor_hostname_ports.insert(
|
|
||||||
*internal,
|
|
||||||
TorHostnamePorts {
|
|
||||||
non_ssl: Some(info.options.preferred_external_port)
|
|
||||||
.filter(|p| *p != ssl.preferred_external_port),
|
|
||||||
ssl: Some(ssl.preferred_external_port),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
tor_hostname_ports.insert(
|
|
||||||
*internal,
|
|
||||||
TorHostnamePorts {
|
|
||||||
non_ssl: Some(info.options.preferred_external_port),
|
|
||||||
ssl: None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for tor_addr in host.onions.iter() {
|
|
||||||
let key = peek
|
|
||||||
.as_private()
|
|
||||||
.as_key_store()
|
|
||||||
.as_onion()
|
|
||||||
.get_key(tor_addr)?;
|
|
||||||
tor.insert(key.onion_address(), (key, tor_binds.clone()));
|
|
||||||
for (internal, ports) in &tor_hostname_ports {
|
|
||||||
let mut bind_hostname_info = hostname_info.remove(internal).unwrap_or_default();
|
|
||||||
bind_hostname_info.push(HostnameInfo::Onion {
|
|
||||||
hostname: OnionHostname {
|
|
||||||
value: InternedString::from_display(tor_addr),
|
|
||||||
port: ports.non_ssl,
|
|
||||||
ssl_port: ports.ssl,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
hostname_info.insert(*internal, bind_hostname_info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let all = binds
|
let all = binds
|
||||||
.forwards
|
.forwards
|
||||||
.keys()
|
.keys()
|
||||||
@@ -763,48 +673,9 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
ctrl.dns.gc_private_domains(&rm)?;
|
ctrl.dns.gc_private_domains(&rm)?;
|
||||||
|
|
||||||
let all = binds
|
|
||||||
.tor
|
|
||||||
.keys()
|
|
||||||
.chain(tor.keys())
|
|
||||||
.cloned()
|
|
||||||
.collect::<BTreeSet<_>>();
|
|
||||||
for onion in all {
|
|
||||||
let mut prev = binds.tor.remove(&onion);
|
|
||||||
if let Some((key, tor_binds)) = tor.remove(&onion).filter(|(_, b)| !b.is_empty()) {
|
|
||||||
prev = prev.filter(|(b, _)| b == &tor_binds);
|
|
||||||
binds.tor.insert(
|
|
||||||
onion,
|
|
||||||
if let Some(prev) = prev {
|
|
||||||
prev
|
|
||||||
} else {
|
|
||||||
let service = ctrl.tor.service(key)?;
|
|
||||||
let rcs = service.proxy_all(tor_binds.iter().map(|(k, v)| (*k, *v)));
|
|
||||||
(tor_binds, rcs)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if let Some((_, rc)) = prev {
|
|
||||||
drop(rc);
|
|
||||||
ctrl.tor.gc(Some(onion)).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = ctrl
|
|
||||||
.db
|
|
||||||
.mutate(|db| {
|
|
||||||
host_for(db, self.id.as_ref(), &id)?
|
|
||||||
.as_hostname_info_mut()
|
|
||||||
.ser(&hostname_info)
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
res.result?;
|
|
||||||
if let Some(pkg_id) = self.id.as_ref() {
|
if let Some(pkg_id) = self.id.as_ref() {
|
||||||
if res.revision.is_some() {
|
if let Some(cbs) = ctrl.callbacks.get_host_info(&(pkg_id.clone(), id)) {
|
||||||
if let Some(cbs) = ctrl.callbacks.get_host_info(&(pkg_id.clone(), id)) {
|
cbs.call(vector![]).await?;
|
||||||
cbs.call(vector![]).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -17,15 +17,11 @@ pub enum HostnameInfo {
|
|||||||
public: bool,
|
public: bool,
|
||||||
hostname: IpHostname,
|
hostname: IpHostname,
|
||||||
},
|
},
|
||||||
Onion {
|
|
||||||
hostname: OnionHostname,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
impl HostnameInfo {
|
impl HostnameInfo {
|
||||||
pub fn to_san_hostname(&self) -> InternedString {
|
pub fn to_san_hostname(&self) -> InternedString {
|
||||||
match self {
|
match self {
|
||||||
Self::Ip { hostname, .. } => hostname.to_san_hostname(),
|
Self::Ip { hostname, .. } => hostname.to_san_hostname(),
|
||||||
Self::Onion { hostname } => hostname.to_san_hostname(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,21 +35,6 @@ pub struct GatewayInfo {
|
|||||||
pub public: bool,
|
pub public: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
|
||||||
#[ts(export)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct OnionHostname {
|
|
||||||
#[ts(type = "string")]
|
|
||||||
pub value: InternedString,
|
|
||||||
pub port: Option<u16>,
|
|
||||||
pub ssl_port: Option<u16>,
|
|
||||||
}
|
|
||||||
impl OnionHostname {
|
|
||||||
pub fn to_san_hostname(&self) -> InternedString {
|
|
||||||
self.value.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use socks5_impl::server::{AuthAdaptor, ClientConnection, Server};
|
|||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
use crate::HOST_IP;
|
use crate::HOST_IP;
|
||||||
use crate::net::tor::TorController;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::actor::background::BackgroundJobQueue;
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
use crate::util::future::NonDetachingJoinHandle;
|
use crate::util::future::NonDetachingJoinHandle;
|
||||||
@@ -22,7 +21,7 @@ pub struct SocksController {
|
|||||||
_thread: NonDetachingJoinHandle<()>,
|
_thread: NonDetachingJoinHandle<()>,
|
||||||
}
|
}
|
||||||
impl SocksController {
|
impl SocksController {
|
||||||
pub fn new(listen: SocketAddr, tor: TorController) -> Result<Self, Error> {
|
pub fn new(listen: SocketAddr) -> Result<Self, Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
_thread: tokio::spawn(async move {
|
_thread: tokio::spawn(async move {
|
||||||
let auth: AuthAdaptor<()> = Arc::new(NoAuth);
|
let auth: AuthAdaptor<()> = Arc::new(NoAuth);
|
||||||
@@ -45,7 +44,6 @@ impl SocksController {
|
|||||||
loop {
|
loop {
|
||||||
match server.accept().await {
|
match server.accept().await {
|
||||||
Ok((stream, _)) => {
|
Ok((stream, _)) => {
|
||||||
let tor = tor.clone();
|
|
||||||
bg.add_job(async move {
|
bg.add_job(async move {
|
||||||
if let Err(e) = async {
|
if let Err(e) = async {
|
||||||
match stream
|
match stream
|
||||||
@@ -57,40 +55,6 @@ impl SocksController {
|
|||||||
.await
|
.await
|
||||||
.with_kind(ErrorKind::Network)?
|
.with_kind(ErrorKind::Network)?
|
||||||
{
|
{
|
||||||
ClientConnection::Connect(
|
|
||||||
reply,
|
|
||||||
Address::DomainAddress(domain, port),
|
|
||||||
) if domain.ends_with(".onion") => {
|
|
||||||
if let Ok(mut target) = tor
|
|
||||||
.connect_onion(&domain.parse()?, port)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
let mut sock = reply
|
|
||||||
.reply(
|
|
||||||
Reply::Succeeded,
|
|
||||||
Address::unspecified(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Network)?;
|
|
||||||
tokio::io::copy_bidirectional(
|
|
||||||
&mut sock,
|
|
||||||
&mut target,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Network)?;
|
|
||||||
} else {
|
|
||||||
let mut sock = reply
|
|
||||||
.reply(
|
|
||||||
Reply::HostUnreachable,
|
|
||||||
Address::unspecified(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Network)?;
|
|
||||||
sock.shutdown()
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Network)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ClientConnection::Connect(reply, addr) => {
|
ClientConnection::Connect(reply, addr) => {
|
||||||
if let Ok(mut target) = match addr {
|
if let Ok(mut target) = match addr {
|
||||||
Address::DomainAddress(domain, port) => {
|
Address::DomainAddress(domain, port) => {
|
||||||
|
|||||||
@@ -1,964 +0,0 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::{Arc, Weak};
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
use arti_client::config::onion_service::OnionServiceConfigBuilder;
|
|
||||||
use arti_client::{TorClient, TorClientConfig};
|
|
||||||
use base64::Engine;
|
|
||||||
use clap::Parser;
|
|
||||||
use color_eyre::eyre::eyre;
|
|
||||||
use futures::{FutureExt, StreamExt};
|
|
||||||
use imbl_value::InternedString;
|
|
||||||
use itertools::Itertools;
|
|
||||||
use rpc_toolkit::{Context, Empty, HandlerExt, ParentHandler, from_fn_async};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use tokio::net::TcpStream;
|
|
||||||
use tokio::sync::Notify;
|
|
||||||
use tor_cell::relaycell::msg::Connected;
|
|
||||||
use tor_hscrypto::pk::{HsId, HsIdKeypair};
|
|
||||||
use tor_hsservice::status::State as ArtiOnionServiceState;
|
|
||||||
use tor_hsservice::{HsNickname, RunningOnionService};
|
|
||||||
use tor_keymgr::config::ArtiKeystoreKind;
|
|
||||||
use tor_proto::client::stream::IncomingStreamRequest;
|
|
||||||
use tor_rtcompat::tokio::TokioRustlsRuntime;
|
|
||||||
use ts_rs::TS;
|
|
||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::util::actor::background::BackgroundJobQueue;
|
|
||||||
use crate::util::future::{NonDetachingJoinHandle, Until};
|
|
||||||
use crate::util::io::ReadWriter;
|
|
||||||
use crate::util::serde::{
|
|
||||||
BASE64, Base64, HandlerExtSerde, WithIoFormat, deserialize_from_str, display_serializable,
|
|
||||||
serialize_display,
|
|
||||||
};
|
|
||||||
use crate::util::sync::{SyncMutex, SyncRwLock, Watch};
|
|
||||||
|
|
||||||
const BOOTSTRAP_PROGRESS_TIMEOUT: Duration = Duration::from_secs(300);
|
|
||||||
const HS_BOOTSTRAP_TIMEOUT: Duration = Duration::from_secs(300);
|
|
||||||
const RETRY_COOLDOWN: Duration = Duration::from_secs(15);
|
|
||||||
const HEALTH_CHECK_FAILURE_ALLOWANCE: usize = 5;
|
|
||||||
const HEALTH_CHECK_COOLDOWN: Duration = Duration::from_secs(120);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct OnionAddress(pub HsId);
|
|
||||||
impl std::fmt::Display for OnionAddress {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
safelog::DisplayRedacted::fmt_unredacted(&self.0, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FromStr for OnionAddress {
|
|
||||||
type Err = Error;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
Ok(Self(
|
|
||||||
if s.ends_with(".onion") {
|
|
||||||
Cow::Borrowed(s)
|
|
||||||
} else {
|
|
||||||
Cow::Owned(format!("{s}.onion"))
|
|
||||||
}
|
|
||||||
.parse::<HsId>()
|
|
||||||
.with_kind(ErrorKind::Tor)?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Serialize for OnionAddress {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
serialize_display(self, serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'de> Deserialize<'de> for OnionAddress {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
deserialize_from_str(deserializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl PartialEq for OnionAddress {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.0.as_ref() == other.0.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Eq for OnionAddress {}
|
|
||||||
impl PartialOrd for OnionAddress {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
self.0.as_ref().partial_cmp(other.0.as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Ord for OnionAddress {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
self.0.as_ref().cmp(other.0.as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TorSecretKey(pub HsIdKeypair);
|
|
||||||
impl TorSecretKey {
|
|
||||||
pub fn onion_address(&self) -> OnionAddress {
|
|
||||||
OnionAddress(HsId::from(self.0.as_ref().public().to_bytes()))
|
|
||||||
}
|
|
||||||
pub fn from_bytes(bytes: [u8; 64]) -> Result<Self, Error> {
|
|
||||||
Ok(Self(
|
|
||||||
tor_llcrypto::pk::ed25519::ExpandedKeypair::from_secret_key_bytes(bytes)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::new(
|
|
||||||
eyre!("{}", t!("net.tor.invalid-ed25519-key")),
|
|
||||||
ErrorKind::Tor,
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.into(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
pub fn generate() -> Self {
|
|
||||||
Self(
|
|
||||||
tor_llcrypto::pk::ed25519::ExpandedKeypair::from(
|
|
||||||
&tor_llcrypto::pk::ed25519::Keypair::generate(&mut rand::rng()),
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Clone for TorSecretKey {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self(HsIdKeypair::from(
|
|
||||||
tor_llcrypto::pk::ed25519::ExpandedKeypair::from_secret_key_bytes(
|
|
||||||
self.0.as_ref().to_secret_key_bytes(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for TorSecretKey {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
BASE64.encode(self.0.as_ref().to_secret_key_bytes())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FromStr for TorSecretKey {
|
|
||||||
type Err = Error;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
Self::from_bytes(Base64::<[u8; 64]>::from_str(s)?.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Serialize for TorSecretKey {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
serialize_display(self, serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'de> Deserialize<'de> for TorSecretKey {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
deserialize_from_str(deserializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Serialize)]
|
|
||||||
pub struct OnionStore(BTreeMap<OnionAddress, TorSecretKey>);
|
|
||||||
impl Map for OnionStore {
|
|
||||||
type Key = OnionAddress;
|
|
||||||
type Value = TorSecretKey;
|
|
||||||
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
|
||||||
Self::key_string(key)
|
|
||||||
}
|
|
||||||
fn key_string(key: &Self::Key) -> Result<imbl_value::InternedString, Error> {
|
|
||||||
Ok(InternedString::from_display(key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl OnionStore {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
pub fn insert(&mut self, key: TorSecretKey) {
|
|
||||||
self.0.insert(key.onion_address(), key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Model<OnionStore> {
|
|
||||||
pub fn new_key(&mut self) -> Result<TorSecretKey, Error> {
|
|
||||||
let key = TorSecretKey::generate();
|
|
||||||
self.insert(&key.onion_address(), &key)?;
|
|
||||||
Ok(key)
|
|
||||||
}
|
|
||||||
pub fn insert_key(&mut self, key: &TorSecretKey) -> Result<(), Error> {
|
|
||||||
self.insert(&key.onion_address(), &key)
|
|
||||||
}
|
|
||||||
pub fn get_key(&self, address: &OnionAddress) -> Result<TorSecretKey, Error> {
|
|
||||||
self.as_idx(address)
|
|
||||||
.or_not_found(lazy_format!("private key for {address}"))?
|
|
||||||
.de()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::fmt::Debug for OnionStore {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
struct OnionStoreMap<'a>(&'a BTreeMap<OnionAddress, TorSecretKey>);
|
|
||||||
impl<'a> std::fmt::Debug for OnionStoreMap<'a> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct KeyFor(#[allow(unused)] OnionAddress);
|
|
||||||
let mut map = f.debug_map();
|
|
||||||
for (k, v) in self.0 {
|
|
||||||
map.key(k);
|
|
||||||
map.value(&KeyFor(v.onion_address()));
|
|
||||||
}
|
|
||||||
map.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.debug_tuple("OnionStore")
|
|
||||||
.field(&OnionStoreMap(&self.0))
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tor_api<C: Context>() -> ParentHandler<C> {
|
|
||||||
ParentHandler::new()
|
|
||||||
.subcommand(
|
|
||||||
"list-services",
|
|
||||||
from_fn_async(list_services)
|
|
||||||
.with_display_serializable()
|
|
||||||
.with_custom_display_fn(|handle, result| display_services(handle.params, result))
|
|
||||||
.with_about("about.display-tor-v3-onion-addresses")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"reset",
|
|
||||||
from_fn_async(reset)
|
|
||||||
.no_display()
|
|
||||||
.with_about("about.reset-tor-daemon")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"key",
|
|
||||||
key::<C>().with_about("about.manage-onion-service-key-store"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn key<C: Context>() -> ParentHandler<C> {
|
|
||||||
ParentHandler::new()
|
|
||||||
.subcommand(
|
|
||||||
"generate",
|
|
||||||
from_fn_async(generate_key)
|
|
||||||
.with_about("about.generate-onion-service-key-add-to-store")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"add",
|
|
||||||
from_fn_async(add_key)
|
|
||||||
.with_about("about.add-onion-service-key-to-store")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"list",
|
|
||||||
from_fn_async(list_keys)
|
|
||||||
.with_custom_display_fn(|_, res| {
|
|
||||||
for addr in res {
|
|
||||||
println!("{addr}");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.with_about("about.list-onion-services-with-keys-in-store")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn generate_key(ctx: RpcContext) -> Result<OnionAddress, Error> {
|
|
||||||
ctx.db
|
|
||||||
.mutate(|db| {
|
|
||||||
Ok(db
|
|
||||||
.as_private_mut()
|
|
||||||
.as_key_store_mut()
|
|
||||||
.as_onion_mut()
|
|
||||||
.new_key()?
|
|
||||||
.onion_address())
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser)]
|
|
||||||
pub struct AddKeyParams {
|
|
||||||
#[arg(help = "help.arg.onion-secret-key")]
|
|
||||||
pub key: Base64<[u8; 64]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add_key(
|
|
||||||
ctx: RpcContext,
|
|
||||||
AddKeyParams { key }: AddKeyParams,
|
|
||||||
) -> Result<OnionAddress, Error> {
|
|
||||||
let key = TorSecretKey::from_bytes(key.0)?;
|
|
||||||
ctx.db
|
|
||||||
.mutate(|db| {
|
|
||||||
db.as_private_mut()
|
|
||||||
.as_key_store_mut()
|
|
||||||
.as_onion_mut()
|
|
||||||
.insert_key(&key)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.result?;
|
|
||||||
Ok(key.onion_address())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list_keys(ctx: RpcContext) -> Result<BTreeSet<OnionAddress>, Error> {
|
|
||||||
ctx.db
|
|
||||||
.peek()
|
|
||||||
.await
|
|
||||||
.into_private()
|
|
||||||
.into_key_store()
|
|
||||||
.into_onion()
|
|
||||||
.keys()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
#[command(rename_all = "kebab-case")]
|
|
||||||
pub struct ResetParams {
|
|
||||||
#[arg(
|
|
||||||
name = "wipe-state",
|
|
||||||
short = 'w',
|
|
||||||
long = "wipe-state",
|
|
||||||
help = "help.arg.wipe-tor-state"
|
|
||||||
)]
|
|
||||||
wipe_state: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn reset(ctx: RpcContext, ResetParams { wipe_state }: ResetParams) -> Result<(), Error> {
|
|
||||||
ctx.net_controller.tor.reset(wipe_state).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn display_services(
|
|
||||||
params: WithIoFormat<Empty>,
|
|
||||||
services: BTreeMap<OnionAddress, OnionServiceInfo>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
use prettytable::*;
|
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
|
||||||
return display_serializable(format, services);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut table = Table::new();
|
|
||||||
table.add_row(row![bc => "ADDRESS", "STATE", "BINDINGS"]);
|
|
||||||
for (service, info) in services {
|
|
||||||
let row = row![
|
|
||||||
&service.to_string(),
|
|
||||||
&format!("{:?}", info.state),
|
|
||||||
&info
|
|
||||||
.bindings
|
|
||||||
.into_iter()
|
|
||||||
.map(|(port, addr)| lazy_format!("{port} -> {addr}"))
|
|
||||||
.join("; ")
|
|
||||||
];
|
|
||||||
table.add_row(row);
|
|
||||||
}
|
|
||||||
table.print_tty(false)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum OnionServiceState {
|
|
||||||
Shutdown,
|
|
||||||
Bootstrapping,
|
|
||||||
DegradedReachable,
|
|
||||||
DegradedUnreachable,
|
|
||||||
Running,
|
|
||||||
Recovering,
|
|
||||||
Broken,
|
|
||||||
}
|
|
||||||
impl From<ArtiOnionServiceState> for OnionServiceState {
|
|
||||||
fn from(value: ArtiOnionServiceState) -> Self {
|
|
||||||
match value {
|
|
||||||
ArtiOnionServiceState::Shutdown => Self::Shutdown,
|
|
||||||
ArtiOnionServiceState::Bootstrapping => Self::Bootstrapping,
|
|
||||||
ArtiOnionServiceState::DegradedReachable => Self::DegradedReachable,
|
|
||||||
ArtiOnionServiceState::DegradedUnreachable => Self::DegradedUnreachable,
|
|
||||||
ArtiOnionServiceState::Running => Self::Running,
|
|
||||||
ArtiOnionServiceState::Recovering => Self::Recovering,
|
|
||||||
ArtiOnionServiceState::Broken => Self::Broken,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct OnionServiceInfo {
|
|
||||||
pub state: OnionServiceState,
|
|
||||||
pub bindings: BTreeMap<u16, SocketAddr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list_services(
|
|
||||||
ctx: RpcContext,
|
|
||||||
_: Empty,
|
|
||||||
) -> Result<BTreeMap<OnionAddress, OnionServiceInfo>, Error> {
|
|
||||||
ctx.net_controller.tor.list_services().await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct TorController(Arc<TorControllerInner>);
|
|
||||||
struct TorControllerInner {
|
|
||||||
client: Watch<(usize, TorClient<TokioRustlsRuntime>)>,
|
|
||||||
_bootstrapper: NonDetachingJoinHandle<()>,
|
|
||||||
services: SyncMutex<BTreeMap<OnionAddress, OnionService>>,
|
|
||||||
reset: Arc<Notify>,
|
|
||||||
}
|
|
||||||
impl TorController {
|
|
||||||
pub fn new() -> Result<Self, Error> {
|
|
||||||
let mut config = TorClientConfig::builder();
|
|
||||||
config
|
|
||||||
.storage()
|
|
||||||
.keystore()
|
|
||||||
.primary()
|
|
||||||
.kind(ArtiKeystoreKind::Ephemeral.into());
|
|
||||||
let client = Watch::new((
|
|
||||||
0,
|
|
||||||
TorClient::with_runtime(TokioRustlsRuntime::current()?)
|
|
||||||
.config(config.build().with_kind(ErrorKind::Tor)?)
|
|
||||||
.local_resource_timeout(Duration::from_secs(0))
|
|
||||||
.create_unbootstrapped()?,
|
|
||||||
));
|
|
||||||
let reset = Arc::new(Notify::new());
|
|
||||||
let bootstrapper_reset = reset.clone();
|
|
||||||
let bootstrapper_client = client.clone();
|
|
||||||
let bootstrapper = tokio::spawn(async move {
|
|
||||||
loop {
|
|
||||||
let (epoch, client): (usize, _) = bootstrapper_client.read();
|
|
||||||
if let Err(e) = Until::new()
|
|
||||||
.with_async_fn(|| bootstrapper_reset.notified().map(Ok))
|
|
||||||
.run(async {
|
|
||||||
let mut events = client.bootstrap_events();
|
|
||||||
let bootstrap_fut =
|
|
||||||
client.bootstrap().map(|res| res.with_kind(ErrorKind::Tor));
|
|
||||||
let failure_fut = async {
|
|
||||||
let mut prev_frac = 0_f32;
|
|
||||||
let mut prev_inst = Instant::now();
|
|
||||||
while let Some(event) =
|
|
||||||
tokio::time::timeout(BOOTSTRAP_PROGRESS_TIMEOUT, events.next())
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Tor)?
|
|
||||||
{
|
|
||||||
if event.ready_for_traffic() {
|
|
||||||
return Ok::<_, Error>(());
|
|
||||||
}
|
|
||||||
let frac = event.as_frac();
|
|
||||||
if frac == prev_frac {
|
|
||||||
if prev_inst.elapsed() > BOOTSTRAP_PROGRESS_TIMEOUT {
|
|
||||||
return Err(Error::new(
|
|
||||||
eyre!(
|
|
||||||
"{}",
|
|
||||||
t!(
|
|
||||||
"net.tor.bootstrap-no-progress",
|
|
||||||
duration = crate::util::serde::Duration::from(
|
|
||||||
BOOTSTRAP_PROGRESS_TIMEOUT
|
|
||||||
)
|
|
||||||
.to_string()
|
|
||||||
)
|
|
||||||
),
|
|
||||||
ErrorKind::Tor,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
prev_frac = frac;
|
|
||||||
prev_inst = Instant::now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
futures::future::pending().await
|
|
||||||
};
|
|
||||||
if let Err::<(), Error>(e) = tokio::select! {
|
|
||||||
res = bootstrap_fut => res,
|
|
||||||
res = failure_fut => res,
|
|
||||||
} {
|
|
||||||
tracing::error!(
|
|
||||||
"{}",
|
|
||||||
t!("net.tor.bootstrap-error", error = e.to_string())
|
|
||||||
);
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
} else {
|
|
||||||
bootstrapper_client.send_modify(|_| ());
|
|
||||||
|
|
||||||
for _ in 0..HEALTH_CHECK_FAILURE_ALLOWANCE {
|
|
||||||
if let Err::<(), Error>(e) = async {
|
|
||||||
loop {
|
|
||||||
let (bg, mut runner) = BackgroundJobQueue::new();
|
|
||||||
runner
|
|
||||||
.run_while(async {
|
|
||||||
const PING_BUF_LEN: usize = 8;
|
|
||||||
let key = TorSecretKey::generate();
|
|
||||||
let onion = key.onion_address();
|
|
||||||
let (hs, stream) = client
|
|
||||||
.launch_onion_service_with_hsid(
|
|
||||||
OnionServiceConfigBuilder::default()
|
|
||||||
.nickname(
|
|
||||||
onion
|
|
||||||
.to_string()
|
|
||||||
.trim_end_matches(".onion")
|
|
||||||
.parse::<HsNickname>()
|
|
||||||
.with_kind(ErrorKind::Tor)?,
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
.with_kind(ErrorKind::Tor)?,
|
|
||||||
key.clone().0,
|
|
||||||
)
|
|
||||||
.with_kind(ErrorKind::Tor)?;
|
|
||||||
bg.add_job(async move {
|
|
||||||
if let Err(e) = async {
|
|
||||||
let mut stream =
|
|
||||||
tor_hsservice::handle_rend_requests(
|
|
||||||
stream,
|
|
||||||
);
|
|
||||||
while let Some(req) = stream.next().await {
|
|
||||||
let mut stream = req
|
|
||||||
.accept(Connected::new_empty())
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Tor)?;
|
|
||||||
let mut buf = [0; PING_BUF_LEN];
|
|
||||||
stream.read_exact(&mut buf).await?;
|
|
||||||
stream.write_all(&buf).await?;
|
|
||||||
stream.flush().await?;
|
|
||||||
stream.shutdown().await?;
|
|
||||||
}
|
|
||||||
Ok::<_, Error>(())
|
|
||||||
}
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::error!(
|
|
||||||
"{}",
|
|
||||||
t!(
|
|
||||||
"net.tor.health-error",
|
|
||||||
error = e.to_string()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tokio::time::timeout(HS_BOOTSTRAP_TIMEOUT, async {
|
|
||||||
let mut status = hs.status_events();
|
|
||||||
while let Some(status) = status.next().await {
|
|
||||||
if status.state().is_fully_reachable() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(Error::new(
|
|
||||||
eyre!(
|
|
||||||
"{}",
|
|
||||||
t!("net.tor.status-stream-ended")
|
|
||||||
),
|
|
||||||
ErrorKind::Tor,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Tor)??;
|
|
||||||
|
|
||||||
let mut stream = client
|
|
||||||
.connect((onion.to_string(), 8080))
|
|
||||||
.await?;
|
|
||||||
let mut ping_buf = [0; PING_BUF_LEN];
|
|
||||||
rand::fill(&mut ping_buf);
|
|
||||||
stream.write_all(&ping_buf).await?;
|
|
||||||
stream.flush().await?;
|
|
||||||
let mut ping_res = [0; PING_BUF_LEN];
|
|
||||||
stream.read_exact(&mut ping_res).await?;
|
|
||||||
ensure_code!(
|
|
||||||
ping_buf == ping_res,
|
|
||||||
ErrorKind::Tor,
|
|
||||||
"ping buffer mismatch"
|
|
||||||
);
|
|
||||||
stream.shutdown().await?;
|
|
||||||
|
|
||||||
Ok::<_, Error>(())
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
tokio::time::sleep(HEALTH_CHECK_COOLDOWN).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::error!(
|
|
||||||
"{}",
|
|
||||||
t!("net.tor.client-health-error", error = e.to_string())
|
|
||||||
);
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tracing::error!(
|
|
||||||
"{}",
|
|
||||||
t!(
|
|
||||||
"net.tor.health-check-failed-recycling",
|
|
||||||
count = HEALTH_CHECK_FAILURE_ALLOWANCE
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::error!(
|
|
||||||
"{}",
|
|
||||||
t!("net.tor.bootstrapper-error", error = e.to_string())
|
|
||||||
);
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
if let Err::<(), Error>(e) = async {
|
|
||||||
tokio::time::sleep(RETRY_COOLDOWN).await;
|
|
||||||
bootstrapper_client.send((
|
|
||||||
epoch.wrapping_add(1),
|
|
||||||
TorClient::with_runtime(TokioRustlsRuntime::current()?)
|
|
||||||
.config(config.build().with_kind(ErrorKind::Tor)?)
|
|
||||||
.local_resource_timeout(Duration::from_secs(0))
|
|
||||||
.create_unbootstrapped_async()
|
|
||||||
.await?,
|
|
||||||
));
|
|
||||||
tracing::debug!("TorClient recycled");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::error!(
|
|
||||||
"{}",
|
|
||||||
t!("net.tor.client-creation-error", error = e.to_string())
|
|
||||||
);
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into();
|
|
||||||
Ok(Self(Arc::new(TorControllerInner {
|
|
||||||
client,
|
|
||||||
_bootstrapper: bootstrapper,
|
|
||||||
services: SyncMutex::new(BTreeMap::new()),
|
|
||||||
reset,
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn service(&self, key: TorSecretKey) -> Result<OnionService, Error> {
|
|
||||||
self.0.services.mutate(|s| {
|
|
||||||
use std::collections::btree_map::Entry;
|
|
||||||
let addr = key.onion_address();
|
|
||||||
match s.entry(addr) {
|
|
||||||
Entry::Occupied(e) => Ok(e.get().clone()),
|
|
||||||
Entry::Vacant(e) => Ok(e
|
|
||||||
.insert(OnionService::launch(self.0.client.clone(), key)?)
|
|
||||||
.clone()),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn gc(&self, addr: Option<OnionAddress>) -> Result<(), Error> {
|
|
||||||
if let Some(addr) = addr {
|
|
||||||
if let Some(s) = self.0.services.mutate(|s| {
|
|
||||||
let rm = if let Some(s) = s.get(&addr) {
|
|
||||||
!s.gc()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
if rm { s.remove(&addr) } else { None }
|
|
||||||
}) {
|
|
||||||
s.shutdown().await
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for s in self.0.services.mutate(|s| {
|
|
||||||
let mut rm = Vec::new();
|
|
||||||
s.retain(|_, s| {
|
|
||||||
if s.gc() {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
rm.push(s.clone());
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
rm
|
|
||||||
}) {
|
|
||||||
s.shutdown().await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn reset(&self, wipe_state: bool) -> Result<(), Error> {
|
|
||||||
self.0.reset.notify_waiters();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list_services(&self) -> Result<BTreeMap<OnionAddress, OnionServiceInfo>, Error> {
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.services
|
|
||||||
.peek(|s| s.iter().map(|(a, s)| (a.clone(), s.info())).collect()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect_onion(
|
|
||||||
&self,
|
|
||||||
addr: &OnionAddress,
|
|
||||||
port: u16,
|
|
||||||
) -> Result<Box<dyn ReadWriter + Unpin + Send + Sync + 'static>, Error> {
|
|
||||||
if let Some(target) = self.0.services.peek(|s| {
|
|
||||||
s.get(addr).and_then(|s| {
|
|
||||||
s.0.bindings.peek(|b| {
|
|
||||||
b.get(&port).and_then(|b| {
|
|
||||||
b.iter()
|
|
||||||
.find(|(_, rc)| rc.strong_count() > 0)
|
|
||||||
.map(|(a, _)| *a)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}) {
|
|
||||||
let tcp_stream = TcpStream::connect(target)
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Network)?;
|
|
||||||
if let Err(e) = socket2::SockRef::from(&tcp_stream).set_keepalive(true) {
|
|
||||||
tracing::error!(
|
|
||||||
"{}",
|
|
||||||
t!("net.tor.failed-to-set-tcp-keepalive", error = e.to_string())
|
|
||||||
);
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
Ok(Box::new(tcp_stream))
|
|
||||||
} else {
|
|
||||||
let mut client = self.0.client.clone();
|
|
||||||
client
|
|
||||||
.wait_for(|(_, c)| c.bootstrap_status().ready_for_traffic())
|
|
||||||
.await;
|
|
||||||
let stream = client
|
|
||||||
.read()
|
|
||||||
.1
|
|
||||||
.connect((addr.to_string(), port))
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Tor)?;
|
|
||||||
Ok(Box::new(stream))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct OnionService(Arc<OnionServiceData>);
|
|
||||||
struct OnionServiceData {
|
|
||||||
service: Arc<SyncMutex<Option<Arc<RunningOnionService>>>>,
|
|
||||||
bindings: Arc<SyncRwLock<BTreeMap<u16, BTreeMap<SocketAddr, Weak<()>>>>>,
|
|
||||||
_thread: NonDetachingJoinHandle<()>,
|
|
||||||
}
|
|
||||||
impl OnionService {
|
|
||||||
fn launch(
|
|
||||||
mut client: Watch<(usize, TorClient<TokioRustlsRuntime>)>,
|
|
||||||
key: TorSecretKey,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
let service = Arc::new(SyncMutex::new(None));
|
|
||||||
let bindings = Arc::new(SyncRwLock::new(BTreeMap::<
|
|
||||||
u16,
|
|
||||||
BTreeMap<SocketAddr, Weak<()>>,
|
|
||||||
>::new()));
|
|
||||||
Ok(Self(Arc::new(OnionServiceData {
|
|
||||||
service: service.clone(),
|
|
||||||
bindings: bindings.clone(),
|
|
||||||
_thread: tokio::spawn(async move {
|
|
||||||
let (bg, mut runner) = BackgroundJobQueue::new();
|
|
||||||
runner
|
|
||||||
.run_while(async {
|
|
||||||
loop {
|
|
||||||
if let Err(e) = async {
|
|
||||||
client.wait_for(|(_,c)| c.bootstrap_status().ready_for_traffic()).await;
|
|
||||||
let epoch = client.peek(|(e, c)| {
|
|
||||||
ensure_code!(c.bootstrap_status().ready_for_traffic(), ErrorKind::Tor, "TorClient recycled");
|
|
||||||
Ok::<_, Error>(*e)
|
|
||||||
})?;
|
|
||||||
let addr = key.onion_address();
|
|
||||||
let (new_service, stream) = client.peek(|(_, c)| {
|
|
||||||
c.launch_onion_service_with_hsid(
|
|
||||||
OnionServiceConfigBuilder::default()
|
|
||||||
.nickname(
|
|
||||||
addr
|
|
||||||
.to_string()
|
|
||||||
.trim_end_matches(".onion")
|
|
||||||
.parse::<HsNickname>()
|
|
||||||
.with_kind(ErrorKind::Tor)?,
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
.with_kind(ErrorKind::Tor)?,
|
|
||||||
key.clone().0,
|
|
||||||
)
|
|
||||||
.with_kind(ErrorKind::Tor)
|
|
||||||
})?;
|
|
||||||
let mut status_stream = new_service.status_events();
|
|
||||||
let mut status = new_service.status();
|
|
||||||
if status.state().is_fully_reachable() {
|
|
||||||
tracing::debug!("{addr} is fully reachable");
|
|
||||||
} else {
|
|
||||||
tracing::debug!("{addr} is not fully reachable");
|
|
||||||
}
|
|
||||||
bg.add_job(async move {
|
|
||||||
while let Some(new_status) = status_stream.next().await {
|
|
||||||
if status.state().is_fully_reachable() && !new_status.state().is_fully_reachable() {
|
|
||||||
tracing::debug!("{addr} is no longer fully reachable");
|
|
||||||
} else if !status.state().is_fully_reachable() && new_status.state().is_fully_reachable() {
|
|
||||||
tracing::debug!("{addr} is now fully reachable");
|
|
||||||
}
|
|
||||||
status = new_status;
|
|
||||||
// TODO: health daemon?
|
|
||||||
}
|
|
||||||
});
|
|
||||||
service.replace(Some(new_service));
|
|
||||||
let mut stream = tor_hsservice::handle_rend_requests(stream);
|
|
||||||
while let Some(req) = tokio::select! {
|
|
||||||
req = stream.next() => req,
|
|
||||||
_ = client.wait_for(|(e, _)| *e != epoch) => None
|
|
||||||
} {
|
|
||||||
bg.add_job({
|
|
||||||
let bg = bg.clone();
|
|
||||||
let bindings = bindings.clone();
|
|
||||||
async move {
|
|
||||||
if let Err(e) = async {
|
|
||||||
let IncomingStreamRequest::Begin(begin) =
|
|
||||||
req.request()
|
|
||||||
else {
|
|
||||||
return req
|
|
||||||
.reject(tor_cell::relaycell::msg::End::new_with_reason(
|
|
||||||
tor_cell::relaycell::msg::EndReason::DONE,
|
|
||||||
))
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Tor);
|
|
||||||
};
|
|
||||||
let Some(target) = bindings.peek(|b| {
|
|
||||||
b.get(&begin.port()).and_then(|a| {
|
|
||||||
a.iter()
|
|
||||||
.find(|(_, rc)| rc.strong_count() > 0)
|
|
||||||
.map(|(addr, _)| *addr)
|
|
||||||
})
|
|
||||||
}) else {
|
|
||||||
return req
|
|
||||||
.reject(tor_cell::relaycell::msg::End::new_with_reason(
|
|
||||||
tor_cell::relaycell::msg::EndReason::DONE,
|
|
||||||
))
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Tor);
|
|
||||||
};
|
|
||||||
bg.add_job(async move {
|
|
||||||
if let Err(e) = async {
|
|
||||||
let mut outgoing =
|
|
||||||
TcpStream::connect(target)
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Network)?;
|
|
||||||
if let Err(e) = socket2::SockRef::from(&outgoing).set_keepalive(true) {
|
|
||||||
tracing::error!("{}", t!("net.tor.failed-to-set-tcp-keepalive", error = e.to_string()));
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
let mut incoming = req
|
|
||||||
.accept(Connected::new_empty())
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Tor)?;
|
|
||||||
if let Err(e) =
|
|
||||||
tokio::io::copy_bidirectional(
|
|
||||||
&mut outgoing,
|
|
||||||
&mut incoming,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::trace!("Tor Stream Error: {e}");
|
|
||||||
tracing::trace!("{e:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok::<_, Error>(())
|
|
||||||
}
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::trace!("Tor Stream Error: {e}");
|
|
||||||
tracing::trace!("{e:?}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Ok::<_, Error>(())
|
|
||||||
}
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::trace!("Tor Request Error: {e}");
|
|
||||||
tracing::trace!("{e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok::<_, Error>(())
|
|
||||||
}
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::error!("{}", t!("net.tor.client-error", error = e.to_string()));
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
.into(),
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn proxy_all<Rcs: FromIterator<Arc<()>>>(
|
|
||||||
&self,
|
|
||||||
bindings: impl IntoIterator<Item = (u16, SocketAddr)>,
|
|
||||||
) -> Result<Rcs, Error> {
|
|
||||||
Ok(self.0.bindings.mutate(|b| {
|
|
||||||
bindings
|
|
||||||
.into_iter()
|
|
||||||
.map(|(port, target)| {
|
|
||||||
let entry = b.entry(port).or_default().entry(target).or_default();
|
|
||||||
if let Some(rc) = entry.upgrade() {
|
|
||||||
rc
|
|
||||||
} else {
|
|
||||||
let rc = Arc::new(());
|
|
||||||
*entry = Arc::downgrade(&rc);
|
|
||||||
rc
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gc(&self) -> bool {
|
|
||||||
self.0.bindings.mutate(|b| {
|
|
||||||
b.retain(|_, targets| {
|
|
||||||
targets.retain(|_, rc| rc.strong_count() > 0);
|
|
||||||
!targets.is_empty()
|
|
||||||
});
|
|
||||||
!b.is_empty()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn shutdown(self) -> Result<(), Error> {
|
|
||||||
self.0.service.replace(None);
|
|
||||||
self.0._thread.abort();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn state(&self) -> OnionServiceState {
|
|
||||||
self.0
|
|
||||||
.service
|
|
||||||
.peek(|s| s.as_ref().map(|s| s.status().state().into()))
|
|
||||||
.unwrap_or(OnionServiceState::Bootstrapping)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn info(&self) -> OnionServiceInfo {
|
|
||||||
OnionServiceInfo {
|
|
||||||
state: self.state(),
|
|
||||||
bindings: self.0.bindings.peek(|b| {
|
|
||||||
b.iter()
|
|
||||||
.filter_map(|(port, b)| {
|
|
||||||
b.iter()
|
|
||||||
.find(|(_, rc)| rc.strong_count() > 0)
|
|
||||||
.map(|(addr, _)| (*port, *addr))
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +0,0 @@
|
|||||||
#[cfg(feature = "arti")]
|
|
||||||
mod arti;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "arti"))]
|
|
||||||
mod ctor;
|
|
||||||
|
|
||||||
#[cfg(feature = "arti")]
|
|
||||||
pub use arti::{OnionAddress, OnionStore, TorController, TorSecretKey, tor_api};
|
|
||||||
#[cfg(not(feature = "arti"))]
|
|
||||||
pub use ctor::{OnionAddress, OnionStore, TorController, TorSecretKey, tor_api};
|
|
||||||
@@ -55,11 +55,9 @@ pub async fn get_ssl_certificate(
|
|||||||
.map(|(_, m)| m.as_hosts().as_entries())
|
.map(|(_, m)| m.as_hosts().as_entries())
|
||||||
.flatten_ok()
|
.flatten_ok()
|
||||||
.map_ok(|(_, m)| {
|
.map_ok(|(_, m)| {
|
||||||
Ok(m.as_onions()
|
Ok(m.as_public_domains()
|
||||||
.de()?
|
.keys()?
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(InternedString::from_display)
|
|
||||||
.chain(m.as_public_domains().keys()?)
|
|
||||||
.chain(m.as_private_domains().de()?)
|
.chain(m.as_private_domains().de()?)
|
||||||
.chain(
|
.chain(
|
||||||
m.as_hostname_info()
|
m.as_hostname_info()
|
||||||
@@ -68,7 +66,7 @@ pub async fn get_ssl_certificate(
|
|||||||
.flatten()
|
.flatten()
|
||||||
.map(|h| h.to_san_hostname()),
|
.map(|h| h.to_san_hostname()),
|
||||||
)
|
)
|
||||||
.collect::<Vec<_>>())
|
.collect::<Vec<InternedString>>())
|
||||||
})
|
})
|
||||||
.map(|a| a.and_then(|a| a))
|
.map(|a| a.and_then(|a| a))
|
||||||
.flatten_ok()
|
.flatten_ok()
|
||||||
@@ -181,11 +179,9 @@ pub async fn get_ssl_key(
|
|||||||
.map(|m| m.as_hosts().as_entries())
|
.map(|m| m.as_hosts().as_entries())
|
||||||
.flatten_ok()
|
.flatten_ok()
|
||||||
.map_ok(|(_, m)| {
|
.map_ok(|(_, m)| {
|
||||||
Ok(m.as_onions()
|
Ok(m.as_public_domains()
|
||||||
.de()?
|
.keys()?
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(InternedString::from_display)
|
|
||||||
.chain(m.as_public_domains().keys()?)
|
|
||||||
.chain(m.as_private_domains().de()?)
|
.chain(m.as_private_domains().de()?)
|
||||||
.chain(
|
.chain(
|
||||||
m.as_hostname_info()
|
m.as_hostname_info()
|
||||||
@@ -194,7 +190,7 @@ pub async fn get_ssl_key(
|
|||||||
.flatten()
|
.flatten()
|
||||||
.map(|h| h.to_san_hostname()),
|
.map(|h| h.to_san_hostname()),
|
||||||
)
|
)
|
||||||
.collect::<Vec<_>>())
|
.collect::<Vec<InternedString>>())
|
||||||
})
|
})
|
||||||
.map(|a| a.and_then(|a| a))
|
.map(|a| a.and_then(|a| a))
|
||||||
.flatten_ok()
|
.flatten_ok()
|
||||||
|
|||||||
@@ -59,8 +59,9 @@ mod v0_4_0_alpha_16;
|
|||||||
mod v0_4_0_alpha_17;
|
mod v0_4_0_alpha_17;
|
||||||
mod v0_4_0_alpha_18;
|
mod v0_4_0_alpha_18;
|
||||||
mod v0_4_0_alpha_19;
|
mod v0_4_0_alpha_19;
|
||||||
|
mod v0_4_0_alpha_20;
|
||||||
|
|
||||||
pub type Current = v0_4_0_alpha_19::Version; // VERSION_BUMP
|
pub type Current = v0_4_0_alpha_20::Version; // VERSION_BUMP
|
||||||
|
|
||||||
impl Current {
|
impl Current {
|
||||||
#[instrument(skip(self, db))]
|
#[instrument(skip(self, db))]
|
||||||
@@ -181,7 +182,8 @@ enum Version {
|
|||||||
V0_4_0_alpha_16(Wrapper<v0_4_0_alpha_16::Version>),
|
V0_4_0_alpha_16(Wrapper<v0_4_0_alpha_16::Version>),
|
||||||
V0_4_0_alpha_17(Wrapper<v0_4_0_alpha_17::Version>),
|
V0_4_0_alpha_17(Wrapper<v0_4_0_alpha_17::Version>),
|
||||||
V0_4_0_alpha_18(Wrapper<v0_4_0_alpha_18::Version>),
|
V0_4_0_alpha_18(Wrapper<v0_4_0_alpha_18::Version>),
|
||||||
V0_4_0_alpha_19(Wrapper<v0_4_0_alpha_19::Version>), // VERSION_BUMP
|
V0_4_0_alpha_19(Wrapper<v0_4_0_alpha_19::Version>),
|
||||||
|
V0_4_0_alpha_20(Wrapper<v0_4_0_alpha_20::Version>), // VERSION_BUMP
|
||||||
Other(exver::Version),
|
Other(exver::Version),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +245,8 @@ impl Version {
|
|||||||
Self::V0_4_0_alpha_16(v) => DynVersion(Box::new(v.0)),
|
Self::V0_4_0_alpha_16(v) => DynVersion(Box::new(v.0)),
|
||||||
Self::V0_4_0_alpha_17(v) => DynVersion(Box::new(v.0)),
|
Self::V0_4_0_alpha_17(v) => DynVersion(Box::new(v.0)),
|
||||||
Self::V0_4_0_alpha_18(v) => DynVersion(Box::new(v.0)),
|
Self::V0_4_0_alpha_18(v) => DynVersion(Box::new(v.0)),
|
||||||
Self::V0_4_0_alpha_19(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
Self::V0_4_0_alpha_19(v) => DynVersion(Box::new(v.0)),
|
||||||
|
Self::V0_4_0_alpha_20(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||||
Self::Other(v) => {
|
Self::Other(v) => {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("unknown version {v}"),
|
eyre!("unknown version {v}"),
|
||||||
@@ -297,7 +300,8 @@ impl Version {
|
|||||||
Version::V0_4_0_alpha_16(Wrapper(x)) => x.semver(),
|
Version::V0_4_0_alpha_16(Wrapper(x)) => x.semver(),
|
||||||
Version::V0_4_0_alpha_17(Wrapper(x)) => x.semver(),
|
Version::V0_4_0_alpha_17(Wrapper(x)) => x.semver(),
|
||||||
Version::V0_4_0_alpha_18(Wrapper(x)) => x.semver(),
|
Version::V0_4_0_alpha_18(Wrapper(x)) => x.semver(),
|
||||||
Version::V0_4_0_alpha_19(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
Version::V0_4_0_alpha_19(Wrapper(x)) => x.semver(),
|
||||||
|
Version::V0_4_0_alpha_20(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||||
Version::Other(x) => x.clone(),
|
Version::Other(x) => x.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::BTreeMap;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -23,17 +23,14 @@ use crate::disk::mount::filesystem::cifs::Cifs;
|
|||||||
use crate::disk::mount::util::unmount;
|
use crate::disk::mount::util::unmount;
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::Hostname;
|
||||||
use crate::net::forward::AvailablePorts;
|
use crate::net::forward::AvailablePorts;
|
||||||
use crate::net::host::Host;
|
|
||||||
use crate::net::keys::KeyStore;
|
use crate::net::keys::KeyStore;
|
||||||
use crate::net::tor::{OnionAddress, TorSecretKey};
|
|
||||||
use crate::notifications::Notifications;
|
use crate::notifications::Notifications;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||||
use crate::ssh::{SshKeys, SshPubKey};
|
use crate::ssh::{SshKeys, SshPubKey};
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::util::crypto::ed25519_expand_key;
|
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
use crate::{DATA_DIR, HostId, Id, PACKAGE_DATA, PackageId, ReplayId};
|
use crate::{DATA_DIR, PACKAGE_DATA, PackageId, ReplayId};
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref V0_3_6_alpha_0: exver::Version = exver::Version::new(
|
static ref V0_3_6_alpha_0: exver::Version = exver::Version::new(
|
||||||
@@ -146,12 +143,7 @@ pub struct Version;
|
|||||||
|
|
||||||
impl VersionT for Version {
|
impl VersionT for Version {
|
||||||
type Previous = v0_3_5_2::Version;
|
type Previous = v0_3_5_2::Version;
|
||||||
type PreUpRes = (
|
type PreUpRes = (AccountInfo, SshKeys, CifsTargets);
|
||||||
AccountInfo,
|
|
||||||
SshKeys,
|
|
||||||
CifsTargets,
|
|
||||||
BTreeMap<PackageId, BTreeMap<HostId, TorSecretKey>>,
|
|
||||||
);
|
|
||||||
fn semver(self) -> exver::Version {
|
fn semver(self) -> exver::Version {
|
||||||
V0_3_6_alpha_0.clone()
|
V0_3_6_alpha_0.clone()
|
||||||
}
|
}
|
||||||
@@ -166,20 +158,18 @@ impl VersionT for Version {
|
|||||||
|
|
||||||
let cifs = previous_cifs(&pg).await?;
|
let cifs = previous_cifs(&pg).await?;
|
||||||
|
|
||||||
let tor_keys = previous_tor_keys(&pg).await?;
|
|
||||||
|
|
||||||
Command::new("systemctl")
|
Command::new("systemctl")
|
||||||
.arg("stop")
|
.arg("stop")
|
||||||
.arg("postgresql@*.service")
|
.arg("postgresql@*.service")
|
||||||
.invoke(crate::ErrorKind::Database)
|
.invoke(crate::ErrorKind::Database)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok((account, ssh_keys, cifs, tor_keys))
|
Ok((account, ssh_keys, cifs))
|
||||||
}
|
}
|
||||||
fn up(
|
fn up(
|
||||||
self,
|
self,
|
||||||
db: &mut Value,
|
db: &mut Value,
|
||||||
(account, ssh_keys, cifs, tor_keys): Self::PreUpRes,
|
(account, ssh_keys, cifs): Self::PreUpRes,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
let prev_package_data = db["package-data"].clone();
|
let prev_package_data = db["package-data"].clone();
|
||||||
|
|
||||||
@@ -242,11 +232,7 @@ impl VersionT for Version {
|
|||||||
"ui": db["ui"],
|
"ui": db["ui"],
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut keystore = KeyStore::new(&account)?;
|
let keystore = KeyStore::new(&account)?;
|
||||||
for key in tor_keys.values().flat_map(|v| v.values()) {
|
|
||||||
assert!(key.is_valid());
|
|
||||||
keystore.onion.insert(key.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let private = {
|
let private = {
|
||||||
let mut value = json!({});
|
let mut value = json!({});
|
||||||
@@ -350,20 +336,6 @@ impl VersionT for Version {
|
|||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
let onions = input[&*id]["installed"]["interface-addresses"]
|
|
||||||
.as_object()
|
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.filter_map(|(id, addrs)| {
|
|
||||||
addrs["tor-address"].as_str().map(|addr| {
|
|
||||||
Ok((
|
|
||||||
HostId::from(Id::try_from(id.clone())?),
|
|
||||||
addr.parse::<OnionAddress>()?,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Result<BTreeMap<_, _>, Error>>()?;
|
|
||||||
|
|
||||||
if let Err(e) = async {
|
if let Err(e) = async {
|
||||||
let package_s9pk = tokio::fs::File::open(path).await?;
|
let package_s9pk = tokio::fs::File::open(path).await?;
|
||||||
let file = MultiCursorFile::open(&package_s9pk).await?;
|
let file = MultiCursorFile::open(&package_s9pk).await?;
|
||||||
@@ -381,11 +353,8 @@ impl VersionT for Version {
|
|||||||
.await?
|
.await?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let to_sync = ctx
|
ctx.db
|
||||||
.db
|
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
let mut to_sync = BTreeSet::new();
|
|
||||||
|
|
||||||
let package = db
|
let package = db
|
||||||
.as_public_mut()
|
.as_public_mut()
|
||||||
.as_package_data_mut()
|
.as_package_data_mut()
|
||||||
@@ -396,29 +365,11 @@ impl VersionT for Version {
|
|||||||
.as_tasks_mut()
|
.as_tasks_mut()
|
||||||
.remove(&ReplayId::from("needs-config"))?;
|
.remove(&ReplayId::from("needs-config"))?;
|
||||||
}
|
}
|
||||||
for (id, onion) in onions {
|
Ok(())
|
||||||
package
|
|
||||||
.as_hosts_mut()
|
|
||||||
.upsert(&id, || Ok(Host::new()))?
|
|
||||||
.as_onions_mut()
|
|
||||||
.mutate(|o| {
|
|
||||||
o.clear();
|
|
||||||
o.insert(onion);
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
to_sync.insert(id);
|
|
||||||
}
|
|
||||||
Ok(to_sync)
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.result?;
|
.result?;
|
||||||
|
|
||||||
if let Some(service) = &*ctx.services.get(&id).await {
|
|
||||||
for host_id in to_sync {
|
|
||||||
service.sync_host(host_id.clone()).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok::<_, Error>(())
|
Ok::<_, Error>(())
|
||||||
}
|
}
|
||||||
.await
|
.await
|
||||||
@@ -481,33 +432,6 @@ async fn previous_account_info(pg: &sqlx::Pool<sqlx::Postgres>) -> Result<Accoun
|
|||||||
password: account_query
|
password: account_query
|
||||||
.try_get("password")
|
.try_get("password")
|
||||||
.with_ctx(|_| (ErrorKind::Database, "password"))?,
|
.with_ctx(|_| (ErrorKind::Database, "password"))?,
|
||||||
tor_keys: vec![TorSecretKey::from_bytes(
|
|
||||||
if let Some(bytes) = account_query
|
|
||||||
.try_get::<Option<Vec<u8>>, _>("tor_key")
|
|
||||||
.with_ctx(|_| (ErrorKind::Database, "tor_key"))?
|
|
||||||
{
|
|
||||||
<[u8; 64]>::try_from(bytes).map_err(|e| {
|
|
||||||
Error::new(
|
|
||||||
eyre!("expected vec of len 64, got len {}", e.len()),
|
|
||||||
ErrorKind::ParseDbField,
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
} else {
|
|
||||||
ed25519_expand_key(
|
|
||||||
&<[u8; 32]>::try_from(
|
|
||||||
account_query
|
|
||||||
.try_get::<Vec<u8>, _>("network_key")
|
|
||||||
.with_kind(ErrorKind::Database)?,
|
|
||||||
)
|
|
||||||
.map_err(|e| {
|
|
||||||
Error::new(
|
|
||||||
eyre!("expected vec of len 32, got len {}", e.len()),
|
|
||||||
ErrorKind::ParseDbField,
|
|
||||||
)
|
|
||||||
})?,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)?],
|
|
||||||
server_id: account_query
|
server_id: account_query
|
||||||
.try_get("server_id")
|
.try_get("server_id")
|
||||||
.with_ctx(|_| (ErrorKind::Database, "server_id"))?,
|
.with_ctx(|_| (ErrorKind::Database, "server_id"))?,
|
||||||
@@ -579,68 +503,3 @@ async fn previous_ssh_keys(pg: &sqlx::Pool<sqlx::Postgres>) -> Result<SshKeys, E
|
|||||||
Ok(ssh_keys)
|
Ok(ssh_keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
async fn previous_tor_keys(
|
|
||||||
pg: &sqlx::Pool<sqlx::Postgres>,
|
|
||||||
) -> Result<BTreeMap<PackageId, BTreeMap<HostId, TorSecretKey>>, Error> {
|
|
||||||
let mut res = BTreeMap::<PackageId, BTreeMap<HostId, TorSecretKey>>::new();
|
|
||||||
let net_key_query = sqlx::query(r#"SELECT * FROM network_keys"#)
|
|
||||||
.fetch_all(pg)
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Database)?;
|
|
||||||
|
|
||||||
for row in net_key_query {
|
|
||||||
let package_id: PackageId = row
|
|
||||||
.try_get::<String, _>("package")
|
|
||||||
.with_ctx(|_| (ErrorKind::Database, "network_keys::package"))?
|
|
||||||
.parse()?;
|
|
||||||
let interface_id: HostId = row
|
|
||||||
.try_get::<String, _>("interface")
|
|
||||||
.with_ctx(|_| (ErrorKind::Database, "network_keys::interface"))?
|
|
||||||
.parse()?;
|
|
||||||
let key = TorSecretKey::from_bytes(ed25519_expand_key(
|
|
||||||
&<[u8; 32]>::try_from(
|
|
||||||
row.try_get::<Vec<u8>, _>("key")
|
|
||||||
.with_ctx(|_| (ErrorKind::Database, "network_keys::key"))?,
|
|
||||||
)
|
|
||||||
.map_err(|e| {
|
|
||||||
Error::new(
|
|
||||||
eyre!("expected vec of len 32, got len {}", e.len()),
|
|
||||||
ErrorKind::ParseDbField,
|
|
||||||
)
|
|
||||||
})?,
|
|
||||||
))?;
|
|
||||||
res.entry(package_id).or_default().insert(interface_id, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tor_key_query = sqlx::query(r#"SELECT * FROM tor"#)
|
|
||||||
.fetch_all(pg)
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Database)?;
|
|
||||||
|
|
||||||
for row in tor_key_query {
|
|
||||||
let package_id: PackageId = row
|
|
||||||
.try_get::<String, _>("package")
|
|
||||||
.with_ctx(|_| (ErrorKind::Database, "tor::package"))?
|
|
||||||
.parse()?;
|
|
||||||
let interface_id: HostId = row
|
|
||||||
.try_get::<String, _>("interface")
|
|
||||||
.with_ctx(|_| (ErrorKind::Database, "tor::interface"))?
|
|
||||||
.parse()?;
|
|
||||||
let key = TorSecretKey::from_bytes(
|
|
||||||
<[u8; 64]>::try_from(
|
|
||||||
row.try_get::<Vec<u8>, _>("key")
|
|
||||||
.with_ctx(|_| (ErrorKind::Database, "tor::key"))?,
|
|
||||||
)
|
|
||||||
.map_err(|e| {
|
|
||||||
Error::new(
|
|
||||||
eyre!("expected vec of len 64, got len {}", e.len()),
|
|
||||||
ErrorKind::ParseDbField,
|
|
||||||
)
|
|
||||||
})?,
|
|
||||||
)?;
|
|
||||||
res.entry(package_id).or_default().insert(interface_id, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use super::v0_3_5::V0_3_0_COMPAT;
|
|||||||
use super::{VersionT, v0_3_6_alpha_9};
|
use super::{VersionT, v0_3_6_alpha_9};
|
||||||
use crate::GatewayId;
|
use crate::GatewayId;
|
||||||
use crate::net::host::address::PublicDomainConfig;
|
use crate::net::host::address::PublicDomainConfig;
|
||||||
use crate::net::tor::OnionAddress;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
@@ -22,7 +21,7 @@ lazy_static::lazy_static! {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[serde(tag = "kind")]
|
#[serde(tag = "kind")]
|
||||||
enum HostAddress {
|
enum HostAddress {
|
||||||
Onion { address: OnionAddress },
|
Onion { address: String },
|
||||||
Domain { address: InternedString },
|
Domain { address: InternedString },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
use exver::{PreReleaseSegment, VersionRange};
|
use exver::{PreReleaseSegment, VersionRange};
|
||||||
use imbl_value::InternedString;
|
|
||||||
|
|
||||||
use super::v0_3_5::V0_3_0_COMPAT;
|
use super::v0_3_5::V0_3_0_COMPAT;
|
||||||
use super::{VersionT, v0_4_0_alpha_11};
|
use super::{VersionT, v0_4_0_alpha_11};
|
||||||
use crate::net::tor::TorSecretKey;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
@@ -33,48 +29,6 @@ impl VersionT for Version {
|
|||||||
}
|
}
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||||
let mut err = None;
|
|
||||||
let onion_store = db["private"]["keyStore"]["onion"]
|
|
||||||
.as_object_mut()
|
|
||||||
.or_not_found("private.keyStore.onion")?;
|
|
||||||
onion_store.retain(|o, v| match from_value::<TorSecretKey>(v.clone()) {
|
|
||||||
Ok(k) => k.is_valid() && &InternedString::from_display(&k.onion_address()) == o,
|
|
||||||
Err(e) => {
|
|
||||||
err = Some(e);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(e) = err {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
let allowed_addresses = onion_store.keys().cloned().collect::<BTreeSet<_>>();
|
|
||||||
let fix_host = |host: &mut Value| {
|
|
||||||
Ok::<_, Error>(
|
|
||||||
host["onions"]
|
|
||||||
.as_array_mut()
|
|
||||||
.or_not_found("host.onions")?
|
|
||||||
.retain(|addr| {
|
|
||||||
addr.as_str()
|
|
||||||
.map(|s| allowed_addresses.contains(s))
|
|
||||||
.unwrap_or(false)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
for (_, pde) in db["public"]["packageData"]
|
|
||||||
.as_object_mut()
|
|
||||||
.or_not_found("public.packageData")?
|
|
||||||
.iter_mut()
|
|
||||||
{
|
|
||||||
for (_, host) in pde["hosts"]
|
|
||||||
.as_object_mut()
|
|
||||||
.or_not_found("public.packageData[].hosts")?
|
|
||||||
.iter_mut()
|
|
||||||
{
|
|
||||||
fix_host(host)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fix_host(&mut db["public"]["serverInfo"]["network"]["host"])?;
|
|
||||||
|
|
||||||
if db["private"]["keyStore"]["localCerts"].is_null() {
|
if db["private"]["keyStore"]["localCerts"].is_null() {
|
||||||
db["private"]["keyStore"]["localCerts"] =
|
db["private"]["keyStore"]["localCerts"] =
|
||||||
db["private"]["keyStore"]["local_certs"].clone();
|
db["private"]["keyStore"]["local_certs"].clone();
|
||||||
|
|||||||
110
core/src/version/v0_4_0_alpha_20.rs
Normal file
110
core/src/version/v0_4_0_alpha_20.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use exver::{PreReleaseSegment, VersionRange};
|
||||||
|
|
||||||
|
use super::v0_3_5::V0_3_0_COMPAT;
|
||||||
|
use super::{VersionT, v0_4_0_alpha_19};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref V0_4_0_alpha_20: exver::Version = exver::Version::new(
|
||||||
|
[0, 4, 0],
|
||||||
|
[PreReleaseSegment::String("alpha".into()), 20.into()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Version;
|
||||||
|
|
||||||
|
impl VersionT for Version {
|
||||||
|
type Previous = v0_4_0_alpha_19::Version;
|
||||||
|
type PreUpRes = ();
|
||||||
|
|
||||||
|
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn semver(self) -> exver::Version {
|
||||||
|
V0_4_0_alpha_20.clone()
|
||||||
|
}
|
||||||
|
fn compat(self) -> &'static VersionRange {
|
||||||
|
&V0_3_0_COMPAT
|
||||||
|
}
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||||
|
// Remove onions and tor-related fields from server host
|
||||||
|
if let Some(host) = db
|
||||||
|
.get_mut("public")
|
||||||
|
.and_then(|p| p.get_mut("serverInfo"))
|
||||||
|
.and_then(|s| s.get_mut("network"))
|
||||||
|
.and_then(|n| n.get_mut("host"))
|
||||||
|
.and_then(|h| h.as_object_mut())
|
||||||
|
{
|
||||||
|
host.remove("onions");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove onions from all package hosts
|
||||||
|
if let Some(packages) = db
|
||||||
|
.get_mut("public")
|
||||||
|
.and_then(|p| p.get_mut("packageData"))
|
||||||
|
.and_then(|p| p.as_object_mut())
|
||||||
|
{
|
||||||
|
for (_, package) in packages.iter_mut() {
|
||||||
|
if let Some(hosts) = package.get_mut("hosts").and_then(|h| h.as_object_mut()) {
|
||||||
|
for (_, host) in hosts.iter_mut() {
|
||||||
|
if let Some(host_obj) = host.as_object_mut() {
|
||||||
|
host_obj.remove("onions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove onion entries from hostnameInfo in server host
|
||||||
|
remove_onion_hostname_info(
|
||||||
|
db.get_mut("public")
|
||||||
|
.and_then(|p| p.get_mut("serverInfo"))
|
||||||
|
.and_then(|s| s.get_mut("network"))
|
||||||
|
.and_then(|n| n.get_mut("host")),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove onion entries from hostnameInfo in all package hosts
|
||||||
|
if let Some(packages) = db
|
||||||
|
.get_mut("public")
|
||||||
|
.and_then(|p| p.get_mut("packageData"))
|
||||||
|
.and_then(|p| p.as_object_mut())
|
||||||
|
{
|
||||||
|
for (_, package) in packages.iter_mut() {
|
||||||
|
if let Some(hosts) = package.get_mut("hosts").and_then(|h| h.as_object_mut()) {
|
||||||
|
for (_, host) in hosts.iter_mut() {
|
||||||
|
remove_onion_hostname_info(Some(host));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove onion store from private keyStore
|
||||||
|
if let Some(key_store) = db
|
||||||
|
.get_mut("private")
|
||||||
|
.and_then(|p| p.get_mut("keyStore"))
|
||||||
|
.and_then(|k| k.as_object_mut())
|
||||||
|
{
|
||||||
|
key_store.remove("onion");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Null)
|
||||||
|
}
|
||||||
|
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_onion_hostname_info(host: Option<&mut Value>) {
|
||||||
|
if let Some(hostname_info) = host
|
||||||
|
.and_then(|h| h.get_mut("hostnameInfo"))
|
||||||
|
.and_then(|h| h.as_object_mut())
|
||||||
|
{
|
||||||
|
for (_, infos) in hostname_info.iter_mut() {
|
||||||
|
if let Some(arr) = infos.as_array_mut() {
|
||||||
|
arr.retain(|info| info.get("kind").and_then(|k| k.as_str()) != Some("onion"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type ErrorData = { details: string; debug: string }
|
export type ErrorData = { details: string; debug: string; info: unknown }
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import type { PublicDomainConfig } from './PublicDomainConfig'
|
|||||||
|
|
||||||
export type Host = {
|
export type Host = {
|
||||||
bindings: { [key: number]: BindInfo }
|
bindings: { [key: number]: BindInfo }
|
||||||
onions: string[]
|
|
||||||
publicDomains: { [key: string]: PublicDomainConfig }
|
publicDomains: { [key: string]: PublicDomainConfig }
|
||||||
privateDomains: Array<string>
|
privateDomains: Array<string>
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { GatewayInfo } from './GatewayInfo'
|
import type { GatewayInfo } from './GatewayInfo'
|
||||||
import type { IpHostname } from './IpHostname'
|
import type { IpHostname } from './IpHostname'
|
||||||
import type { OnionHostname } from './OnionHostname'
|
|
||||||
|
|
||||||
export type HostnameInfo =
|
export type HostnameInfo = {
|
||||||
| { kind: 'ip'; gateway: GatewayInfo; public: boolean; hostname: IpHostname }
|
kind: 'ip'
|
||||||
| { kind: 'onion'; hostname: OnionHostname }
|
gateway: GatewayInfo
|
||||||
|
public: boolean
|
||||||
|
hostname: IpHostname
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type OnionHostname = {
|
|
||||||
value: string
|
|
||||||
port: number | null
|
|
||||||
sslPort: number | null
|
|
||||||
}
|
|
||||||
@@ -149,7 +149,6 @@ export { NetInfo } from './NetInfo'
|
|||||||
export { NetworkInfo } from './NetworkInfo'
|
export { NetworkInfo } from './NetworkInfo'
|
||||||
export { NetworkInterfaceInfo } from './NetworkInterfaceInfo'
|
export { NetworkInterfaceInfo } from './NetworkInterfaceInfo'
|
||||||
export { NetworkInterfaceType } from './NetworkInterfaceType'
|
export { NetworkInterfaceType } from './NetworkInterfaceType'
|
||||||
export { OnionHostname } from './OnionHostname'
|
|
||||||
export { OsIndex } from './OsIndex'
|
export { OsIndex } from './OsIndex'
|
||||||
export { OsVersionInfoMap } from './OsVersionInfoMap'
|
export { OsVersionInfoMap } from './OsVersionInfoMap'
|
||||||
export { OsVersionInfo } from './OsVersionInfo'
|
export { OsVersionInfo } from './OsVersionInfo'
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export const getHostname = (url: string): Hostname | null => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FilterKinds =
|
type FilterKinds =
|
||||||
| 'onion'
|
|
||||||
| 'mdns'
|
| 'mdns'
|
||||||
| 'domain'
|
| 'domain'
|
||||||
| 'ip'
|
| 'ip'
|
||||||
@@ -42,27 +41,25 @@ type VisibilityFilter<V extends 'public' | 'private'> = V extends 'public'
|
|||||||
| (HostnameInfo & { public: false })
|
| (HostnameInfo & { public: false })
|
||||||
| VisibilityFilter<Exclude<V, 'private'>>
|
| VisibilityFilter<Exclude<V, 'private'>>
|
||||||
: never
|
: never
|
||||||
type KindFilter<K extends FilterKinds> = K extends 'onion'
|
type KindFilter<K extends FilterKinds> = K extends 'mdns'
|
||||||
? (HostnameInfo & { kind: 'onion' }) | KindFilter<Exclude<K, 'onion'>>
|
?
|
||||||
: K extends 'mdns'
|
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'local' } })
|
||||||
|
| KindFilter<Exclude<K, 'mdns'>>
|
||||||
|
: K extends 'domain'
|
||||||
?
|
?
|
||||||
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'local' } })
|
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'domain' } })
|
||||||
| KindFilter<Exclude<K, 'mdns'>>
|
| KindFilter<Exclude<K, 'domain'>>
|
||||||
: K extends 'domain'
|
: K extends 'ipv4'
|
||||||
?
|
?
|
||||||
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'domain' } })
|
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'ipv4' } })
|
||||||
| KindFilter<Exclude<K, 'domain'>>
|
| KindFilter<Exclude<K, 'ipv4'>>
|
||||||
: K extends 'ipv4'
|
: K extends 'ipv6'
|
||||||
?
|
?
|
||||||
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'ipv4' } })
|
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'ipv6' } })
|
||||||
| KindFilter<Exclude<K, 'ipv4'>>
|
| KindFilter<Exclude<K, 'ipv6'>>
|
||||||
: K extends 'ipv6'
|
: K extends 'ip'
|
||||||
?
|
? KindFilter<Exclude<K, 'ip'> | 'ipv4' | 'ipv6'>
|
||||||
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'ipv6' } })
|
: never
|
||||||
| KindFilter<Exclude<K, 'ipv6'>>
|
|
||||||
: K extends 'ip'
|
|
||||||
? KindFilter<Exclude<K, 'ip'> | 'ipv4' | 'ipv6'>
|
|
||||||
: never
|
|
||||||
|
|
||||||
type FilterReturnTy<F extends Filter> = F extends {
|
type FilterReturnTy<F extends Filter> = F extends {
|
||||||
visibility: infer V extends 'public' | 'private'
|
visibility: infer V extends 'public' | 'private'
|
||||||
@@ -90,10 +87,6 @@ const nonLocalFilter = {
|
|||||||
const publicFilter = {
|
const publicFilter = {
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
} as const
|
} as const
|
||||||
const onionFilter = {
|
|
||||||
kind: 'onion',
|
|
||||||
} as const
|
|
||||||
|
|
||||||
type Formats = 'hostname-info' | 'urlstring' | 'url'
|
type Formats = 'hostname-info' | 'urlstring' | 'url'
|
||||||
type FormatReturnTy<
|
type FormatReturnTy<
|
||||||
F extends Filter,
|
F extends Filter,
|
||||||
@@ -124,7 +117,6 @@ export type Filled<F extends Filter = {}> = {
|
|||||||
|
|
||||||
nonLocal: Filled<typeof nonLocalFilter & Filter>
|
nonLocal: Filled<typeof nonLocalFilter & Filter>
|
||||||
public: Filled<typeof publicFilter & Filter>
|
public: Filled<typeof publicFilter & Filter>
|
||||||
onion: Filled<typeof onionFilter & Filter>
|
|
||||||
}
|
}
|
||||||
export type FilledAddressInfo = AddressInfo & Filled
|
export type FilledAddressInfo = AddressInfo & Filled
|
||||||
export type ServiceInterfaceFilled = {
|
export type ServiceInterfaceFilled = {
|
||||||
@@ -162,9 +154,7 @@ export const addressHostToUrl = (
|
|||||||
scheme in knownProtocols &&
|
scheme in knownProtocols &&
|
||||||
port === knownProtocols[scheme as keyof typeof knownProtocols].defaultPort
|
port === knownProtocols[scheme as keyof typeof knownProtocols].defaultPort
|
||||||
let hostname
|
let hostname
|
||||||
if (host.kind === 'onion') {
|
if (host.kind === 'ip') {
|
||||||
hostname = host.hostname.value
|
|
||||||
} else if (host.kind === 'ip') {
|
|
||||||
if (host.hostname.kind === 'domain') {
|
if (host.hostname.kind === 'domain') {
|
||||||
hostname = host.hostname.value
|
hostname = host.hostname.value
|
||||||
} else if (host.hostname.kind === 'ipv6') {
|
} else if (host.hostname.kind === 'ipv6') {
|
||||||
@@ -201,13 +191,9 @@ function filterRec(
|
|||||||
hostnames = hostnames.filter((h) => invert !== pred(h))
|
hostnames = hostnames.filter((h) => invert !== pred(h))
|
||||||
}
|
}
|
||||||
if (filter.visibility === 'public')
|
if (filter.visibility === 'public')
|
||||||
hostnames = hostnames.filter(
|
hostnames = hostnames.filter((h) => invert !== h.public)
|
||||||
(h) => invert !== (h.kind === 'onion' || h.public),
|
|
||||||
)
|
|
||||||
if (filter.visibility === 'private')
|
if (filter.visibility === 'private')
|
||||||
hostnames = hostnames.filter(
|
hostnames = hostnames.filter((h) => invert !== !h.public)
|
||||||
(h) => invert !== (h.kind !== 'onion' && !h.public),
|
|
||||||
)
|
|
||||||
if (filter.kind) {
|
if (filter.kind) {
|
||||||
const kind = new Set(
|
const kind = new Set(
|
||||||
Array.isArray(filter.kind) ? filter.kind : [filter.kind],
|
Array.isArray(filter.kind) ? filter.kind : [filter.kind],
|
||||||
@@ -219,10 +205,7 @@ function filterRec(
|
|||||||
hostnames = hostnames.filter(
|
hostnames = hostnames.filter(
|
||||||
(h) =>
|
(h) =>
|
||||||
invert !==
|
invert !==
|
||||||
((kind.has('onion') && h.kind === 'onion') ||
|
((kind.has('mdns') && h.kind === 'ip' && h.hostname.kind === 'local') ||
|
||||||
(kind.has('mdns') &&
|
|
||||||
h.kind === 'ip' &&
|
|
||||||
h.hostname.kind === 'local') ||
|
|
||||||
(kind.has('domain') &&
|
(kind.has('domain') &&
|
||||||
h.kind === 'ip' &&
|
h.kind === 'ip' &&
|
||||||
h.hostname.kind === 'domain') ||
|
h.hostname.kind === 'domain') ||
|
||||||
@@ -266,11 +249,6 @@ export const filledAddress = (
|
|||||||
filterRec(hostnames, publicFilter, false),
|
filterRec(hostnames, publicFilter, false),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
const getOnion = once(() =>
|
|
||||||
filledAddressFromHostnames<typeof onionFilter & F>(
|
|
||||||
filterRec(hostnames, onionFilter, false),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
...addressInfo,
|
...addressInfo,
|
||||||
hostnames,
|
hostnames,
|
||||||
@@ -294,9 +272,6 @@ export const filledAddress = (
|
|||||||
get public(): Filled<typeof publicFilter & F> {
|
get public(): Filled<typeof publicFilter & F> {
|
||||||
return getPublic()
|
return getPublic()
|
||||||
},
|
},
|
||||||
get onion(): Filled<typeof onionFilter & F> {
|
|
||||||
return getOnion()
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,6 @@ export const localHostname: Pattern = {
|
|||||||
description: 'Must be a valid ".local" hostname',
|
description: 'Must be a valid ".local" hostname',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const torHostname: Pattern = {
|
|
||||||
regex: regexes.torHostname.matches(),
|
|
||||||
description: 'Must be a valid Tor (".onion") hostname',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const url: Pattern = {
|
export const url: Pattern = {
|
||||||
regex: regexes.url.matches(),
|
regex: regexes.url.matches(),
|
||||||
description: 'Must be a valid URL',
|
description: 'Must be a valid URL',
|
||||||
@@ -36,11 +31,6 @@ export const localUrl: Pattern = {
|
|||||||
description: 'Must be a valid ".local" URL',
|
description: 'Must be a valid ".local" URL',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const torUrl: Pattern = {
|
|
||||||
regex: regexes.torUrl.matches(),
|
|
||||||
description: 'Must be a valid Tor (".onion") URL',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ascii: Pattern = {
|
export const ascii: Pattern = {
|
||||||
regex: regexes.ascii.matches(),
|
regex: regexes.ascii.matches(),
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -39,10 +39,6 @@ export const localHostname = new ComposableRegex(
|
|||||||
/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.local/,
|
/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.local/,
|
||||||
)
|
)
|
||||||
|
|
||||||
export const torHostname = new ComposableRegex(
|
|
||||||
/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.onion/,
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://ihateregex.io/expr/url/
|
// https://ihateregex.io/expr/url/
|
||||||
export const url = new ComposableRegex(
|
export const url = new ComposableRegex(
|
||||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/,
|
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/,
|
||||||
@@ -52,10 +48,6 @@ export const localUrl = new ComposableRegex(
|
|||||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.local\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/,
|
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.local\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/,
|
||||||
)
|
)
|
||||||
|
|
||||||
export const torUrl = new ComposableRegex(
|
|
||||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.onion\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/,
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://ihateregex.io/expr/ascii/
|
// https://ihateregex.io/expr/ascii/
|
||||||
export const ascii = new ComposableRegex(/[ -~]*/)
|
export const ascii = new ComposableRegex(/[ -~]*/)
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import * as fs from "fs"
|
import * as fs from 'fs'
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/2970525/converting-any-string-into-camel-case
|
// https://stackoverflow.com/questions/2970525/converting-any-string-into-camel-case
|
||||||
export function camelCase(value: string) {
|
export function camelCase(value: string) {
|
||||||
return value
|
return value
|
||||||
.replace(/([\(\)\[\]])/g, "")
|
.replace(/([\(\)\[\]])/g, '')
|
||||||
.replace(/^([A-Z])|[\s-_](\w)/g, function (match, p1, p2, offset) {
|
.replace(/^([A-Z])|[\s-_](\w)/g, function (match, p1, p2, offset) {
|
||||||
if (p2) return p2.toUpperCase()
|
if (p2) return p2.toUpperCase()
|
||||||
return p1.toLowerCase()
|
return p1.toLowerCase()
|
||||||
@@ -23,12 +23,12 @@ export async function oldSpecToBuilder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isString(x: unknown): x is string {
|
function isString(x: unknown): x is string {
|
||||||
return typeof x === "string"
|
return typeof x === 'string'
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function makeFileContentFromOld(
|
export default async function makeFileContentFromOld(
|
||||||
inputData: Promise<any> | any,
|
inputData: Promise<any> | any,
|
||||||
{ StartSdk = "start-sdk", nested = true } = {},
|
{ StartSdk = 'start-sdk', nested = true } = {},
|
||||||
) {
|
) {
|
||||||
const outputLines: string[] = []
|
const outputLines: string[] = []
|
||||||
outputLines.push(`
|
outputLines.push(`
|
||||||
@@ -37,16 +37,16 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
`)
|
`)
|
||||||
const data = await inputData
|
const data = await inputData
|
||||||
|
|
||||||
const namedConsts = new Set(["InputSpec", "Value", "List"])
|
const namedConsts = new Set(['InputSpec', 'Value', 'List'])
|
||||||
const inputSpecName = newConst("inputSpecSpec", convertInputSpec(data))
|
const inputSpecName = newConst('inputSpecSpec', convertInputSpec(data))
|
||||||
outputLines.push(`export type InputSpecSpec = typeof ${inputSpecName}._TYPE;`)
|
outputLines.push(`export type InputSpecSpec = typeof ${inputSpecName}._TYPE;`)
|
||||||
|
|
||||||
return outputLines.join("\n")
|
return outputLines.join('\n')
|
||||||
|
|
||||||
function newConst(key: string, data: string, type?: string) {
|
function newConst(key: string, data: string, type?: string) {
|
||||||
const variableName = getNextConstName(camelCase(key))
|
const variableName = getNextConstName(camelCase(key))
|
||||||
outputLines.push(
|
outputLines.push(
|
||||||
`export const ${variableName}${!type ? "" : `: ${type}`} = ${data};`,
|
`export const ${variableName}${!type ? '' : `: ${type}`} = ${data};`,
|
||||||
)
|
)
|
||||||
return variableName
|
return variableName
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
return newConst(key, data)
|
return newConst(key, data)
|
||||||
}
|
}
|
||||||
function convertInputSpecInner(data: any) {
|
function convertInputSpecInner(data: any) {
|
||||||
let answer = "{"
|
let answer = '{'
|
||||||
for (const [key, value] of Object.entries(data)) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
const variableName = maybeNewConst(key, convertValueSpec(value))
|
const variableName = maybeNewConst(key, convertValueSpec(value))
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
}
|
}
|
||||||
function convertValueSpec(value: any): string {
|
function convertValueSpec(value: any): string {
|
||||||
switch (value.type) {
|
switch (value.type) {
|
||||||
case "string": {
|
case 'string': {
|
||||||
if (value.textarea) {
|
if (value.textarea) {
|
||||||
return `${rangeToTodoComment(
|
return `${rangeToTodoComment(
|
||||||
value?.range,
|
value?.range,
|
||||||
@@ -99,12 +99,12 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
warning: value.warning || null,
|
warning: value.warning || null,
|
||||||
masked: value.masked || false,
|
masked: value.masked || false,
|
||||||
placeholder: value.placeholder || null,
|
placeholder: value.placeholder || null,
|
||||||
inputmode: "text",
|
inputmode: 'text',
|
||||||
patterns: value.pattern
|
patterns: value.pattern
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
regex: value.pattern,
|
regex: value.pattern,
|
||||||
description: value["pattern-description"],
|
description: value['pattern-description'],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
@@ -115,7 +115,7 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
2,
|
2,
|
||||||
)})`
|
)})`
|
||||||
}
|
}
|
||||||
case "number": {
|
case 'number': {
|
||||||
return `${rangeToTodoComment(
|
return `${rangeToTodoComment(
|
||||||
value?.range,
|
value?.range,
|
||||||
)}Value.number(${JSON.stringify(
|
)}Value.number(${JSON.stringify(
|
||||||
@@ -136,7 +136,7 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
2,
|
2,
|
||||||
)})`
|
)})`
|
||||||
}
|
}
|
||||||
case "boolean": {
|
case 'boolean': {
|
||||||
return `Value.toggle(${JSON.stringify(
|
return `Value.toggle(${JSON.stringify(
|
||||||
{
|
{
|
||||||
name: value.name || null,
|
name: value.name || null,
|
||||||
@@ -148,15 +148,15 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
2,
|
2,
|
||||||
)})`
|
)})`
|
||||||
}
|
}
|
||||||
case "enum": {
|
case 'enum': {
|
||||||
const allValueNames = new Set([
|
const allValueNames = new Set([
|
||||||
...(value?.["values"] || []),
|
...(value?.['values'] || []),
|
||||||
...Object.keys(value?.["value-names"] || {}),
|
...Object.keys(value?.['value-names'] || {}),
|
||||||
])
|
])
|
||||||
const values = Object.fromEntries(
|
const values = Object.fromEntries(
|
||||||
Array.from(allValueNames)
|
Array.from(allValueNames)
|
||||||
.filter(isString)
|
.filter(isString)
|
||||||
.map((key) => [key, value?.spec?.["value-names"]?.[key] || key]),
|
.map((key) => [key, value?.spec?.['value-names']?.[key] || key]),
|
||||||
)
|
)
|
||||||
return `Value.select(${JSON.stringify(
|
return `Value.select(${JSON.stringify(
|
||||||
{
|
{
|
||||||
@@ -170,9 +170,9 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
2,
|
2,
|
||||||
)} as const)`
|
)} as const)`
|
||||||
}
|
}
|
||||||
case "object": {
|
case 'object': {
|
||||||
const specName = maybeNewConst(
|
const specName = maybeNewConst(
|
||||||
value.name + "_spec",
|
value.name + '_spec',
|
||||||
convertInputSpec(value.spec),
|
convertInputSpec(value.spec),
|
||||||
)
|
)
|
||||||
return `Value.object({
|
return `Value.object({
|
||||||
@@ -180,10 +180,10 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
description: ${JSON.stringify(value.description || null)},
|
description: ${JSON.stringify(value.description || null)},
|
||||||
}, ${specName})`
|
}, ${specName})`
|
||||||
}
|
}
|
||||||
case "union": {
|
case 'union': {
|
||||||
const variants = maybeNewConst(
|
const variants = maybeNewConst(
|
||||||
value.name + "_variants",
|
value.name + '_variants',
|
||||||
convertVariants(value.variants, value.tag["variant-names"] || {}),
|
convertVariants(value.variants, value.tag['variant-names'] || {}),
|
||||||
)
|
)
|
||||||
|
|
||||||
return `Value.union({
|
return `Value.union({
|
||||||
@@ -194,18 +194,18 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
variants: ${variants},
|
variants: ${variants},
|
||||||
})`
|
})`
|
||||||
}
|
}
|
||||||
case "list": {
|
case 'list': {
|
||||||
if (value.subtype === "enum") {
|
if (value.subtype === 'enum') {
|
||||||
const allValueNames = new Set([
|
const allValueNames = new Set([
|
||||||
...(value?.spec?.["values"] || []),
|
...(value?.spec?.['values'] || []),
|
||||||
...Object.keys(value?.spec?.["value-names"] || {}),
|
...Object.keys(value?.spec?.['value-names'] || {}),
|
||||||
])
|
])
|
||||||
const values = Object.fromEntries(
|
const values = Object.fromEntries(
|
||||||
Array.from(allValueNames)
|
Array.from(allValueNames)
|
||||||
.filter(isString)
|
.filter(isString)
|
||||||
.map((key: string) => [
|
.map((key: string) => [
|
||||||
key,
|
key,
|
||||||
value?.spec?.["value-names"]?.[key] ?? key,
|
value?.spec?.['value-names']?.[key] ?? key,
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
return `Value.multiselect(${JSON.stringify(
|
return `Value.multiselect(${JSON.stringify(
|
||||||
@@ -222,10 +222,10 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
2,
|
2,
|
||||||
)})`
|
)})`
|
||||||
}
|
}
|
||||||
const list = maybeNewConst(value.name + "_list", convertList(value))
|
const list = maybeNewConst(value.name + '_list', convertList(value))
|
||||||
return `Value.list(${list})`
|
return `Value.list(${list})`
|
||||||
}
|
}
|
||||||
case "pointer": {
|
case 'pointer': {
|
||||||
return `/* TODO deal with point removed point "${value.name}" */null as any`
|
return `/* TODO deal with point removed point "${value.name}" */null as any`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,7 +234,7 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
|
|
||||||
function convertList(value: any) {
|
function convertList(value: any) {
|
||||||
switch (value.subtype) {
|
switch (value.subtype) {
|
||||||
case "string": {
|
case 'string': {
|
||||||
return `${rangeToTodoComment(value?.range)}List.text(${JSON.stringify(
|
return `${rangeToTodoComment(value?.range)}List.text(${JSON.stringify(
|
||||||
{
|
{
|
||||||
name: value.name || null,
|
name: value.name || null,
|
||||||
@@ -253,7 +253,7 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
regex: value.spec.pattern,
|
regex: value.spec.pattern,
|
||||||
description: value?.spec?.["pattern-description"],
|
description: value?.spec?.['pattern-description'],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
@@ -281,12 +281,12 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
// placeholder: value?.spec?.placeholder || null,
|
// placeholder: value?.spec?.placeholder || null,
|
||||||
// })})`
|
// })})`
|
||||||
// }
|
// }
|
||||||
case "enum": {
|
case 'enum': {
|
||||||
return "/* error!! list.enum */"
|
return '/* error!! list.enum */'
|
||||||
}
|
}
|
||||||
case "object": {
|
case 'object': {
|
||||||
const specName = maybeNewConst(
|
const specName = maybeNewConst(
|
||||||
value.name + "_spec",
|
value.name + '_spec',
|
||||||
convertInputSpec(value.spec.spec),
|
convertInputSpec(value.spec.spec),
|
||||||
)
|
)
|
||||||
return `${rangeToTodoComment(value?.range)}List.obj({
|
return `${rangeToTodoComment(value?.range)}List.obj({
|
||||||
@@ -297,20 +297,20 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
description: ${JSON.stringify(value.description || null)},
|
description: ${JSON.stringify(value.description || null)},
|
||||||
}, {
|
}, {
|
||||||
spec: ${specName},
|
spec: ${specName},
|
||||||
displayAs: ${JSON.stringify(value?.spec?.["display-as"] || null)},
|
displayAs: ${JSON.stringify(value?.spec?.['display-as'] || null)},
|
||||||
uniqueBy: ${JSON.stringify(value?.spec?.["unique-by"] || null)},
|
uniqueBy: ${JSON.stringify(value?.spec?.['unique-by'] || null)},
|
||||||
})`
|
})`
|
||||||
}
|
}
|
||||||
case "union": {
|
case 'union': {
|
||||||
const variants = maybeNewConst(
|
const variants = maybeNewConst(
|
||||||
value.name + "_variants",
|
value.name + '_variants',
|
||||||
convertVariants(
|
convertVariants(
|
||||||
value.spec.variants,
|
value.spec.variants,
|
||||||
value.spec["variant-names"] || {},
|
value.spec['variant-names'] || {},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
const unionValueName = maybeNewConst(
|
const unionValueName = maybeNewConst(
|
||||||
value.name + "_union",
|
value.name + '_union',
|
||||||
`${rangeToTodoComment(value?.range)}
|
`${rangeToTodoComment(value?.range)}
|
||||||
Value.union({
|
Value.union({
|
||||||
name: ${JSON.stringify(value?.spec?.tag?.name || null)},
|
name: ${JSON.stringify(value?.spec?.tag?.name || null)},
|
||||||
@@ -324,7 +324,7 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
const listInputSpec = maybeNewConst(
|
const listInputSpec = maybeNewConst(
|
||||||
value.name + "_list_inputSpec",
|
value.name + '_list_inputSpec',
|
||||||
`
|
`
|
||||||
InputSpec.of({
|
InputSpec.of({
|
||||||
"union": ${unionValueName}
|
"union": ${unionValueName}
|
||||||
@@ -340,8 +340,8 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
warning: ${JSON.stringify(value.warning || null)},
|
warning: ${JSON.stringify(value.warning || null)},
|
||||||
}, {
|
}, {
|
||||||
spec: ${listInputSpec},
|
spec: ${listInputSpec},
|
||||||
displayAs: ${JSON.stringify(value?.spec?.["display-as"] || null)},
|
displayAs: ${JSON.stringify(value?.spec?.['display-as'] || null)},
|
||||||
uniqueBy: ${JSON.stringify(value?.spec?.["unique-by"] || null)},
|
uniqueBy: ${JSON.stringify(value?.spec?.['unique-by'] || null)},
|
||||||
})`
|
})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,7 +352,7 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
variants: Record<string, unknown>,
|
variants: Record<string, unknown>,
|
||||||
variantNames: Record<string, string>,
|
variantNames: Record<string, string>,
|
||||||
): string {
|
): string {
|
||||||
let answer = "Variants.of({"
|
let answer = 'Variants.of({'
|
||||||
for (const [key, value] of Object.entries(variants)) {
|
for (const [key, value] of Object.entries(variants)) {
|
||||||
const variantSpec = maybeNewConst(key, convertInputSpec(value))
|
const variantSpec = maybeNewConst(key, convertInputSpec(value))
|
||||||
answer += `"${key}": {name: "${
|
answer += `"${key}": {name: "${
|
||||||
@@ -373,7 +373,7 @@ const {InputSpec, List, Value, Variants} = sdk
|
|||||||
}
|
}
|
||||||
|
|
||||||
function rangeToTodoComment(range: string | undefined) {
|
function rangeToTodoComment(range: string | undefined) {
|
||||||
if (!range) return ""
|
if (!range) return ''
|
||||||
return `/* TODO: Convert range for this value (${range})*/`
|
return `/* TODO: Convert range for this value (${range})*/`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
export type AccessType =
|
export type AccessType =
|
||||||
| 'tor'
|
|
||||||
| 'mdns'
|
| 'mdns'
|
||||||
| 'localhost'
|
| 'localhost'
|
||||||
| 'ipv4'
|
| 'ipv4'
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
|||||||
import { tuiButtonOptionsProvider } from '@taiga-ui/core'
|
import { tuiButtonOptionsProvider } from '@taiga-ui/core'
|
||||||
import { MappedServiceInterface } from './interface.service'
|
import { MappedServiceInterface } from './interface.service'
|
||||||
import { InterfaceGatewaysComponent } from './gateways.component'
|
import { InterfaceGatewaysComponent } from './gateways.component'
|
||||||
import { InterfaceTorDomainsComponent } from './tor-domains.component'
|
|
||||||
import { PublicDomainsComponent } from './public-domains/pd.component'
|
import { PublicDomainsComponent } from './public-domains/pd.component'
|
||||||
import { InterfacePrivateDomainsComponent } from './private-domains.component'
|
import { InterfacePrivateDomainsComponent } from './private-domains.component'
|
||||||
import { InterfaceAddressesComponent } from './addresses/addresses.component'
|
import { InterfaceAddressesComponent } from './addresses/addresses.component'
|
||||||
@@ -16,7 +15,6 @@ import { InterfaceAddressesComponent } from './addresses/addresses.component'
|
|||||||
[publicDomains]="value()?.publicDomains"
|
[publicDomains]="value()?.publicDomains"
|
||||||
[addSsl]="value()?.addSsl || false"
|
[addSsl]="value()?.addSsl || false"
|
||||||
></section>
|
></section>
|
||||||
<section [torDomains]="value()?.torDomains"></section>
|
|
||||||
<section [privateDomains]="value()?.privateDomains"></section>
|
<section [privateDomains]="value()?.privateDomains"></section>
|
||||||
</div>
|
</div>
|
||||||
<hr [style.width.rem]="10" />
|
<hr [style.width.rem]="10" />
|
||||||
@@ -52,7 +50,6 @@ import { InterfaceAddressesComponent } from './addresses/addresses.component'
|
|||||||
providers: [tuiButtonOptionsProvider({ size: 'xs' })],
|
providers: [tuiButtonOptionsProvider({ size: 'xs' })],
|
||||||
imports: [
|
imports: [
|
||||||
InterfaceGatewaysComponent,
|
InterfaceGatewaysComponent,
|
||||||
InterfaceTorDomainsComponent,
|
|
||||||
PublicDomainsComponent,
|
PublicDomainsComponent,
|
||||||
InterfacePrivateDomainsComponent,
|
InterfacePrivateDomainsComponent,
|
||||||
InterfaceAddressesComponent,
|
InterfaceAddressesComponent,
|
||||||
|
|||||||
@@ -27,14 +27,6 @@ function cmpWithRankedPredicates<T extends AddressWithInfo>(
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
type TorAddress = AddressWithInfo & { info: { kind: 'onion' } }
|
|
||||||
function filterTor(a: AddressWithInfo): a is TorAddress {
|
|
||||||
return a.info.kind === 'onion'
|
|
||||||
}
|
|
||||||
function cmpTor(a: TorAddress, b: TorAddress): -1 | 0 | 1 {
|
|
||||||
return cmpWithRankedPredicates(a, b, [x => !x.showSsl])
|
|
||||||
}
|
|
||||||
|
|
||||||
type LanAddress = AddressWithInfo & { info: { kind: 'ip'; public: false } }
|
type LanAddress = AddressWithInfo & { info: { kind: 'ip'; public: false } }
|
||||||
function filterLan(a: AddressWithInfo): a is LanAddress {
|
function filterLan(a: AddressWithInfo): a is LanAddress {
|
||||||
return a.info.kind === 'ip' && !a.info.public
|
return a.info.kind === 'ip' && !a.info.public
|
||||||
@@ -171,7 +163,6 @@ export class InterfaceService {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const torAddrs = allAddressesWithInfo.filter(filterTor).sort(cmpTor)
|
|
||||||
const lanAddrs = allAddressesWithInfo
|
const lanAddrs = allAddressesWithInfo
|
||||||
.filter(filterLan)
|
.filter(filterLan)
|
||||||
.sort((a, b) => cmpLan(host, a, b))
|
.sort((a, b) => cmpLan(host, a, b))
|
||||||
@@ -188,7 +179,6 @@ export class InterfaceService {
|
|||||||
clearnetAddrs[0],
|
clearnetAddrs[0],
|
||||||
lanAddrs[0],
|
lanAddrs[0],
|
||||||
vpnAddrs[0],
|
vpnAddrs[0],
|
||||||
torAddrs[0],
|
|
||||||
]
|
]
|
||||||
.filter(a => !!a)
|
.filter(a => !!a)
|
||||||
.reduce((acc, x) => {
|
.reduce((acc, x) => {
|
||||||
@@ -214,9 +204,8 @@ export class InterfaceService {
|
|||||||
kind: 'domain',
|
kind: 'domain',
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
})
|
})
|
||||||
const tor = addresses.filter({ kind: 'onion' })
|
|
||||||
const wanIp = addresses.filter({ kind: 'ipv4', visibility: 'public' })
|
const wanIp = addresses.filter({ kind: 'ipv4', visibility: 'public' })
|
||||||
const bestPublic = [publicDomains, tor, wanIp].flatMap(h =>
|
const bestPublic = [publicDomains, wanIp].flatMap(h =>
|
||||||
h.format('urlstring'),
|
h.format('urlstring'),
|
||||||
)[0]
|
)[0]
|
||||||
const privateDomains = addresses.filter({
|
const privateDomains = addresses.filter({
|
||||||
@@ -254,9 +243,6 @@ export class InterfaceService {
|
|||||||
.format('urlstring')[0]
|
.format('urlstring')[0]
|
||||||
onLan = true
|
onLan = true
|
||||||
break
|
break
|
||||||
case 'tor':
|
|
||||||
matching = tor.format('urlstring')[0]
|
|
||||||
break
|
|
||||||
case 'mdns':
|
case 'mdns':
|
||||||
matching = mdns.format('urlstring')[0]
|
matching = mdns.format('urlstring')[0]
|
||||||
onLan = true
|
onLan = true
|
||||||
@@ -302,31 +288,7 @@ export class InterfaceService {
|
|||||||
"Requires trusting your server's Root CA",
|
"Requires trusting your server's Root CA",
|
||||||
)
|
)
|
||||||
|
|
||||||
// ** Tor **
|
{
|
||||||
if (info.kind === 'onion') {
|
|
||||||
access = null
|
|
||||||
gatewayName = null
|
|
||||||
type = 'Tor'
|
|
||||||
bullets = [
|
|
||||||
this.i18n.transform('Connections can be slow or unreliable at times'),
|
|
||||||
this.i18n.transform(
|
|
||||||
'Public if you share the address publicly, otherwise private',
|
|
||||||
),
|
|
||||||
this.i18n.transform('Requires using a Tor-enabled device or browser'),
|
|
||||||
]
|
|
||||||
// Tor (SSL)
|
|
||||||
if (showSsl) {
|
|
||||||
bullets = [rootCaRequired, ...bullets]
|
|
||||||
// Tor (NON-SSL)
|
|
||||||
} else {
|
|
||||||
bullets.unshift(
|
|
||||||
this.i18n.transform(
|
|
||||||
'Ideal for anonymous, censorship-resistant hosting and remote access',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// ** Not Tor **
|
|
||||||
} else {
|
|
||||||
const port = info.hostname.sslPort || info.hostname.port
|
const port = info.hostname.sslPort || info.hostname.port
|
||||||
gatewayName = info.gateway.name
|
gatewayName = info.gateway.name
|
||||||
|
|
||||||
@@ -479,7 +441,6 @@ export class InterfaceService {
|
|||||||
|
|
||||||
export type MappedServiceInterface = T.ServiceInterface & {
|
export type MappedServiceInterface = T.ServiceInterface & {
|
||||||
gateways: InterfaceGateway[]
|
gateways: InterfaceGateway[]
|
||||||
torDomains: string[]
|
|
||||||
publicDomains: PublicDomain[]
|
publicDomains: PublicDomain[]
|
||||||
privateDomains: string[]
|
privateDomains: string[]
|
||||||
addresses: {
|
addresses: {
|
||||||
|
|||||||
@@ -1,193 +0,0 @@
|
|||||||
import {
|
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
inject,
|
|
||||||
input,
|
|
||||||
} from '@angular/core'
|
|
||||||
import {
|
|
||||||
DialogService,
|
|
||||||
DocsLinkDirective,
|
|
||||||
ErrorService,
|
|
||||||
i18nPipe,
|
|
||||||
LoadingService,
|
|
||||||
} from '@start9labs/shared'
|
|
||||||
import { ISB, utils } from '@start9labs/start-sdk'
|
|
||||||
import { TuiButton, TuiTitle } from '@taiga-ui/core'
|
|
||||||
import { TuiSkeleton } from '@taiga-ui/kit'
|
|
||||||
import { TuiCell } from '@taiga-ui/layout'
|
|
||||||
import { filter } from 'rxjs'
|
|
||||||
import {
|
|
||||||
FormComponent,
|
|
||||||
FormContext,
|
|
||||||
} from 'src/app/routes/portal/components/form.component'
|
|
||||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
|
||||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
|
||||||
|
|
||||||
import { InterfaceComponent } from './interface.component'
|
|
||||||
|
|
||||||
type OnionForm = {
|
|
||||||
key: string
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'section[torDomains]',
|
|
||||||
template: `
|
|
||||||
<header>
|
|
||||||
Tor Domains
|
|
||||||
<a
|
|
||||||
tuiIconButton
|
|
||||||
docsLink
|
|
||||||
path="/user-manual/connecting-remotely/tor.html"
|
|
||||||
appearance="icon"
|
|
||||||
iconStart="@tui.external-link"
|
|
||||||
>
|
|
||||||
{{ 'Documentation' | i18n }}
|
|
||||||
</a>
|
|
||||||
<button
|
|
||||||
tuiButton
|
|
||||||
iconStart="@tui.plus"
|
|
||||||
[style.margin-inline-start]="'auto'"
|
|
||||||
(click)="add()"
|
|
||||||
[disabled]="!torDomains()"
|
|
||||||
>
|
|
||||||
{{ 'Add' | i18n }}
|
|
||||||
</button>
|
|
||||||
</header>
|
|
||||||
@for (domain of torDomains(); track domain) {
|
|
||||||
<div tuiCell="s">
|
|
||||||
<span tuiTitle>{{ domain }}</span>
|
|
||||||
<button
|
|
||||||
tuiIconButton
|
|
||||||
iconStart="@tui.trash"
|
|
||||||
appearance="action-destructive"
|
|
||||||
(click)="remove(domain)"
|
|
||||||
>
|
|
||||||
{{ 'Delete' | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
} @empty {
|
|
||||||
@if (torDomains()) {
|
|
||||||
<app-placeholder icon="@tui.target">
|
|
||||||
{{ 'No Tor domains' | i18n }}
|
|
||||||
</app-placeholder>
|
|
||||||
} @else {
|
|
||||||
@for (_ of [0, 1]; track $index) {
|
|
||||||
<label tuiCell="s">
|
|
||||||
<span tuiTitle [tuiSkeleton]="true">{{ 'Loading' | i18n }}</span>
|
|
||||||
</label>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
styles: `
|
|
||||||
:host {
|
|
||||||
grid-column: span 6;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
host: { class: 'g-card' },
|
|
||||||
imports: [
|
|
||||||
TuiCell,
|
|
||||||
TuiTitle,
|
|
||||||
TuiButton,
|
|
||||||
PlaceholderComponent,
|
|
||||||
i18nPipe,
|
|
||||||
DocsLinkDirective,
|
|
||||||
TuiSkeleton,
|
|
||||||
],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class InterfaceTorDomainsComponent {
|
|
||||||
private readonly dialog = inject(DialogService)
|
|
||||||
private readonly formDialog = inject(FormDialogService)
|
|
||||||
private readonly loader = inject(LoadingService)
|
|
||||||
private readonly errorService = inject(ErrorService)
|
|
||||||
private readonly api = inject(ApiService)
|
|
||||||
private readonly interface = inject(InterfaceComponent)
|
|
||||||
private readonly i18n = inject(i18nPipe)
|
|
||||||
|
|
||||||
readonly torDomains = input.required<readonly string[] | undefined>()
|
|
||||||
|
|
||||||
async remove(onion: string) {
|
|
||||||
this.dialog
|
|
||||||
.openConfirm({ label: 'Are you sure?', size: 's' })
|
|
||||||
.pipe(filter(Boolean))
|
|
||||||
.subscribe(async () => {
|
|
||||||
const loader = this.loader.open('Removing').subscribe()
|
|
||||||
const params = { onion }
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (this.interface.packageId()) {
|
|
||||||
await this.api.pkgRemoveOnion({
|
|
||||||
...params,
|
|
||||||
package: this.interface.packageId(),
|
|
||||||
host: this.interface.value()?.addressInfo.hostId || '',
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await this.api.serverRemoveOnion(params)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errorService.handleError(e)
|
|
||||||
return false
|
|
||||||
} finally {
|
|
||||||
loader.unsubscribe()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async add() {
|
|
||||||
this.formDialog.open<FormContext<OnionForm>>(FormComponent, {
|
|
||||||
label: 'New Tor domain',
|
|
||||||
data: {
|
|
||||||
spec: await configBuilderToSpec(
|
|
||||||
ISB.InputSpec.of({
|
|
||||||
key: ISB.Value.text({
|
|
||||||
name: this.i18n.transform('Private Key (optional)')!,
|
|
||||||
description: this.i18n.transform(
|
|
||||||
'Optionally provide a base64-encoded ed25519 private key for generating the Tor V3 (.onion) domain. If not provided, a random key will be generated.',
|
|
||||||
),
|
|
||||||
required: false,
|
|
||||||
default: null,
|
|
||||||
patterns: [utils.Patterns.base64],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: this.i18n.transform('Save')!,
|
|
||||||
handler: async value => this.save(value.key),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private async save(key?: string): Promise<boolean> {
|
|
||||||
const loader = this.loader.open('Saving').subscribe()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const onion = key
|
|
||||||
? await this.api.addTorKey({ key })
|
|
||||||
: await this.api.generateTorKey({})
|
|
||||||
|
|
||||||
if (this.interface.packageId()) {
|
|
||||||
await this.api.pkgAddOnion({
|
|
||||||
onion,
|
|
||||||
package: this.interface.packageId(),
|
|
||||||
host: this.interface.value()?.addressInfo.hostId || '',
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await this.api.serverAddOnion({ onion })
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errorService.handleError(e)
|
|
||||||
return false
|
|
||||||
} finally {
|
|
||||||
loader.unsubscribe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,10 +13,6 @@ export const ROUTES: Routes = [
|
|||||||
path: 'os',
|
path: 'os',
|
||||||
loadComponent: () => import('./routes/os.component'),
|
loadComponent: () => import('./routes/os.component'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'tor',
|
|
||||||
loadComponent: () => import('./routes/tor.component'),
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export default ROUTES
|
export default ROUTES
|
||||||
|
|||||||
@@ -79,12 +79,6 @@ export default class SystemLogsComponent {
|
|||||||
subtitle: 'Raw, unfiltered operating system logs',
|
subtitle: 'Raw, unfiltered operating system logs',
|
||||||
icon: '@tui.square-dashed-bottom-code',
|
icon: '@tui.square-dashed-bottom-code',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
link: 'tor',
|
|
||||||
title: 'Tor Logs',
|
|
||||||
subtitle: 'Diagnostics for the Tor daemon on this server',
|
|
||||||
icon: '@tui.target',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
link: 'kernel',
|
link: 'kernel',
|
||||||
title: 'Kernel Logs',
|
title: 'Kernel Logs',
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
|
||||||
import { i18nPipe } from '@start9labs/shared'
|
|
||||||
import { LogsComponent } from 'src/app/routes/portal/components/logs/logs.component'
|
|
||||||
import { LogsHeaderComponent } from 'src/app/routes/portal/routes/logs/components/header.component'
|
|
||||||
import { RR } from 'src/app/services/api/api.types'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
template: `
|
|
||||||
<logs-header [title]="'Tor Logs' | i18n">
|
|
||||||
{{ 'Diagnostics for the Tor daemon on this server' | i18n }}
|
|
||||||
</logs-header>
|
|
||||||
<logs context="tor" [followLogs]="follow" [fetchLogs]="fetch" />
|
|
||||||
`,
|
|
||||||
styles: `
|
|
||||||
:host {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
imports: [LogsComponent, LogsHeaderComponent, i18nPipe],
|
|
||||||
host: { class: 'g-page' },
|
|
||||||
})
|
|
||||||
export default class SystemTorComponent {
|
|
||||||
private readonly api = inject(ApiService)
|
|
||||||
|
|
||||||
protected readonly follow = (params: RR.FollowServerLogsReq) =>
|
|
||||||
this.api.followTorLogs(params)
|
|
||||||
|
|
||||||
protected readonly fetch = (params: RR.GetServerLogsReq) =>
|
|
||||||
this.api.getTorLogs(params)
|
|
||||||
}
|
|
||||||
@@ -139,7 +139,6 @@ export default class ServiceInterfaceRoute {
|
|||||||
: !binding?.net.privateDisabled.includes(g.id)) ?? false,
|
: !binding?.net.privateDisabled.includes(g.id)) ?? false,
|
||||||
...g,
|
...g,
|
||||||
})) || [],
|
})) || [],
|
||||||
torDomains: host.onions,
|
|
||||||
publicDomains: getPublicDomains(host.publicDomains, gateways),
|
publicDomains: getPublicDomains(host.publicDomains, gateways),
|
||||||
privateDomains: host.privateDomains,
|
privateDomains: host.privateDomains,
|
||||||
addSsl: !!binding?.options.addSsl,
|
addSsl: !!binding?.options.addSsl,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
TuiFiles,
|
TuiFiles,
|
||||||
tuiInputFilesOptionsProvider,
|
tuiInputFilesOptionsProvider,
|
||||||
} from '@taiga-ui/kit'
|
} from '@taiga-ui/kit'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
|
||||||
import { TitleDirective } from 'src/app/services/title.service'
|
import { TitleDirective } from 'src/app/services/title.service'
|
||||||
|
|
||||||
import { SideloadPackageComponent } from './package.component'
|
import { SideloadPackageComponent } from './package.component'
|
||||||
@@ -55,11 +54,6 @@ import { MarketplacePkgSideload, validateS9pk } from './sideload.utils'
|
|||||||
<div>
|
<div>
|
||||||
<tui-avatar appearance="secondary" src="@tui.upload" />
|
<tui-avatar appearance="secondary" src="@tui.upload" />
|
||||||
<p>{{ 'Upload .s9pk package file' | i18n }}</p>
|
<p>{{ 'Upload .s9pk package file' | i18n }}</p>
|
||||||
@if (isTor) {
|
|
||||||
<p class="g-warning">
|
|
||||||
{{ 'Warning: package upload will be slow over Tor.' | i18n }}
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
<button tuiButton>{{ 'Select' | i18n }}</button>
|
<button tuiButton>{{ 'Select' | i18n }}</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -92,8 +86,6 @@ import { MarketplacePkgSideload, validateS9pk } from './sideload.utils'
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export default class SideloadComponent {
|
export default class SideloadComponent {
|
||||||
readonly isTor = inject(ConfigService).accessType === 'tor'
|
|
||||||
|
|
||||||
file: File | null = null
|
file: File | null = null
|
||||||
readonly package = signal<MarketplacePkgSideload | null>(null)
|
readonly package = signal<MarketplacePkgSideload | null>(null)
|
||||||
readonly error = signal<i18nKey | null>(null)
|
readonly error = signal<i18nKey | null>(null)
|
||||||
|
|||||||
@@ -47,13 +47,11 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
|||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { filter } from 'rxjs'
|
import { filter } from 'rxjs'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
|
||||||
import { OSService } from 'src/app/services/os.service'
|
import { OSService } from 'src/app/services/os.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { TitleDirective } from 'src/app/services/title.service'
|
import { TitleDirective } from 'src/app/services/title.service'
|
||||||
import { SnekDirective } from './snek.directive'
|
import { SnekDirective } from './snek.directive'
|
||||||
import { UPDATE } from './update.component'
|
import { UPDATE } from './update.component'
|
||||||
import { SystemWipeComponent } from './wipe.component'
|
|
||||||
import { KeyboardSelectComponent } from './keyboard-select.component'
|
import { KeyboardSelectComponent } from './keyboard-select.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -66,7 +64,7 @@ import { KeyboardSelectComponent } from './keyboard-select.component'
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
@if (server(); as server) {
|
@if (server(); as server) {
|
||||||
<div tuiCell tuiAppearance="outline-grayscale">
|
<div tuiCell tuiAppearance="outline-grayscale">
|
||||||
<tui-icon icon="@tui.zap" />
|
<tui-icon icon="@tui.zap" (click)="count = count + 1" />
|
||||||
<span tuiTitle>
|
<span tuiTitle>
|
||||||
<strong>{{ 'Software Update' | i18n }}</strong>
|
<strong>{{ 'Software Update' | i18n }}</strong>
|
||||||
<span tuiSubtitle [style.flex-wrap]="'wrap'">
|
<span tuiSubtitle [style.flex-wrap]="'wrap'">
|
||||||
@@ -178,18 +176,6 @@ import { KeyboardSelectComponent } from './keyboard-select.component'
|
|||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div tuiCell tuiAppearance="outline-grayscale">
|
|
||||||
<tui-icon icon="@tui.rotate-cw" (click)="count = count + 1" />
|
|
||||||
<span tuiTitle>
|
|
||||||
<strong>{{ 'Restart Tor' | i18n }}</strong>
|
|
||||||
<span tuiSubtitle>
|
|
||||||
{{ 'Restart the Tor daemon on your server' | i18n }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<button tuiButton appearance="glass" (click)="onTorRestart()">
|
|
||||||
{{ 'Restart' | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
@if (count > 4) {
|
@if (count > 4) {
|
||||||
<div tuiCell tuiAppearance="outline-grayscale" tuiAnimated>
|
<div tuiCell tuiAppearance="outline-grayscale" tuiAnimated>
|
||||||
<tui-icon icon="@tui.briefcase-medical" />
|
<tui-icon icon="@tui.briefcase-medical" />
|
||||||
@@ -283,12 +269,10 @@ export default class SystemGeneralComponent {
|
|||||||
private readonly errorService = inject(ErrorService)
|
private readonly errorService = inject(ErrorService)
|
||||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||||
private readonly api = inject(ApiService)
|
private readonly api = inject(ApiService)
|
||||||
private readonly isTor = inject(ConfigService).accessType === 'tor'
|
|
||||||
private readonly dialog = inject(DialogService)
|
private readonly dialog = inject(DialogService)
|
||||||
private readonly i18n = inject(i18nPipe)
|
private readonly i18n = inject(i18nPipe)
|
||||||
private readonly injector = inject(INJECTOR)
|
private readonly injector = inject(INJECTOR)
|
||||||
|
|
||||||
wipe = false
|
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
readonly server = toSignal(this.patch.watch$('serverInfo'))
|
readonly server = toSignal(this.patch.watch$('serverInfo'))
|
||||||
@@ -392,24 +376,6 @@ export default class SystemGeneralComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onTorRestart() {
|
|
||||||
this.wipe = false
|
|
||||||
this.dialog
|
|
||||||
.openConfirm({
|
|
||||||
label: this.isTor ? 'Warning' : 'Confirm',
|
|
||||||
data: {
|
|
||||||
content: new PolymorpheusComponent(
|
|
||||||
SystemWipeComponent,
|
|
||||||
this.injector,
|
|
||||||
),
|
|
||||||
yes: 'Restart',
|
|
||||||
no: 'Cancel',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.pipe(filter(Boolean))
|
|
||||||
.subscribe(() => this.resetTor(this.wipe))
|
|
||||||
}
|
|
||||||
|
|
||||||
async onRepair() {
|
async onRepair() {
|
||||||
this.dialog
|
this.dialog
|
||||||
.openConfirm({
|
.openConfirm({
|
||||||
@@ -532,19 +498,6 @@ export default class SystemGeneralComponent {
|
|||||||
.subscribe(() => this.restart())
|
.subscribe(() => this.restart())
|
||||||
}
|
}
|
||||||
|
|
||||||
private async resetTor(wipeState: boolean) {
|
|
||||||
const loader = this.loader.open().subscribe()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.api.resetTor({ wipeState, reason: 'User triggered' })
|
|
||||||
this.dialog.openAlert('Tor restart in progress').subscribe()
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errorService.handleError(e)
|
|
||||||
} finally {
|
|
||||||
loader.unsubscribe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private update() {
|
private update() {
|
||||||
this.dialogs
|
this.dialogs
|
||||||
.open(UPDATE, {
|
.open(UPDATE, {
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
|
||||||
import { FormsModule } from '@angular/forms'
|
|
||||||
import { TuiLabel } from '@taiga-ui/core'
|
|
||||||
import { TuiCheckbox } from '@taiga-ui/kit'
|
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
|
||||||
import SystemGeneralComponent from './general.component'
|
|
||||||
import { i18nPipe } from '@start9labs/shared'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
template: `
|
|
||||||
@if (isTor) {
|
|
||||||
<p>
|
|
||||||
{{
|
|
||||||
'You are currently connected over Tor. If you restart the Tor daemon, you will lose connectivity until it comes back online.'
|
|
||||||
| i18n
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
<p>
|
|
||||||
{{
|
|
||||||
'Optionally wipe state to forcibly acquire new guard nodes. It is recommended to try without wiping state first.'
|
|
||||||
| i18n
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
<label tuiLabel>
|
|
||||||
<input type="checkbox" tuiCheckbox [(ngModel)]="component.wipe" />
|
|
||||||
{{ 'Wipe state' | i18n }}
|
|
||||||
</label>
|
|
||||||
`,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
imports: [TuiLabel, FormsModule, TuiCheckbox, i18nPipe],
|
|
||||||
})
|
|
||||||
export class SystemWipeComponent {
|
|
||||||
readonly isTor = inject(ConfigService).accessType === 'tor'
|
|
||||||
readonly component = inject(SystemGeneralComponent)
|
|
||||||
}
|
|
||||||
@@ -100,7 +100,6 @@ export default class StartOsUiComponent {
|
|||||||
: !binding?.net.privateDisabled.includes(g.id)) ?? false,
|
: !binding?.net.privateDisabled.includes(g.id)) ?? false,
|
||||||
...g,
|
...g,
|
||||||
})),
|
})),
|
||||||
torDomains: network.host.onions,
|
|
||||||
publicDomains: getPublicDomains(network.host.publicDomains, gateways),
|
publicDomains: getPublicDomains(network.host.publicDomains, gateways),
|
||||||
privateDomains: network.host.privateDomains,
|
privateDomains: network.host.privateDomains,
|
||||||
addSsl: true,
|
addSsl: true,
|
||||||
|
|||||||
@@ -2138,7 +2138,6 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
publicDomains: {},
|
publicDomains: {},
|
||||||
privateDomains: [],
|
privateDomains: [],
|
||||||
onions: [],
|
|
||||||
hostnameInfo: {
|
hostnameInfo: {
|
||||||
80: [
|
80: [
|
||||||
{
|
{
|
||||||
@@ -2209,14 +2208,6 @@ export namespace Mock {
|
|||||||
sslPort: 1234,
|
sslPort: 1234,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
kind: 'onion',
|
|
||||||
hostname: {
|
|
||||||
value: 'bitcoin-p2p.onion',
|
|
||||||
port: 80,
|
|
||||||
sslPort: 443,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -2239,7 +2230,6 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
publicDomains: {},
|
publicDomains: {},
|
||||||
privateDomains: [],
|
privateDomains: [],
|
||||||
onions: [],
|
|
||||||
hostnameInfo: {
|
hostnameInfo: {
|
||||||
8332: [],
|
8332: [],
|
||||||
},
|
},
|
||||||
@@ -2263,7 +2253,6 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
publicDomains: {},
|
publicDomains: {},
|
||||||
privateDomains: [],
|
privateDomains: [],
|
||||||
onions: [],
|
|
||||||
hostnameInfo: {
|
hostnameInfo: {
|
||||||
8333: [],
|
8333: [],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -79,14 +79,14 @@ export namespace RR {
|
|||||||
uptime: number // seconds
|
uptime: number // seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetServerLogsReq = FetchLogsReq // server.logs & server.kernel-logs & net.tor.logs
|
export type GetServerLogsReq = FetchLogsReq // server.logs & server.kernel-logs
|
||||||
export type GetServerLogsRes = FetchLogsRes
|
export type GetServerLogsRes = FetchLogsRes
|
||||||
|
|
||||||
export type FollowServerLogsReq = {
|
export type FollowServerLogsReq = {
|
||||||
limit?: number // (optional) default is 50. Ignored if cursor provided
|
limit?: number // (optional) default is 50. Ignored if cursor provided
|
||||||
boot?: number | string | null // (optional) number is offset (0: current, -1 prev, +1 first), string is a specific boot id, null is all. Default is undefined
|
boot?: number | string | null // (optional) number is offset (0: current, -1 prev, +1 first), string is a specific boot id, null is all. Default is undefined
|
||||||
cursor?: string // the last known log. Websocket will return all logs since this log
|
cursor?: string // the last known log. Websocket will return all logs since this log
|
||||||
} // server.logs.follow & server.kernel-logs.follow & net.tor.follow-logs
|
} // server.logs.follow & server.kernel-logs.follow
|
||||||
export type FollowServerLogsRes = {
|
export type FollowServerLogsRes = {
|
||||||
startCursor: string
|
startCursor: string
|
||||||
guid: string
|
guid: string
|
||||||
@@ -120,12 +120,6 @@ export namespace RR {
|
|||||||
} // net.dns.query
|
} // net.dns.query
|
||||||
export type QueryDnsRes = string | null
|
export type QueryDnsRes = string | null
|
||||||
|
|
||||||
export type ResetTorReq = {
|
|
||||||
wipeState: boolean
|
|
||||||
reason: string
|
|
||||||
} // net.tor.reset
|
|
||||||
export type ResetTorRes = null
|
|
||||||
|
|
||||||
export type SetKeyboardReq = FullKeyboard // server.set-keyboard
|
export type SetKeyboardReq = FullKeyboard // server.set-keyboard
|
||||||
export type SetKeyboardRes = null
|
export type SetKeyboardRes = null
|
||||||
|
|
||||||
@@ -287,13 +281,6 @@ export namespace RR {
|
|||||||
}
|
}
|
||||||
export type RemoveAcmeRes = null
|
export type RemoveAcmeRes = null
|
||||||
|
|
||||||
export type AddTorKeyReq = {
|
|
||||||
// net.tor.key.add
|
|
||||||
key: string
|
|
||||||
}
|
|
||||||
export type GenerateTorKeyReq = {} // net.tor.key.generate
|
|
||||||
export type AddTorKeyRes = string // onion address *with* .onion suffix
|
|
||||||
|
|
||||||
export type ServerBindingToggleGatewayReq = {
|
export type ServerBindingToggleGatewayReq = {
|
||||||
// server.host.binding.set-gateway-enabled
|
// server.host.binding.set-gateway-enabled
|
||||||
gateway: T.GatewayId
|
gateway: T.GatewayId
|
||||||
@@ -302,15 +289,6 @@ export namespace RR {
|
|||||||
}
|
}
|
||||||
export type ServerBindingToggleGatewayRes = null
|
export type ServerBindingToggleGatewayRes = null
|
||||||
|
|
||||||
export type ServerAddOnionReq = {
|
|
||||||
// server.host.address.onion.add
|
|
||||||
onion: string // address *with* .onion suffix
|
|
||||||
}
|
|
||||||
export type AddOnionRes = null
|
|
||||||
|
|
||||||
export type ServerRemoveOnionReq = ServerAddOnionReq // server.host.address.onion.remove
|
|
||||||
export type RemoveOnionRes = null
|
|
||||||
|
|
||||||
export type OsUiAddPublicDomainReq = {
|
export type OsUiAddPublicDomainReq = {
|
||||||
// server.host.address.domain.public.add
|
// server.host.address.domain.public.add
|
||||||
fqdn: string // FQDN
|
fqdn: string // FQDN
|
||||||
@@ -348,13 +326,6 @@ export namespace RR {
|
|||||||
}
|
}
|
||||||
export type PkgBindingToggleGatewayRes = null
|
export type PkgBindingToggleGatewayRes = null
|
||||||
|
|
||||||
export type PkgAddOnionReq = ServerAddOnionReq & {
|
|
||||||
// package.host.address.onion.add
|
|
||||||
package: T.PackageId // string
|
|
||||||
host: T.HostId // string
|
|
||||||
}
|
|
||||||
export type PkgRemoveOnionReq = PkgAddOnionReq // package.host.address.onion.remove
|
|
||||||
|
|
||||||
export type PkgAddPublicDomainReq = OsUiAddPublicDomainReq & {
|
export type PkgAddPublicDomainReq = OsUiAddPublicDomainReq & {
|
||||||
// package.host.address.domain.public.add
|
// package.host.address.domain.public.add
|
||||||
package: T.PackageId // string
|
package: T.PackageId // string
|
||||||
|
|||||||
@@ -81,8 +81,6 @@ export abstract class ApiService {
|
|||||||
params: RR.GetServerLogsReq,
|
params: RR.GetServerLogsReq,
|
||||||
): Promise<RR.GetServerLogsRes>
|
): Promise<RR.GetServerLogsRes>
|
||||||
|
|
||||||
abstract getTorLogs(params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes>
|
|
||||||
|
|
||||||
abstract getKernelLogs(
|
abstract getKernelLogs(
|
||||||
params: RR.GetServerLogsReq,
|
params: RR.GetServerLogsReq,
|
||||||
): Promise<RR.GetServerLogsRes>
|
): Promise<RR.GetServerLogsRes>
|
||||||
@@ -91,10 +89,6 @@ export abstract class ApiService {
|
|||||||
params: RR.FollowServerLogsReq,
|
params: RR.FollowServerLogsReq,
|
||||||
): Promise<RR.FollowServerLogsRes>
|
): Promise<RR.FollowServerLogsRes>
|
||||||
|
|
||||||
abstract followTorLogs(
|
|
||||||
params: RR.FollowServerLogsReq,
|
|
||||||
): Promise<RR.FollowServerLogsRes>
|
|
||||||
|
|
||||||
abstract followKernelLogs(
|
abstract followKernelLogs(
|
||||||
params: RR.FollowServerLogsReq,
|
params: RR.FollowServerLogsReq,
|
||||||
): Promise<RR.FollowServerLogsRes>
|
): Promise<RR.FollowServerLogsRes>
|
||||||
@@ -125,8 +119,6 @@ export abstract class ApiService {
|
|||||||
|
|
||||||
abstract queryDns(params: RR.QueryDnsReq): Promise<RR.QueryDnsRes>
|
abstract queryDns(params: RR.QueryDnsReq): Promise<RR.QueryDnsRes>
|
||||||
|
|
||||||
abstract resetTor(params: RR.ResetTorReq): Promise<RR.ResetTorRes>
|
|
||||||
|
|
||||||
// smtp
|
// smtp
|
||||||
|
|
||||||
abstract setSmtp(params: RR.SetSMTPReq): Promise<RR.SetSMTPRes>
|
abstract setSmtp(params: RR.SetSMTPReq): Promise<RR.SetSMTPRes>
|
||||||
@@ -344,22 +336,10 @@ export abstract class ApiService {
|
|||||||
|
|
||||||
abstract removeAcme(params: RR.RemoveAcmeReq): Promise<RR.RemoveAcmeRes>
|
abstract removeAcme(params: RR.RemoveAcmeReq): Promise<RR.RemoveAcmeRes>
|
||||||
|
|
||||||
abstract addTorKey(params: RR.AddTorKeyReq): Promise<RR.AddTorKeyRes>
|
|
||||||
|
|
||||||
abstract generateTorKey(
|
|
||||||
params: RR.GenerateTorKeyReq,
|
|
||||||
): Promise<RR.AddTorKeyRes>
|
|
||||||
|
|
||||||
abstract serverBindingToggleGateway(
|
abstract serverBindingToggleGateway(
|
||||||
params: RR.ServerBindingToggleGatewayReq,
|
params: RR.ServerBindingToggleGatewayReq,
|
||||||
): Promise<RR.ServerBindingToggleGatewayRes>
|
): Promise<RR.ServerBindingToggleGatewayRes>
|
||||||
|
|
||||||
abstract serverAddOnion(params: RR.ServerAddOnionReq): Promise<RR.AddOnionRes>
|
|
||||||
|
|
||||||
abstract serverRemoveOnion(
|
|
||||||
params: RR.ServerRemoveOnionReq,
|
|
||||||
): Promise<RR.RemoveOnionRes>
|
|
||||||
|
|
||||||
abstract osUiAddPublicDomain(
|
abstract osUiAddPublicDomain(
|
||||||
params: RR.OsUiAddPublicDomainReq,
|
params: RR.OsUiAddPublicDomainReq,
|
||||||
): Promise<RR.OsUiAddPublicDomainRes>
|
): Promise<RR.OsUiAddPublicDomainRes>
|
||||||
@@ -380,12 +360,6 @@ export abstract class ApiService {
|
|||||||
params: RR.PkgBindingToggleGatewayReq,
|
params: RR.PkgBindingToggleGatewayReq,
|
||||||
): Promise<RR.PkgBindingToggleGatewayRes>
|
): Promise<RR.PkgBindingToggleGatewayRes>
|
||||||
|
|
||||||
abstract pkgAddOnion(params: RR.PkgAddOnionReq): Promise<RR.AddOnionRes>
|
|
||||||
|
|
||||||
abstract pkgRemoveOnion(
|
|
||||||
params: RR.PkgRemoveOnionReq,
|
|
||||||
): Promise<RR.RemoveOnionRes>
|
|
||||||
|
|
||||||
abstract pkgAddPublicDomain(
|
abstract pkgAddPublicDomain(
|
||||||
params: RR.PkgAddPublicDomainReq,
|
params: RR.PkgAddPublicDomainReq,
|
||||||
): Promise<RR.PkgAddPublicDomainRes>
|
): Promise<RR.PkgAddPublicDomainRes>
|
||||||
|
|||||||
@@ -195,10 +195,6 @@ export class LiveApiService extends ApiService {
|
|||||||
return this.rpcRequest({ method: 'server.logs', params })
|
return this.rpcRequest({ method: 'server.logs', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTorLogs(params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes> {
|
|
||||||
return this.rpcRequest({ method: 'net.tor.logs', params })
|
|
||||||
}
|
|
||||||
|
|
||||||
async getKernelLogs(
|
async getKernelLogs(
|
||||||
params: RR.GetServerLogsReq,
|
params: RR.GetServerLogsReq,
|
||||||
): Promise<RR.GetServerLogsRes> {
|
): Promise<RR.GetServerLogsRes> {
|
||||||
@@ -211,12 +207,6 @@ export class LiveApiService extends ApiService {
|
|||||||
return this.rpcRequest({ method: 'server.logs.follow', params })
|
return this.rpcRequest({ method: 'server.logs.follow', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
async followTorLogs(
|
|
||||||
params: RR.FollowServerLogsReq,
|
|
||||||
): Promise<RR.FollowServerLogsRes> {
|
|
||||||
return this.rpcRequest({ method: 'net.tor.logs.follow', params })
|
|
||||||
}
|
|
||||||
|
|
||||||
async followKernelLogs(
|
async followKernelLogs(
|
||||||
params: RR.FollowServerLogsReq,
|
params: RR.FollowServerLogsReq,
|
||||||
): Promise<RR.FollowServerLogsRes> {
|
): Promise<RR.FollowServerLogsRes> {
|
||||||
@@ -278,10 +268,6 @@ export class LiveApiService extends ApiService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetTor(params: RR.ResetTorReq): Promise<RR.ResetTorRes> {
|
|
||||||
return this.rpcRequest({ method: 'net.tor.reset', params })
|
|
||||||
}
|
|
||||||
|
|
||||||
// marketplace URLs
|
// marketplace URLs
|
||||||
|
|
||||||
async checkOSUpdate(
|
async checkOSUpdate(
|
||||||
@@ -621,20 +607,6 @@ export class LiveApiService extends ApiService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async addTorKey(params: RR.AddTorKeyReq): Promise<RR.AddTorKeyRes> {
|
|
||||||
return this.rpcRequest({
|
|
||||||
method: 'net.tor.key.add',
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async generateTorKey(params: RR.GenerateTorKeyReq): Promise<RR.AddTorKeyRes> {
|
|
||||||
return this.rpcRequest({
|
|
||||||
method: 'net.tor.key.generate',
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async serverBindingToggleGateway(
|
async serverBindingToggleGateway(
|
||||||
params: RR.ServerBindingToggleGatewayReq,
|
params: RR.ServerBindingToggleGatewayReq,
|
||||||
): Promise<RR.ServerBindingToggleGatewayRes> {
|
): Promise<RR.ServerBindingToggleGatewayRes> {
|
||||||
@@ -644,22 +616,6 @@ export class LiveApiService extends ApiService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async serverAddOnion(params: RR.ServerAddOnionReq): Promise<RR.AddOnionRes> {
|
|
||||||
return this.rpcRequest({
|
|
||||||
method: 'server.host.address.onion.add',
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async serverRemoveOnion(
|
|
||||||
params: RR.ServerRemoveOnionReq,
|
|
||||||
): Promise<RR.RemoveOnionRes> {
|
|
||||||
return this.rpcRequest({
|
|
||||||
method: 'server.host.address.onion.remove',
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async osUiAddPublicDomain(
|
async osUiAddPublicDomain(
|
||||||
params: RR.OsUiAddPublicDomainReq,
|
params: RR.OsUiAddPublicDomainReq,
|
||||||
): Promise<RR.OsUiAddPublicDomainRes> {
|
): Promise<RR.OsUiAddPublicDomainRes> {
|
||||||
@@ -705,22 +661,6 @@ export class LiveApiService extends ApiService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async pkgAddOnion(params: RR.PkgAddOnionReq): Promise<RR.AddOnionRes> {
|
|
||||||
return this.rpcRequest({
|
|
||||||
method: 'package.host.address.onion.add',
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async pkgRemoveOnion(
|
|
||||||
params: RR.PkgRemoveOnionReq,
|
|
||||||
): Promise<RR.RemoveOnionRes> {
|
|
||||||
return this.rpcRequest({
|
|
||||||
method: 'package.host.address.onion.remove',
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async pkgAddPublicDomain(
|
async pkgAddPublicDomain(
|
||||||
params: RR.PkgAddPublicDomainReq,
|
params: RR.PkgAddPublicDomainReq,
|
||||||
): Promise<RR.PkgAddPublicDomainRes> {
|
): Promise<RR.PkgAddPublicDomainRes> {
|
||||||
|
|||||||
@@ -281,17 +281,6 @@ export class MockApiService extends ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTorLogs(params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes> {
|
|
||||||
await pauseFor(2000)
|
|
||||||
const entries = this.randomLogs(params.limit)
|
|
||||||
|
|
||||||
return {
|
|
||||||
entries,
|
|
||||||
startCursor: 'start-cursor',
|
|
||||||
endCursor: 'end-cursor',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getKernelLogs(
|
async getKernelLogs(
|
||||||
params: RR.GetServerLogsReq,
|
params: RR.GetServerLogsReq,
|
||||||
): Promise<RR.GetServerLogsRes> {
|
): Promise<RR.GetServerLogsRes> {
|
||||||
@@ -315,16 +304,6 @@ export class MockApiService extends ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async followTorLogs(
|
|
||||||
params: RR.FollowServerLogsReq,
|
|
||||||
): Promise<RR.FollowServerLogsRes> {
|
|
||||||
await pauseFor(2000)
|
|
||||||
return {
|
|
||||||
startCursor: 'start-cursor',
|
|
||||||
guid: 'logs-guid',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async followKernelLogs(
|
async followKernelLogs(
|
||||||
params: RR.FollowServerLogsReq,
|
params: RR.FollowServerLogsReq,
|
||||||
): Promise<RR.FollowServerLogsRes> {
|
): Promise<RR.FollowServerLogsRes> {
|
||||||
@@ -504,11 +483,6 @@ export class MockApiService extends ApiService {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetTor(params: RR.ResetTorReq): Promise<RR.ResetTorRes> {
|
|
||||||
await pauseFor(2000)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// marketplace URLs
|
// marketplace URLs
|
||||||
|
|
||||||
async checkOSUpdate(
|
async checkOSUpdate(
|
||||||
@@ -1374,16 +1348,6 @@ export class MockApiService extends ApiService {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
async addTorKey(params: RR.AddTorKeyReq): Promise<RR.AddTorKeyRes> {
|
|
||||||
await pauseFor(2000)
|
|
||||||
return 'vanityabcdefghijklmnop.onion'
|
|
||||||
}
|
|
||||||
|
|
||||||
async generateTorKey(params: RR.GenerateTorKeyReq): Promise<RR.AddTorKeyRes> {
|
|
||||||
await pauseFor(2000)
|
|
||||||
return 'abcdefghijklmnopqrstuv.onion'
|
|
||||||
}
|
|
||||||
|
|
||||||
async serverBindingToggleGateway(
|
async serverBindingToggleGateway(
|
||||||
params: RR.ServerBindingToggleGatewayReq,
|
params: RR.ServerBindingToggleGatewayReq,
|
||||||
): Promise<RR.ServerBindingToggleGatewayRes> {
|
): Promise<RR.ServerBindingToggleGatewayRes> {
|
||||||
@@ -1401,53 +1365,6 @@ export class MockApiService extends ApiService {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
async serverAddOnion(params: RR.ServerAddOnionReq): Promise<RR.AddOnionRes> {
|
|
||||||
await pauseFor(2000)
|
|
||||||
|
|
||||||
const patch: Operation<any>[] = [
|
|
||||||
{
|
|
||||||
op: PatchOp.ADD,
|
|
||||||
path: `/serverInfo/host/onions/0`,
|
|
||||||
value: params.onion,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: PatchOp.ADD,
|
|
||||||
path: `/serverInfo/host/hostnameInfo/80/0`,
|
|
||||||
value: {
|
|
||||||
kind: 'onion',
|
|
||||||
hostname: {
|
|
||||||
port: 80,
|
|
||||||
sslPort: 443,
|
|
||||||
value: params.onion,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
this.mockRevision(patch)
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
async serverRemoveOnion(
|
|
||||||
params: RR.ServerRemoveOnionReq,
|
|
||||||
): Promise<RR.RemoveOnionRes> {
|
|
||||||
await pauseFor(2000)
|
|
||||||
|
|
||||||
const patch: RemoveOperation[] = [
|
|
||||||
{
|
|
||||||
op: PatchOp.REMOVE,
|
|
||||||
path: `/serverInfo/host/onions/0`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: PatchOp.REMOVE,
|
|
||||||
path: `/serverInfo/host/hostnameInfo/80/-1`,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
this.mockRevision(patch)
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
async osUiAddPublicDomain(
|
async osUiAddPublicDomain(
|
||||||
params: RR.OsUiAddPublicDomainReq,
|
params: RR.OsUiAddPublicDomainReq,
|
||||||
): Promise<RR.OsUiAddPublicDomainRes> {
|
): Promise<RR.OsUiAddPublicDomainRes> {
|
||||||
@@ -1574,53 +1491,6 @@ export class MockApiService extends ApiService {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
async pkgAddOnion(params: RR.PkgAddOnionReq): Promise<RR.AddOnionRes> {
|
|
||||||
await pauseFor(2000)
|
|
||||||
|
|
||||||
const patch: Operation<any>[] = [
|
|
||||||
{
|
|
||||||
op: PatchOp.ADD,
|
|
||||||
path: `/packageData/${params.package}/hosts/${params.host}/onions/0`,
|
|
||||||
value: params.onion,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: PatchOp.ADD,
|
|
||||||
path: `/packageData/${params.package}/hosts/${params.host}/hostnameInfo/80/0`,
|
|
||||||
value: {
|
|
||||||
kind: 'onion',
|
|
||||||
hostname: {
|
|
||||||
port: 80,
|
|
||||||
sslPort: 443,
|
|
||||||
value: params.onion,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
this.mockRevision(patch)
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
async pkgRemoveOnion(
|
|
||||||
params: RR.PkgRemoveOnionReq,
|
|
||||||
): Promise<RR.RemoveOnionRes> {
|
|
||||||
await pauseFor(2000)
|
|
||||||
|
|
||||||
const patch: RemoveOperation[] = [
|
|
||||||
{
|
|
||||||
op: PatchOp.REMOVE,
|
|
||||||
path: `/packageData/${params.package}/hosts/${params.host}/onions/0`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: PatchOp.REMOVE,
|
|
||||||
path: `/packageData/${params.package}/hosts/${params.host}/hostnameInfo/80/0`,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
this.mockRevision(patch)
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
async pkgAddPublicDomain(
|
async pkgAddPublicDomain(
|
||||||
params: RR.PkgAddPublicDomainReq,
|
params: RR.PkgAddPublicDomainReq,
|
||||||
): Promise<RR.PkgAddPublicDomainRes> {
|
): Promise<RR.PkgAddPublicDomainRes> {
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
publicDomains: {},
|
publicDomains: {},
|
||||||
privateDomains: [],
|
privateDomains: [],
|
||||||
onions: ['myveryownspecialtoraddress'],
|
|
||||||
hostnameInfo: {
|
hostnameInfo: {
|
||||||
80: [
|
80: [
|
||||||
{
|
{
|
||||||
@@ -125,14 +124,6 @@ export const mockPatchData: DataModel = {
|
|||||||
sslPort: 443,
|
sslPort: 443,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
kind: 'onion',
|
|
||||||
hostname: {
|
|
||||||
value: 'myveryownspecialtoraddress.onion',
|
|
||||||
port: 80,
|
|
||||||
sslPort: 443,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -524,7 +515,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
publicDomains: {},
|
publicDomains: {},
|
||||||
privateDomains: [],
|
privateDomains: [],
|
||||||
onions: [],
|
|
||||||
hostnameInfo: {
|
hostnameInfo: {
|
||||||
80: [
|
80: [
|
||||||
{
|
{
|
||||||
@@ -595,14 +585,6 @@ export const mockPatchData: DataModel = {
|
|||||||
sslPort: 1234,
|
sslPort: 1234,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
kind: 'onion',
|
|
||||||
hostname: {
|
|
||||||
value: 'bitcoin-p2p.onion',
|
|
||||||
port: 80,
|
|
||||||
sslPort: 443,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -625,7 +607,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
publicDomains: {},
|
publicDomains: {},
|
||||||
privateDomains: [],
|
privateDomains: [],
|
||||||
onions: [],
|
|
||||||
hostnameInfo: {
|
hostnameInfo: {
|
||||||
8332: [],
|
8332: [],
|
||||||
},
|
},
|
||||||
@@ -649,7 +630,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
publicDomains: {},
|
publicDomains: {},
|
||||||
privateDomains: [],
|
privateDomains: [],
|
||||||
onions: [],
|
|
||||||
hostnameInfo: {
|
hostnameInfo: {
|
||||||
8333: [],
|
8333: [],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export class ConfigService {
|
|||||||
private getAccessType = utils.once(() => {
|
private getAccessType = utils.once(() => {
|
||||||
if (useMocks) return mocks.maskAs
|
if (useMocks) return mocks.maskAs
|
||||||
if (this.hostname === 'localhost') return 'localhost'
|
if (this.hostname === 'localhost') return 'localhost'
|
||||||
if (this.hostname.endsWith('.onion')) return 'tor'
|
|
||||||
if (this.hostname.endsWith('.local')) return 'mdns'
|
if (this.hostname.endsWith('.local')) return 'mdns'
|
||||||
let ip = null
|
let ip = null
|
||||||
try {
|
try {
|
||||||
@@ -51,7 +50,7 @@ export class ConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isLanHttp(): boolean {
|
isLanHttp(): boolean {
|
||||||
return !this.isHttps() && !['localhost', 'tor'].includes(this.accessType)
|
return !this.isHttps() && this.accessType !== 'localhost'
|
||||||
}
|
}
|
||||||
|
|
||||||
isHttps(): boolean {
|
isHttps(): boolean {
|
||||||
|
|||||||
Reference in New Issue
Block a user