mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
Merge branch 'feat/preferred-port-design' into sdk-comments
This commit is contained in:
@@ -1008,6 +1008,13 @@ hostname.invalid-character:
|
|||||||
fr_FR: "Caractère invalide dans le nom d'hôte : %{char}"
|
fr_FR: "Caractère invalide dans le nom d'hôte : %{char}"
|
||||||
pl_PL: "Nieprawidłowy znak w nazwie hosta: %{char}"
|
pl_PL: "Nieprawidłowy znak w nazwie hosta: %{char}"
|
||||||
|
|
||||||
|
hostname.must-provide-name-or-hostname:
|
||||||
|
en_US: "Must provide at least one of: name, hostname"
|
||||||
|
de_DE: "Es muss mindestens eines angegeben werden: name, hostname"
|
||||||
|
es_ES: "Se debe proporcionar al menos uno de: name, hostname"
|
||||||
|
fr_FR: "Vous devez fournir au moins l'un des éléments suivants : name, hostname"
|
||||||
|
pl_PL: "Należy podać co najmniej jedno z: name, hostname"
|
||||||
|
|
||||||
# init.rs
|
# init.rs
|
||||||
init.running-preinit:
|
init.running-preinit:
|
||||||
en_US: "Running preinit.sh"
|
en_US: "Running preinit.sh"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use openssl::pkey::{PKey, Private};
|
|||||||
use openssl::x509::X509;
|
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::{ServerHostnameInfo, generate_hostname, generate_id};
|
||||||
use crate::net::ssl::{gen_nistp256, make_root_cert};
|
use crate::net::ssl::{gen_nistp256, make_root_cert};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
@@ -23,7 +23,7 @@ fn hash_password(password: &str) -> Result<String, Error> {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AccountInfo {
|
pub struct AccountInfo {
|
||||||
pub server_id: String,
|
pub server_id: String,
|
||||||
pub hostname: Hostname,
|
pub hostname: ServerHostnameInfo,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub root_ca_key: PKey<Private>,
|
pub root_ca_key: PKey<Private>,
|
||||||
pub root_ca_cert: X509,
|
pub root_ca_cert: X509,
|
||||||
@@ -34,16 +34,16 @@ impl AccountInfo {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
password: &str,
|
password: &str,
|
||||||
start_time: SystemTime,
|
start_time: SystemTime,
|
||||||
hostname: Option<InternedString>,
|
hostname: Option<ServerHostnameInfo>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let server_id = generate_id();
|
let server_id = generate_id();
|
||||||
let hostname = if let Some(h) = hostname {
|
let hostname = if let Some(h) = hostname {
|
||||||
Hostname::validate(h)?
|
h
|
||||||
} else {
|
} else {
|
||||||
generate_hostname()
|
ServerHostnameInfo::from_hostname(generate_hostname())
|
||||||
};
|
};
|
||||||
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.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(
|
||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
));
|
));
|
||||||
@@ -62,7 +62,7 @@ impl AccountInfo {
|
|||||||
|
|
||||||
pub fn load(db: &DatabaseModel) -> Result<Self, Error> {
|
pub fn load(db: &DatabaseModel) -> Result<Self, Error> {
|
||||||
let server_id = db.as_public().as_server_info().as_id().de()?;
|
let server_id = db.as_public().as_server_info().as_id().de()?;
|
||||||
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
let hostname = ServerHostnameInfo::load(db.as_public().as_server_info())?;
|
||||||
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 cert_store = key_store.as_local_certs();
|
let cert_store = key_store.as_local_certs();
|
||||||
@@ -85,7 +85,7 @@ impl AccountInfo {
|
|||||||
pub fn save(&self, db: &mut DatabaseModel) -> Result<(), Error> {
|
pub fn save(&self, db: &mut DatabaseModel) -> Result<(), Error> {
|
||||||
let server_info = db.as_public_mut().as_server_info_mut();
|
let server_info = db.as_public_mut().as_server_info_mut();
|
||||||
server_info.as_id_mut().ser(&self.server_id)?;
|
server_info.as_id_mut().ser(&self.server_id)?;
|
||||||
server_info.as_hostname_mut().ser(&self.hostname.0)?;
|
self.hostname.save(server_info)?;
|
||||||
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()?)?;
|
||||||
@@ -123,8 +123,8 @@ impl AccountInfo {
|
|||||||
|
|
||||||
pub fn hostnames(&self) -> impl IntoIterator<Item = InternedString> + Send + '_ {
|
pub fn hostnames(&self) -> impl IntoIterator<Item = InternedString> + Send + '_ {
|
||||||
[
|
[
|
||||||
self.hostname.no_dot_host_name(),
|
(*self.hostname.hostname).clone(),
|
||||||
self.hostname.local_domain_name(),
|
self.hostname.hostname.local_domain_name(),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ async fn perform_backup(
|
|||||||
let timestamp = Utc::now();
|
let timestamp = Utc::now();
|
||||||
|
|
||||||
backup_guard.unencrypted_metadata.version = crate::version::Current::default().semver().into();
|
backup_guard.unencrypted_metadata.version = crate::version::Current::default().semver().into();
|
||||||
backup_guard.unencrypted_metadata.hostname = ctx.account.peek(|a| a.hostname.clone());
|
backup_guard.unencrypted_metadata.hostname = ctx.account.peek(|a| a.hostname.hostname.clone());
|
||||||
backup_guard.unencrypted_metadata.timestamp = timestamp.clone();
|
backup_guard.unencrypted_metadata.timestamp = timestamp.clone();
|
||||||
backup_guard.metadata.version = crate::version::Current::default().semver().into();
|
backup_guard.metadata.version = crate::version::Current::default().semver().into();
|
||||||
backup_guard.metadata.timestamp = Some(timestamp);
|
backup_guard.metadata.timestamp = Some(timestamp);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use ssh_key::private::Ed25519Keypair;
|
use ssh_key::private::Ed25519Keypair;
|
||||||
|
|
||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::hostname::{Hostname, generate_hostname, generate_id};
|
use crate::hostname::{ServerHostname, ServerHostnameInfo, generate_hostname, generate_id};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::{Base32, Base64, Pem};
|
use crate::util::serde::{Base32, Base64, Pem};
|
||||||
|
|
||||||
@@ -27,10 +27,12 @@ impl<'de> Deserialize<'de> for OsBackup {
|
|||||||
.map_err(serde::de::Error::custom)?,
|
.map_err(serde::de::Error::custom)?,
|
||||||
1 => patch_db::value::from_value::<OsBackupV1>(tagged.rest)
|
1 => patch_db::value::from_value::<OsBackupV1>(tagged.rest)
|
||||||
.map_err(serde::de::Error::custom)?
|
.map_err(serde::de::Error::custom)?
|
||||||
.project(),
|
.project()
|
||||||
|
.map_err(serde::de::Error::custom)?,
|
||||||
2 => patch_db::value::from_value::<OsBackupV2>(tagged.rest)
|
2 => patch_db::value::from_value::<OsBackupV2>(tagged.rest)
|
||||||
.map_err(serde::de::Error::custom)?
|
.map_err(serde::de::Error::custom)?
|
||||||
.project(),
|
.project()
|
||||||
|
.map_err(serde::de::Error::custom)?,
|
||||||
v => {
|
v => {
|
||||||
return Err(serde::de::Error::custom(&format!(
|
return Err(serde::de::Error::custom(&format!(
|
||||||
"Unknown backup version {v}"
|
"Unknown backup version {v}"
|
||||||
@@ -75,7 +77,7 @@ impl OsBackupV0 {
|
|||||||
Ok(OsBackup {
|
Ok(OsBackup {
|
||||||
account: AccountInfo {
|
account: AccountInfo {
|
||||||
server_id: generate_id(),
|
server_id: generate_id(),
|
||||||
hostname: generate_hostname(),
|
hostname: ServerHostnameInfo::from_hostname(generate_hostname()),
|
||||||
password: Default::default(),
|
password: Default::default(),
|
||||||
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,
|
||||||
@@ -104,11 +106,11 @@ struct OsBackupV1 {
|
|||||||
ui: Value, // JSON Value
|
ui: Value, // JSON Value
|
||||||
}
|
}
|
||||||
impl OsBackupV1 {
|
impl OsBackupV1 {
|
||||||
fn project(self) -> OsBackup {
|
fn project(self) -> Result<OsBackup, Error> {
|
||||||
OsBackup {
|
Ok(OsBackup {
|
||||||
account: AccountInfo {
|
account: AccountInfo {
|
||||||
server_id: self.server_id,
|
server_id: self.server_id,
|
||||||
hostname: Hostname(self.hostname),
|
hostname: ServerHostnameInfo::from_hostname(ServerHostname::new(self.hostname)?),
|
||||||
password: Default::default(),
|
password: Default::default(),
|
||||||
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,
|
||||||
@@ -116,7 +118,7 @@ impl OsBackupV1 {
|
|||||||
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,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,11 +136,11 @@ struct OsBackupV2 {
|
|||||||
ui: Value, // JSON Value
|
ui: Value, // JSON Value
|
||||||
}
|
}
|
||||||
impl OsBackupV2 {
|
impl OsBackupV2 {
|
||||||
fn project(self) -> OsBackup {
|
fn project(self) -> Result<OsBackup, Error> {
|
||||||
OsBackup {
|
Ok(OsBackup {
|
||||||
account: AccountInfo {
|
account: AccountInfo {
|
||||||
server_id: self.server_id,
|
server_id: self.server_id,
|
||||||
hostname: Hostname(self.hostname),
|
hostname: ServerHostnameInfo::from_hostname(ServerHostname::new(self.hostname)?),
|
||||||
password: Default::default(),
|
password: Default::default(),
|
||||||
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,
|
||||||
@@ -146,12 +148,12 @@ impl OsBackupV2 {
|
|||||||
developer_key: self.compat_s9pk_key.0,
|
developer_key: self.compat_s9pk_key.0,
|
||||||
},
|
},
|
||||||
ui: self.ui,
|
ui: self.ui,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
fn unproject(backup: &OsBackup) -> Self {
|
fn unproject(backup: &OsBackup) -> Self {
|
||||||
Self {
|
Self {
|
||||||
server_id: backup.account.server_id.clone(),
|
server_id: backup.account.server_id.clone(),
|
||||||
hostname: backup.account.hostname.0.clone(),
|
hostname: (*backup.account.hostname.hostname).clone(),
|
||||||
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()),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use crate::db::model::Database;
|
|||||||
use crate::disk::mount::backup::BackupMountGuard;
|
use crate::disk::mount::backup::BackupMountGuard;
|
||||||
use crate::disk::mount::filesystem::ReadWrite;
|
use crate::disk::mount::filesystem::ReadWrite;
|
||||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::ServerHostnameInfo;
|
||||||
use crate::init::init;
|
use crate::init::init;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::ProgressUnits;
|
use crate::progress::ProgressUnits;
|
||||||
@@ -91,7 +91,7 @@ pub async fn recover_full_server(
|
|||||||
server_id: &str,
|
server_id: &str,
|
||||||
recovery_password: &str,
|
recovery_password: &str,
|
||||||
kiosk: Option<bool>,
|
kiosk: Option<bool>,
|
||||||
hostname: Option<InternedString>,
|
hostname: Option<ServerHostnameInfo>,
|
||||||
SetupExecuteProgress {
|
SetupExecuteProgress {
|
||||||
init_phases,
|
init_phases,
|
||||||
restore_phase,
|
restore_phase,
|
||||||
@@ -118,7 +118,7 @@ pub async fn recover_full_server(
|
|||||||
.with_kind(ErrorKind::PasswordHashGeneration)?;
|
.with_kind(ErrorKind::PasswordHashGeneration)?;
|
||||||
|
|
||||||
if let Some(h) = hostname {
|
if let Some(h) = hostname {
|
||||||
os_backup.account.hostname = Hostname::validate(h)?;
|
os_backup.account.hostname = h;
|
||||||
}
|
}
|
||||||
|
|
||||||
let kiosk = Some(kiosk.unwrap_or(true)).filter(|_| &*PLATFORM != "raspberrypi");
|
let kiosk = Some(kiosk.unwrap_or(true)).filter(|_| &*PLATFORM != "raspberrypi");
|
||||||
@@ -189,7 +189,7 @@ pub async fn recover_full_server(
|
|||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
SetupResult {
|
SetupResult {
|
||||||
hostname: os_backup.account.hostname,
|
hostname: os_backup.account.hostname.hostname,
|
||||||
root_ca: Pem(os_backup.account.root_ca_cert),
|
root_ca: Pem(os_backup.account.root_ca_cert),
|
||||||
needs_restart: ctx.install_rootfs.peek(|a| a.is_some()),
|
needs_restart: ctx.install_rootfs.peek(|a| a.is_some()),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -218,7 +218,10 @@ pub struct CifsRemoveParams {
|
|||||||
pub id: BackupTargetId,
|
pub id: BackupTargetId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove(ctx: RpcContext, CifsRemoveParams { id }: CifsRemoveParams) -> Result<(), Error> {
|
pub async fn remove(
|
||||||
|
ctx: RpcContext,
|
||||||
|
CifsRemoveParams { id }: CifsRemoveParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let id = if let BackupTargetId::Cifs { id } = id {
|
let id = if let BackupTargetId::Cifs { id } = id {
|
||||||
id
|
id
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ async fn inner_main(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (rpc_ctx, shutdown) = async {
|
let (rpc_ctx, shutdown) = async {
|
||||||
crate::hostname::sync_hostname(&rpc_ctx.account.peek(|a| a.hostname.clone())).await?;
|
crate::hostname::sync_hostname(&rpc_ctx.account.peek(|a| a.hostname.hostname.clone()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut shutdown_recv = rpc_ctx.shutdown.subscribe();
|
let mut shutdown_recv = rpc_ctx.shutdown.subscribe();
|
||||||
|
|
||||||
@@ -147,10 +148,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
|||||||
.build()
|
.build()
|
||||||
.expect(&t!("bins.startd.failed-to-initialize-runtime"));
|
.expect(&t!("bins.startd.failed-to-initialize-runtime"));
|
||||||
let res = rt.block_on(async {
|
let res = rt.block_on(async {
|
||||||
let mut server = WebServer::new(
|
let mut server = WebServer::new(Acceptor::new(WildcardListener::new(80)?), refresher());
|
||||||
Acceptor::new(WildcardListener::new(80)?),
|
|
||||||
refresher(),
|
|
||||||
);
|
|
||||||
match inner_main(&mut server, &config).await {
|
match inner_main(&mut server, &config).await {
|
||||||
Ok(a) => {
|
Ok(a) => {
|
||||||
server.shutdown().await;
|
server.shutdown().await;
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ use clap::Parser;
|
|||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use rpc_toolkit::CliApp;
|
use rpc_toolkit::CliApp;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
use tokio::signal::unix::signal;
|
use tokio::signal::unix::signal;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use visit_rs::Visit;
|
use visit_rs::Visit;
|
||||||
|
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::context::config::ClientConfig;
|
use crate::context::config::ClientConfig;
|
||||||
use tokio::net::TcpListener;
|
|
||||||
use crate::net::tls::TlsListener;
|
use crate::net::tls::TlsListener;
|
||||||
use crate::net::web_server::{Accept, Acceptor, MetadataVisitor, WebServer};
|
use crate::net::web_server::{Accept, Acceptor, MetadataVisitor, WebServer};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use crate::MAIN_DATA;
|
|||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::disk::mount::guard::{MountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{MountGuard, TmpMountGuard};
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::ServerHostname;
|
||||||
use crate::net::gateway::WildcardListener;
|
use crate::net::gateway::WildcardListener;
|
||||||
use crate::net::web_server::{WebServer, WebServerAcceptorSetter};
|
use crate::net::web_server::{WebServer, WebServerAcceptorSetter};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -45,7 +45,7 @@ lazy_static::lazy_static! {
|
|||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct SetupResult {
|
pub struct SetupResult {
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub hostname: Hostname,
|
pub hostname: ServerHostname,
|
||||||
pub root_ca: Pem<X509>,
|
pub root_ca: Pem<X509>,
|
||||||
pub needs_restart: bool,
|
pub needs_restart: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ impl Public {
|
|||||||
platform: get_platform(),
|
platform: get_platform(),
|
||||||
id: account.server_id.clone(),
|
id: account.server_id.clone(),
|
||||||
version: Current::default().semver(),
|
version: Current::default().semver(),
|
||||||
hostname: account.hostname.no_dot_host_name(),
|
name: account.hostname.name.clone(),
|
||||||
|
hostname: (*account.hostname.hostname).clone(),
|
||||||
last_backup: None,
|
last_backup: None,
|
||||||
package_version_compat: Current::default().compat().clone(),
|
package_version_compat: Current::default().compat().clone(),
|
||||||
post_init_migration_todos: BTreeMap::new(),
|
post_init_migration_todos: BTreeMap::new(),
|
||||||
@@ -176,13 +177,11 @@ pub fn default_ifconfig_url() -> Url {
|
|||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct ServerInfo {
|
pub struct ServerInfo {
|
||||||
#[serde(default = "get_arch")]
|
#[serde(default = "get_arch")]
|
||||||
#[ts(type = "string")]
|
|
||||||
pub arch: InternedString,
|
pub arch: InternedString,
|
||||||
#[serde(default = "get_platform")]
|
#[serde(default = "get_platform")]
|
||||||
#[ts(type = "string")]
|
|
||||||
pub platform: InternedString,
|
pub platform: InternedString,
|
||||||
pub id: String,
|
pub id: String,
|
||||||
#[ts(type = "string")]
|
pub name: InternedString,
|
||||||
pub hostname: InternedString,
|
pub hostname: InternedString,
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use super::mount::filesystem::block_dev::BlockDev;
|
|||||||
use super::mount::guard::TmpMountGuard;
|
use super::mount::guard::TmpMountGuard;
|
||||||
use crate::disk::OsPartitionInfo;
|
use crate::disk::OsPartitionInfo;
|
||||||
use crate::disk::mount::guard::GenericMountGuard;
|
use crate::disk::mount::guard::GenericMountGuard;
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::ServerHostname;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::util::serde::IoFormat;
|
use crate::util::serde::IoFormat;
|
||||||
@@ -61,7 +61,7 @@ pub struct PartitionInfo {
|
|||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct StartOsRecoveryInfo {
|
pub struct StartOsRecoveryInfo {
|
||||||
pub hostname: Hostname,
|
pub hostname: ServerHostname,
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub version: exver::Version,
|
pub version: exver::Version,
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
|
|||||||
@@ -1,39 +1,41 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use lazy_format::lazy_format;
|
use lazy_format::lazy_format;
|
||||||
use rand::{Rng, rng};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
|
use crate::db::model::public::ServerInfo;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, ts_rs::TS)]
|
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, ts_rs::TS)]
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub struct Hostname(pub InternedString);
|
pub struct ServerHostname(InternedString);
|
||||||
|
impl std::ops::Deref for ServerHostname {
|
||||||
lazy_static::lazy_static! {
|
type Target = InternedString;
|
||||||
static ref ADJECTIVES: Vec<String> = include_str!("./assets/adjectives.txt").lines().map(|x| x.to_string()).collect();
|
fn deref(&self) -> &Self::Target {
|
||||||
static ref NOUNS: Vec<String> = include_str!("./assets/nouns.txt").lines().map(|x| x.to_string()).collect();
|
|
||||||
}
|
|
||||||
impl AsRef<str> for Hostname {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AsRef<str> for ServerHostname {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
&***self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Hostname {
|
impl ServerHostname {
|
||||||
pub fn validate(h: InternedString) -> Result<Self, Error> {
|
fn validate(&self) -> Result<(), Error> {
|
||||||
if h.is_empty() {
|
if self.0.is_empty() {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("{}", t!("hostname.empty")),
|
eyre!("{}", t!("hostname.empty")),
|
||||||
ErrorKind::InvalidRequest,
|
ErrorKind::InvalidRequest,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if let Some(c) = h
|
if let Some(c) = self
|
||||||
|
.0
|
||||||
.chars()
|
.chars()
|
||||||
.find(|c| !(c.is_ascii_alphanumeric() || c == &'-') || c.is_ascii_uppercase())
|
.find(|c| !(c.is_ascii_alphanumeric() || c == &'-') || c.is_ascii_uppercase())
|
||||||
{
|
{
|
||||||
@@ -42,7 +44,13 @@ impl Hostname {
|
|||||||
ErrorKind::InvalidRequest,
|
ErrorKind::InvalidRequest,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(Self(h))
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(hostname: InternedString) -> Result<Self, Error> {
|
||||||
|
let res = Self(hostname);
|
||||||
|
res.validate()?;
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lan_address(&self) -> InternedString {
|
pub fn lan_address(&self) -> InternedString {
|
||||||
@@ -53,17 +61,135 @@ impl Hostname {
|
|||||||
InternedString::from_display(&lazy_format!("{}.local", self.0))
|
InternedString::from_display(&lazy_format!("{}.local", self.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn no_dot_host_name(&self) -> InternedString {
|
pub fn load(server_info: &Model<ServerInfo>) -> Result<Self, Error> {
|
||||||
self.0.clone()
|
Ok(Self(server_info.as_hostname().de()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(&self, server_info: &mut Model<ServerInfo>) -> Result<(), Error> {
|
||||||
|
server_info.as_hostname_mut().ser(&**self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_hostname() -> Hostname {
|
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, ts_rs::TS)]
|
||||||
let mut rng = rng();
|
#[ts(type = "string")]
|
||||||
let adjective = &ADJECTIVES[rng.random_range(0..ADJECTIVES.len())];
|
pub struct ServerHostnameInfo {
|
||||||
let noun = &NOUNS[rng.random_range(0..NOUNS.len())];
|
pub name: InternedString,
|
||||||
Hostname(InternedString::from_display(&lazy_format!(
|
pub hostname: ServerHostname,
|
||||||
"{adjective}-{noun}"
|
}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref ADJECTIVES: Vec<String> = include_str!("./assets/adjectives.txt").lines().map(|x| x.to_string()).collect();
|
||||||
|
static ref NOUNS: Vec<String> = include_str!("./assets/nouns.txt").lines().map(|x| x.to_string()).collect();
|
||||||
|
}
|
||||||
|
impl AsRef<str> for ServerHostnameInfo {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
&self.hostname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize(s: &str) -> InternedString {
|
||||||
|
let mut prev_was_dash = true;
|
||||||
|
let mut normalized = s
|
||||||
|
.chars()
|
||||||
|
.filter_map(|c| {
|
||||||
|
if c.is_alphanumeric() {
|
||||||
|
prev_was_dash = false;
|
||||||
|
Some(c.to_ascii_lowercase())
|
||||||
|
} else if (c == '-' || c.is_whitespace()) && !prev_was_dash {
|
||||||
|
prev_was_dash = true;
|
||||||
|
Some('-')
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<String>();
|
||||||
|
while normalized.ends_with('-') {
|
||||||
|
normalized.pop();
|
||||||
|
}
|
||||||
|
if normalized.len() < 4 {
|
||||||
|
generate_hostname().0
|
||||||
|
} else {
|
||||||
|
normalized.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn denormalize(s: &str) -> InternedString {
|
||||||
|
let mut cap = true;
|
||||||
|
s.chars()
|
||||||
|
.map(|c| {
|
||||||
|
if c == '-' {
|
||||||
|
cap = true;
|
||||||
|
' '
|
||||||
|
} else if cap {
|
||||||
|
cap = false;
|
||||||
|
c.to_ascii_uppercase()
|
||||||
|
} else {
|
||||||
|
c
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<String>()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerHostnameInfo {
|
||||||
|
pub fn new(
|
||||||
|
name: Option<InternedString>,
|
||||||
|
hostname: Option<InternedString>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
Self::new_opt(name, hostname)
|
||||||
|
.map(|h| h.unwrap_or_else(|| ServerHostnameInfo::from_hostname(generate_hostname())))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_opt(
|
||||||
|
name: Option<InternedString>,
|
||||||
|
hostname: Option<InternedString>,
|
||||||
|
) -> Result<Option<Self>, Error> {
|
||||||
|
let name = name.filter(|n| !n.is_empty());
|
||||||
|
let hostname = hostname.filter(|h| !h.is_empty());
|
||||||
|
Ok(match (name, hostname) {
|
||||||
|
(Some(name), Some(hostname)) => Some(ServerHostnameInfo {
|
||||||
|
name,
|
||||||
|
hostname: ServerHostname::new(hostname)?,
|
||||||
|
}),
|
||||||
|
(Some(name), None) => Some(ServerHostnameInfo::from_name(name)),
|
||||||
|
(None, Some(hostname)) => Some(ServerHostnameInfo::from_hostname(ServerHostname::new(
|
||||||
|
hostname,
|
||||||
|
)?)),
|
||||||
|
(None, None) => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_hostname(hostname: ServerHostname) -> Self {
|
||||||
|
Self {
|
||||||
|
name: denormalize(&**hostname),
|
||||||
|
hostname,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_name(name: InternedString) -> Self {
|
||||||
|
Self {
|
||||||
|
hostname: ServerHostname(normalize(&*name)),
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(server_info: &Model<ServerInfo>) -> Result<Self, Error> {
|
||||||
|
Ok(Self {
|
||||||
|
name: server_info.as_name().de()?,
|
||||||
|
hostname: ServerHostname::load(server_info)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(&self, server_info: &mut Model<ServerInfo>) -> Result<(), Error> {
|
||||||
|
server_info.as_name_mut().ser(&self.name)?;
|
||||||
|
self.hostname.save(server_info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_hostname() -> ServerHostname {
|
||||||
|
let num = rand::random::<u16>();
|
||||||
|
ServerHostname(InternedString::from_display(&lazy_format!(
|
||||||
|
"startos-{num:04x}"
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,17 +199,17 @@ pub fn generate_id() -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn get_current_hostname() -> Result<Hostname, Error> {
|
pub async fn get_current_hostname() -> Result<InternedString, Error> {
|
||||||
let out = Command::new("hostname")
|
let out = Command::new("hostname")
|
||||||
.invoke(ErrorKind::ParseSysInfo)
|
.invoke(ErrorKind::ParseSysInfo)
|
||||||
.await?;
|
.await?;
|
||||||
let out_string = String::from_utf8(out)?;
|
let out_string = String::from_utf8(out)?;
|
||||||
Ok(Hostname(out_string.trim().into()))
|
Ok(out_string.trim().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn set_hostname(hostname: &Hostname) -> Result<(), Error> {
|
pub async fn set_hostname(hostname: &ServerHostname) -> Result<(), Error> {
|
||||||
let hostname = &*hostname.0;
|
let hostname = &***hostname;
|
||||||
Command::new("hostnamectl")
|
Command::new("hostnamectl")
|
||||||
.arg("--static")
|
.arg("--static")
|
||||||
.arg("set-hostname")
|
.arg("set-hostname")
|
||||||
@@ -102,7 +228,7 @@ pub async fn set_hostname(hostname: &Hostname) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn sync_hostname(hostname: &Hostname) -> Result<(), Error> {
|
pub async fn sync_hostname(hostname: &ServerHostname) -> Result<(), Error> {
|
||||||
set_hostname(hostname).await?;
|
set_hostname(hostname).await?;
|
||||||
Command::new("systemctl")
|
Command::new("systemctl")
|
||||||
.arg("restart")
|
.arg("restart")
|
||||||
@@ -117,25 +243,31 @@ pub async fn sync_hostname(hostname: &Hostname) -> Result<(), Error> {
|
|||||||
#[command(rename_all = "kebab-case")]
|
#[command(rename_all = "kebab-case")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct SetServerHostnameParams {
|
pub struct SetServerHostnameParams {
|
||||||
hostname: InternedString,
|
name: Option<InternedString>,
|
||||||
|
hostname: Option<InternedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_hostname_rpc(
|
pub async fn set_hostname_rpc(
|
||||||
ctx: RpcContext,
|
ctx: RpcContext,
|
||||||
SetServerHostnameParams { hostname }: SetServerHostnameParams,
|
SetServerHostnameParams { name, hostname }: SetServerHostnameParams,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let hostname = Hostname::validate(hostname)?;
|
let Some(hostname) = ServerHostnameInfo::new_opt(name, hostname)? else {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("{}", t!("hostname.must-provide-name-or-hostname")),
|
||||||
|
ErrorKind::InvalidRequest,
|
||||||
|
));
|
||||||
|
};
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| hostname.save(db.as_public_mut().as_server_info_mut()))
|
||||||
db.as_public_mut()
|
|
||||||
.as_server_info_mut()
|
|
||||||
.as_hostname_mut()
|
|
||||||
.ser(&hostname.0)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.result?;
|
.result?;
|
||||||
ctx.account.mutate(|a| a.hostname = hostname.clone());
|
ctx.account.mutate(|a| a.hostname = hostname.clone());
|
||||||
sync_hostname(&hostname).await?;
|
sync_hostname(&hostname.hostname).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate_hostname() {
|
||||||
|
assert_eq!(dbg!(generate_hostname().0).len(), 12);
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use crate::context::{CliContext, InitContext, RpcContext};
|
|||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
use crate::db::model::public::ServerStatus;
|
use crate::db::model::public::ServerStatus;
|
||||||
use crate::developer::OS_DEVELOPER_KEY_PATH;
|
use crate::developer::OS_DEVELOPER_KEY_PATH;
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::ServerHostname;
|
||||||
use crate::middleware::auth::local::LocalAuthContext;
|
use crate::middleware::auth::local::LocalAuthContext;
|
||||||
use crate::net::gateway::WildcardListener;
|
use crate::net::gateway::WildcardListener;
|
||||||
use crate::net::net_controller::{NetController, NetService};
|
use crate::net::net_controller::{NetController, NetService};
|
||||||
@@ -191,15 +191,16 @@ pub async fn init(
|
|||||||
.arg(OS_DEVELOPER_KEY_PATH)
|
.arg(OS_DEVELOPER_KEY_PATH)
|
||||||
.invoke(ErrorKind::Filesystem)
|
.invoke(ErrorKind::Filesystem)
|
||||||
.await?;
|
.await?;
|
||||||
|
let hostname = ServerHostname::load(peek.as_public().as_server_info())?;
|
||||||
crate::ssh::sync_keys(
|
crate::ssh::sync_keys(
|
||||||
&Hostname(peek.as_public().as_server_info().as_hostname().de()?),
|
&hostname,
|
||||||
&peek.as_private().as_ssh_privkey().de()?,
|
&peek.as_private().as_ssh_privkey().de()?,
|
||||||
&peek.as_private().as_ssh_pubkeys().de()?,
|
&peek.as_private().as_ssh_pubkeys().de()?,
|
||||||
SSH_DIR,
|
SSH_DIR,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
crate::ssh::sync_keys(
|
crate::ssh::sync_keys(
|
||||||
&Hostname(peek.as_public().as_server_info().as_hostname().de()?),
|
&hostname,
|
||||||
&peek.as_private().as_ssh_privkey().de()?,
|
&peek.as_private().as_ssh_privkey().de()?,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
"/root/.ssh",
|
"/root/.ssh",
|
||||||
|
|||||||
@@ -279,9 +279,7 @@ impl Resolver {
|
|||||||
let Some((ref config, ref opts)) = last_config else {
|
let Some((ref config, ref opts)) = last_config else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let static_servers: Option<
|
let static_servers: Option<std::collections::VecDeque<SocketAddr>> = db
|
||||||
std::collections::VecDeque<SocketAddr>,
|
|
||||||
> = db
|
|
||||||
.peek()
|
.peek()
|
||||||
.await
|
.await
|
||||||
.as_public()
|
.as_public()
|
||||||
@@ -290,12 +288,9 @@ impl Resolver {
|
|||||||
.as_dns()
|
.as_dns()
|
||||||
.as_static_servers()
|
.as_static_servers()
|
||||||
.de()?;
|
.de()?;
|
||||||
let hash =
|
let hash = crate::util::serde::hash_serializable::<sha2::Sha256, _>(
|
||||||
crate::util::serde::hash_serializable::<sha2::Sha256, _>(&(
|
&(config, opts, &static_servers),
|
||||||
config,
|
)?;
|
||||||
opts,
|
|
||||||
&static_servers,
|
|
||||||
))?;
|
|
||||||
if hash == prev {
|
if hash == prev {
|
||||||
prev = hash;
|
prev = hash;
|
||||||
continue;
|
continue;
|
||||||
@@ -320,26 +315,25 @@ impl Resolver {
|
|||||||
.await
|
.await
|
||||||
.result?;
|
.result?;
|
||||||
}
|
}
|
||||||
let forward_servers =
|
let forward_servers = if let Some(servers) = &static_servers {
|
||||||
if let Some(servers) = &static_servers {
|
servers
|
||||||
servers
|
.iter()
|
||||||
.iter()
|
.flat_map(|addr| {
|
||||||
.flat_map(|addr| {
|
[
|
||||||
[
|
NameServerConfig::new(*addr, Protocol::Udp),
|
||||||
NameServerConfig::new(*addr, Protocol::Udp),
|
NameServerConfig::new(*addr, Protocol::Tcp),
|
||||||
NameServerConfig::new(*addr, Protocol::Tcp),
|
]
|
||||||
]
|
})
|
||||||
})
|
.map(|n| to_value(&n))
|
||||||
.map(|n| to_value(&n))
|
.collect::<Result<_, Error>>()?
|
||||||
.collect::<Result<_, Error>>()?
|
} else {
|
||||||
} else {
|
config
|
||||||
config
|
.name_servers()
|
||||||
.name_servers()
|
.into_iter()
|
||||||
.into_iter()
|
.skip(4)
|
||||||
.skip(4)
|
.map(to_value)
|
||||||
.map(to_value)
|
.collect::<Result<_, Error>>()?
|
||||||
.collect::<Result<_, Error>>()?
|
};
|
||||||
};
|
|
||||||
let auth: Vec<Arc<dyn AuthorityObject>> = vec![Arc::new(
|
let auth: Vec<Arc<dyn AuthorityObject>> = vec![Arc::new(
|
||||||
ForwardAuthority::builder_tokio(ForwardConfig {
|
ForwardAuthority::builder_tokio(ForwardConfig {
|
||||||
name_servers: from_value(Value::Array(forward_servers))?,
|
name_servers: from_value(Value::Array(forward_servers))?,
|
||||||
@@ -349,17 +343,15 @@ impl Resolver {
|
|||||||
.map_err(|e| Error::new(eyre!("{e}"), ErrorKind::Network))?,
|
.map_err(|e| Error::new(eyre!("{e}"), ErrorKind::Network))?,
|
||||||
)];
|
)];
|
||||||
{
|
{
|
||||||
let mut guard = tokio::time::timeout(
|
let mut guard =
|
||||||
Duration::from_secs(10),
|
tokio::time::timeout(Duration::from_secs(10), catalog.write())
|
||||||
catalog.write(),
|
.await
|
||||||
)
|
.map_err(|_| {
|
||||||
.await
|
Error::new(
|
||||||
.map_err(|_| {
|
eyre!("{}", t!("net.dns.timeout-updating-catalog")),
|
||||||
Error::new(
|
ErrorKind::Timeout,
|
||||||
eyre!("{}", t!("net.dns.timeout-updating-catalog")),
|
)
|
||||||
ErrorKind::Timeout,
|
})?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
guard.upsert(Name::root().into(), auth);
|
guard.upsert(Name::root().into(), auth);
|
||||||
drop(guard);
|
drop(guard);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::future::Future;
|
|||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::task::Poll;
|
use std::task::Poll;
|
||||||
use std::time::Duration;
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::{FutureExt, Stream, StreamExt, TryStreamExt};
|
use futures::{FutureExt, Stream, StreamExt, TryStreamExt};
|
||||||
@@ -732,6 +732,57 @@ async fn get_wan_ipv4(iface: &str, base_url: &Url) -> Result<Option<Ipv4Addr>, E
|
|||||||
Ok(Some(trimmed.parse()?))
|
Ok(Some(trimmed.parse()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PolicyRoutingCleanup {
|
||||||
|
table_id: u32,
|
||||||
|
iface: String,
|
||||||
|
}
|
||||||
|
impl Drop for PolicyRoutingCleanup {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let table_str = self.table_id.to_string();
|
||||||
|
let iface = std::mem::take(&mut self.iface);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("rule")
|
||||||
|
.arg("del")
|
||||||
|
.arg("fwmark")
|
||||||
|
.arg(&table_str)
|
||||||
|
.arg("lookup")
|
||||||
|
.arg(&table_str)
|
||||||
|
.arg("priority")
|
||||||
|
.arg("50")
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("route")
|
||||||
|
.arg("flush")
|
||||||
|
.arg("table")
|
||||||
|
.arg(&table_str)
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
Command::new("iptables")
|
||||||
|
.arg("-t")
|
||||||
|
.arg("mangle")
|
||||||
|
.arg("-D")
|
||||||
|
.arg("PREROUTING")
|
||||||
|
.arg("-i")
|
||||||
|
.arg(&iface)
|
||||||
|
.arg("-m")
|
||||||
|
.arg("conntrack")
|
||||||
|
.arg("--ctstate")
|
||||||
|
.arg("NEW")
|
||||||
|
.arg("-j")
|
||||||
|
.arg("CONNMARK")
|
||||||
|
.arg("--set-mark")
|
||||||
|
.arg(&table_str)
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip(connection, device_proxy, write_to, db))]
|
#[instrument(skip(connection, device_proxy, write_to, db))]
|
||||||
async fn watch_ip(
|
async fn watch_ip(
|
||||||
connection: &Connection,
|
connection: &Connection,
|
||||||
@@ -758,12 +809,14 @@ async fn watch_ip(
|
|||||||
.with_stream(device_proxy.receive_ip6_config_changed().await.stub())
|
.with_stream(device_proxy.receive_ip6_config_changed().await.stub())
|
||||||
.with_async_fn(|| {
|
.with_async_fn(|| {
|
||||||
async {
|
async {
|
||||||
tokio::time::sleep(Duration::from_secs(300)).await;
|
tokio::time::sleep(Duration::from_secs(600)).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
.fuse()
|
.fuse()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let mut prev_attempt: Option<Instant> = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
until
|
until
|
||||||
.run(async {
|
.run(async {
|
||||||
@@ -850,10 +903,7 @@ async fn watch_ip(
|
|||||||
// Policy routing: track per-interface table for cleanup on scope exit
|
// Policy routing: track per-interface table for cleanup on scope exit
|
||||||
let policy_table_id = if !matches!(
|
let policy_table_id = if !matches!(
|
||||||
device_type,
|
device_type,
|
||||||
Some(
|
Some(NetworkInterfaceType::Bridge | NetworkInterfaceType::Loopback)
|
||||||
NetworkInterfaceType::Bridge
|
|
||||||
| NetworkInterfaceType::Loopback
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
if_nametoindex(iface.as_str())
|
if_nametoindex(iface.as_str())
|
||||||
.map(|idx| 1000 + idx)
|
.map(|idx| 1000 + idx)
|
||||||
@@ -861,44 +911,7 @@ async fn watch_ip(
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
struct PolicyRoutingCleanup {
|
let policy_guard: Option<PolicyRoutingCleanup> =
|
||||||
table_id: u32,
|
|
||||||
iface: String,
|
|
||||||
}
|
|
||||||
impl Drop for PolicyRoutingCleanup {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let table_str = self.table_id.to_string();
|
|
||||||
let iface = std::mem::take(&mut self.iface);
|
|
||||||
tokio::spawn(async move {
|
|
||||||
Command::new("ip")
|
|
||||||
.arg("rule").arg("del")
|
|
||||||
.arg("fwmark").arg(&table_str)
|
|
||||||
.arg("lookup").arg(&table_str)
|
|
||||||
.arg("priority").arg("50")
|
|
||||||
.invoke(ErrorKind::Network)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
Command::new("ip")
|
|
||||||
.arg("route").arg("flush")
|
|
||||||
.arg("table").arg(&table_str)
|
|
||||||
.invoke(ErrorKind::Network)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
Command::new("iptables")
|
|
||||||
.arg("-t").arg("mangle")
|
|
||||||
.arg("-D").arg("PREROUTING")
|
|
||||||
.arg("-i").arg(&iface)
|
|
||||||
.arg("-m").arg("conntrack")
|
|
||||||
.arg("--ctstate").arg("NEW")
|
|
||||||
.arg("-j").arg("CONNMARK")
|
|
||||||
.arg("--set-mark").arg(&table_str)
|
|
||||||
.invoke(ErrorKind::Network)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _policy_guard: Option<PolicyRoutingCleanup> =
|
|
||||||
policy_table_id.map(|t| PolicyRoutingCleanup {
|
policy_table_id.map(|t| PolicyRoutingCleanup {
|
||||||
table_id: t,
|
table_id: t,
|
||||||
iface: iface.as_str().to_owned(),
|
iface: iface.as_str().to_owned(),
|
||||||
@@ -906,271 +919,18 @@ async fn watch_ip(
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
until
|
until
|
||||||
.run(async {
|
.run(poll_ip_info(
|
||||||
let addresses = ip4_proxy
|
&ip4_proxy,
|
||||||
.address_data()
|
&ip6_proxy,
|
||||||
.await?
|
&dhcp4_proxy,
|
||||||
.into_iter()
|
&policy_guard,
|
||||||
.chain(ip6_proxy.address_data().await?)
|
&iface,
|
||||||
.collect_vec();
|
&mut prev_attempt,
|
||||||
let lan_ip: OrdSet<IpAddr> = [
|
db,
|
||||||
Some(ip4_proxy.gateway().await?)
|
write_to,
|
||||||
.filter(|g| !g.is_empty())
|
device_type,
|
||||||
.and_then(|g| g.parse::<IpAddr>().log_err()),
|
&name,
|
||||||
Some(ip6_proxy.gateway().await?)
|
))
|
||||||
.filter(|g| !g.is_empty())
|
|
||||||
.and_then(|g| g.parse::<IpAddr>().log_err()),
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|a| a)
|
|
||||||
.collect();
|
|
||||||
let mut ntp_servers = OrdSet::new();
|
|
||||||
let mut dns_servers = OrdSet::new();
|
|
||||||
if let Some(dhcp4_proxy) = &dhcp4_proxy {
|
|
||||||
let dhcp = dhcp4_proxy.options().await?;
|
|
||||||
if let Some(ntp) = dhcp.ntp_servers {
|
|
||||||
ntp_servers.extend(
|
|
||||||
ntp.split_whitespace()
|
|
||||||
.map(InternedString::intern),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(dns) = dhcp.domain_name_servers {
|
|
||||||
dns_servers.extend(
|
|
||||||
dns.split_ascii_whitespace()
|
|
||||||
.filter_map(|s| {
|
|
||||||
s.parse::<IpAddr>().log_err()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let scope_id = if_nametoindex(iface.as_str())
|
|
||||||
.with_kind(ErrorKind::Network)?;
|
|
||||||
let subnets: OrdSet<IpNet> = addresses
|
|
||||||
.into_iter()
|
|
||||||
.map(IpNet::try_from)
|
|
||||||
.try_collect()?;
|
|
||||||
// Policy routing: ensure replies exit the same interface
|
|
||||||
// they arrived on, eliminating the need for MASQUERADE.
|
|
||||||
if let Some(guard) = &_policy_guard {
|
|
||||||
let table_id = guard.table_id;
|
|
||||||
let table_str = table_id.to_string();
|
|
||||||
|
|
||||||
let ipv4_gateway: Option<Ipv4Addr> =
|
|
||||||
lan_ip.iter().find_map(|ip| match ip {
|
|
||||||
IpAddr::V4(v4) => Some(v4),
|
|
||||||
_ => None,
|
|
||||||
}).copied();
|
|
||||||
|
|
||||||
// Flush and rebuild per-interface routing table.
|
|
||||||
// Clone all non-default routes from the main table so
|
|
||||||
// that LAN IPs on other subnets remain reachable when
|
|
||||||
// the priority-75 catch-all overrides default routing,
|
|
||||||
// then replace the default route with this interface's.
|
|
||||||
Command::new("ip")
|
|
||||||
.arg("route").arg("flush")
|
|
||||||
.arg("table").arg(&table_str)
|
|
||||||
.invoke(ErrorKind::Network)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
if let Ok(main_routes) = Command::new("ip")
|
|
||||||
.arg("route").arg("show")
|
|
||||||
.arg("table").arg("main")
|
|
||||||
.invoke(ErrorKind::Network)
|
|
||||||
.await
|
|
||||||
.and_then(|b| String::from_utf8(b).with_kind(ErrorKind::Utf8))
|
|
||||||
{
|
|
||||||
for line in main_routes.lines() {
|
|
||||||
let line = line.trim();
|
|
||||||
if line.is_empty() || line.starts_with("default") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let mut cmd = Command::new("ip");
|
|
||||||
cmd.arg("route").arg("add");
|
|
||||||
for part in line.split_whitespace() {
|
|
||||||
// Skip status flags that appear in
|
|
||||||
// route output but are not valid for
|
|
||||||
// `ip route add`.
|
|
||||||
if part == "linkdown" || part == "dead" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
cmd.arg(part);
|
|
||||||
}
|
|
||||||
cmd.arg("table").arg(&table_str);
|
|
||||||
cmd.invoke(ErrorKind::Network)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add default route via this interface's gateway
|
|
||||||
{
|
|
||||||
let mut cmd = Command::new("ip");
|
|
||||||
cmd.arg("route").arg("add").arg("default");
|
|
||||||
if let Some(gw) = ipv4_gateway {
|
|
||||||
cmd.arg("via").arg(gw.to_string());
|
|
||||||
}
|
|
||||||
cmd.arg("dev").arg(iface.as_str())
|
|
||||||
.arg("table").arg(&table_str);
|
|
||||||
if ipv4_gateway.is_none() {
|
|
||||||
cmd.arg("scope").arg("link");
|
|
||||||
}
|
|
||||||
cmd.invoke(ErrorKind::Network)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure global CONNMARK restore rules in mangle
|
|
||||||
// PREROUTING (forwarded packets) and OUTPUT (locally-generated replies).
|
|
||||||
// Both are needed: PREROUTING handles DNAT-forwarded traffic,
|
|
||||||
// OUTPUT handles replies from locally-bound listeners (e.g. vhost).
|
|
||||||
// The `-m mark --mark 0` condition ensures we only restore
|
|
||||||
// when the packet has no existing fwmark, preserving marks
|
|
||||||
// set by WireGuard on encapsulation packets.
|
|
||||||
for chain in ["PREROUTING", "OUTPUT"] {
|
|
||||||
if Command::new("iptables")
|
|
||||||
.arg("-t").arg("mangle")
|
|
||||||
.arg("-C").arg(chain)
|
|
||||||
.arg("-m").arg("mark").arg("--mark").arg("0")
|
|
||||||
.arg("-j").arg("CONNMARK")
|
|
||||||
.arg("--restore-mark")
|
|
||||||
.invoke(ErrorKind::Network).await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
Command::new("iptables")
|
|
||||||
.arg("-t").arg("mangle")
|
|
||||||
.arg("-I").arg(chain).arg("1")
|
|
||||||
.arg("-m").arg("mark").arg("--mark").arg("0")
|
|
||||||
.arg("-j").arg("CONNMARK")
|
|
||||||
.arg("--restore-mark")
|
|
||||||
.invoke(ErrorKind::Network)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark NEW connections arriving on this interface
|
|
||||||
// with its routing table ID via conntrack mark
|
|
||||||
if Command::new("iptables")
|
|
||||||
.arg("-t").arg("mangle")
|
|
||||||
.arg("-C").arg("PREROUTING")
|
|
||||||
.arg("-i").arg(iface.as_str())
|
|
||||||
.arg("-m").arg("conntrack")
|
|
||||||
.arg("--ctstate").arg("NEW")
|
|
||||||
.arg("-j").arg("CONNMARK")
|
|
||||||
.arg("--set-mark").arg(&table_str)
|
|
||||||
.invoke(ErrorKind::Network).await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
Command::new("iptables")
|
|
||||||
.arg("-t").arg("mangle")
|
|
||||||
.arg("-A").arg("PREROUTING")
|
|
||||||
.arg("-i").arg(iface.as_str())
|
|
||||||
.arg("-m").arg("conntrack")
|
|
||||||
.arg("--ctstate").arg("NEW")
|
|
||||||
.arg("-j").arg("CONNMARK")
|
|
||||||
.arg("--set-mark").arg(&table_str)
|
|
||||||
.invoke(ErrorKind::Network)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure fwmark-based ip rule for this interface's table
|
|
||||||
let rules_output = String::from_utf8(
|
|
||||||
Command::new("ip")
|
|
||||||
.arg("rule").arg("list")
|
|
||||||
.invoke(ErrorKind::Network)
|
|
||||||
.await?,
|
|
||||||
)?;
|
|
||||||
if !rules_output.lines().any(|l| {
|
|
||||||
l.contains("fwmark")
|
|
||||||
&& l.contains(&format!("lookup {table_id}"))
|
|
||||||
}) {
|
|
||||||
Command::new("ip")
|
|
||||||
.arg("rule").arg("add")
|
|
||||||
.arg("fwmark").arg(&table_str)
|
|
||||||
.arg("lookup").arg(&table_str)
|
|
||||||
.arg("priority").arg("50")
|
|
||||||
.invoke(ErrorKind::Network)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let ifconfig_url = if let Some(db) = db {
|
|
||||||
db.peek()
|
|
||||||
.await
|
|
||||||
.as_public()
|
|
||||||
.as_server_info()
|
|
||||||
.as_ifconfig_url()
|
|
||||||
.de()
|
|
||||||
.unwrap_or_else(|_| crate::db::model::public::default_ifconfig_url())
|
|
||||||
} else {
|
|
||||||
crate::db::model::public::default_ifconfig_url()
|
|
||||||
};
|
|
||||||
let wan_ip = if !subnets.is_empty()
|
|
||||||
&& !matches!(
|
|
||||||
device_type,
|
|
||||||
Some(
|
|
||||||
NetworkInterfaceType::Bridge
|
|
||||||
| NetworkInterfaceType::Loopback
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
match get_wan_ipv4(iface.as_str(), &ifconfig_url).await {
|
|
||||||
Ok(a) => a,
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!(
|
|
||||||
"{}",
|
|
||||||
t!("net.gateway.failed-to-determine-wan-ip", iface = iface.to_string(), error = e.to_string())
|
|
||||||
);
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let mut ip_info = IpInfo {
|
|
||||||
name: name.clone(),
|
|
||||||
scope_id,
|
|
||||||
device_type,
|
|
||||||
subnets,
|
|
||||||
lan_ip,
|
|
||||||
wan_ip,
|
|
||||||
ntp_servers,
|
|
||||||
dns_servers,
|
|
||||||
};
|
|
||||||
|
|
||||||
write_to.send_if_modified(
|
|
||||||
|m: &mut OrdMap<GatewayId, NetworkInterfaceInfo>| {
|
|
||||||
let (name, secure, gateway_type, prev_wan_ip) = m
|
|
||||||
.get(&iface)
|
|
||||||
.map_or((None, None, None, None), |i| {
|
|
||||||
(
|
|
||||||
i.name.clone(),
|
|
||||||
i.secure,
|
|
||||||
i.gateway_type,
|
|
||||||
i.ip_info
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|i| i.wan_ip),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
ip_info.wan_ip = ip_info.wan_ip.or(prev_wan_ip);
|
|
||||||
let ip_info = Arc::new(ip_info);
|
|
||||||
m.insert(
|
|
||||||
iface.clone(),
|
|
||||||
NetworkInterfaceInfo {
|
|
||||||
name,
|
|
||||||
secure,
|
|
||||||
ip_info: Some(ip_info.clone()),
|
|
||||||
gateway_type,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.filter(|old| &old.ip_info == &Some(ip_info))
|
|
||||||
.is_none()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok::<_, Error>(())
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1181,6 +941,319 @@ async fn watch_ip(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn apply_policy_routing(
|
||||||
|
guard: &PolicyRoutingCleanup,
|
||||||
|
iface: &GatewayId,
|
||||||
|
lan_ip: &OrdSet<IpAddr>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let table_id = guard.table_id;
|
||||||
|
let table_str = table_id.to_string();
|
||||||
|
|
||||||
|
let ipv4_gateway: Option<Ipv4Addr> = lan_ip
|
||||||
|
.iter()
|
||||||
|
.find_map(|ip| match ip {
|
||||||
|
IpAddr::V4(v4) => Some(v4),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.copied();
|
||||||
|
|
||||||
|
// Flush and rebuild per-interface routing table.
|
||||||
|
// Clone all non-default routes from the main table so that LAN IPs on
|
||||||
|
// other subnets remain reachable when the priority-75 catch-all overrides
|
||||||
|
// default routing, then replace the default route with this interface's.
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("route")
|
||||||
|
.arg("flush")
|
||||||
|
.arg("table")
|
||||||
|
.arg(&table_str)
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
if let Ok(main_routes) = Command::new("ip")
|
||||||
|
.arg("route")
|
||||||
|
.arg("show")
|
||||||
|
.arg("table")
|
||||||
|
.arg("main")
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.and_then(|b| String::from_utf8(b).with_kind(ErrorKind::Utf8))
|
||||||
|
{
|
||||||
|
for line in main_routes.lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.is_empty() || line.starts_with("default") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut cmd = Command::new("ip");
|
||||||
|
cmd.arg("route").arg("add");
|
||||||
|
for part in line.split_whitespace() {
|
||||||
|
// Skip status flags that appear in route output but
|
||||||
|
// are not valid for `ip route add`.
|
||||||
|
if part == "linkdown" || part == "dead" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cmd.arg(part);
|
||||||
|
}
|
||||||
|
cmd.arg("table").arg(&table_str);
|
||||||
|
cmd.invoke(ErrorKind::Network).await.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add default route via this interface's gateway
|
||||||
|
{
|
||||||
|
let mut cmd = Command::new("ip");
|
||||||
|
cmd.arg("route").arg("add").arg("default");
|
||||||
|
if let Some(gw) = ipv4_gateway {
|
||||||
|
cmd.arg("via").arg(gw.to_string());
|
||||||
|
}
|
||||||
|
cmd.arg("dev")
|
||||||
|
.arg(iface.as_str())
|
||||||
|
.arg("table")
|
||||||
|
.arg(&table_str);
|
||||||
|
if ipv4_gateway.is_none() {
|
||||||
|
cmd.arg("scope").arg("link");
|
||||||
|
}
|
||||||
|
cmd.invoke(ErrorKind::Network).await.log_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure global CONNMARK restore rules in mangle PREROUTING (forwarded
|
||||||
|
// packets) and OUTPUT (locally-generated replies). Both are needed:
|
||||||
|
// PREROUTING handles DNAT-forwarded traffic, OUTPUT handles replies from
|
||||||
|
// locally-bound listeners (e.g. vhost). The `-m mark --mark 0` condition
|
||||||
|
// ensures we only restore when the packet has no existing fwmark,
|
||||||
|
// preserving marks set by WireGuard on encapsulation packets.
|
||||||
|
for chain in ["PREROUTING", "OUTPUT"] {
|
||||||
|
if Command::new("iptables")
|
||||||
|
.arg("-t")
|
||||||
|
.arg("mangle")
|
||||||
|
.arg("-C")
|
||||||
|
.arg(chain)
|
||||||
|
.arg("-m")
|
||||||
|
.arg("mark")
|
||||||
|
.arg("--mark")
|
||||||
|
.arg("0")
|
||||||
|
.arg("-j")
|
||||||
|
.arg("CONNMARK")
|
||||||
|
.arg("--restore-mark")
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
Command::new("iptables")
|
||||||
|
.arg("-t")
|
||||||
|
.arg("mangle")
|
||||||
|
.arg("-I")
|
||||||
|
.arg(chain)
|
||||||
|
.arg("1")
|
||||||
|
.arg("-m")
|
||||||
|
.arg("mark")
|
||||||
|
.arg("--mark")
|
||||||
|
.arg("0")
|
||||||
|
.arg("-j")
|
||||||
|
.arg("CONNMARK")
|
||||||
|
.arg("--restore-mark")
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark NEW connections arriving on this interface with its routing
|
||||||
|
// table ID via conntrack mark
|
||||||
|
if Command::new("iptables")
|
||||||
|
.arg("-t")
|
||||||
|
.arg("mangle")
|
||||||
|
.arg("-C")
|
||||||
|
.arg("PREROUTING")
|
||||||
|
.arg("-i")
|
||||||
|
.arg(iface.as_str())
|
||||||
|
.arg("-m")
|
||||||
|
.arg("conntrack")
|
||||||
|
.arg("--ctstate")
|
||||||
|
.arg("NEW")
|
||||||
|
.arg("-j")
|
||||||
|
.arg("CONNMARK")
|
||||||
|
.arg("--set-mark")
|
||||||
|
.arg(&table_str)
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
Command::new("iptables")
|
||||||
|
.arg("-t")
|
||||||
|
.arg("mangle")
|
||||||
|
.arg("-A")
|
||||||
|
.arg("PREROUTING")
|
||||||
|
.arg("-i")
|
||||||
|
.arg(iface.as_str())
|
||||||
|
.arg("-m")
|
||||||
|
.arg("conntrack")
|
||||||
|
.arg("--ctstate")
|
||||||
|
.arg("NEW")
|
||||||
|
.arg("-j")
|
||||||
|
.arg("CONNMARK")
|
||||||
|
.arg("--set-mark")
|
||||||
|
.arg(&table_str)
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure fwmark-based ip rule for this interface's table
|
||||||
|
let rules_output = String::from_utf8(
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("rule")
|
||||||
|
.arg("list")
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await?,
|
||||||
|
)?;
|
||||||
|
if !rules_output
|
||||||
|
.lines()
|
||||||
|
.any(|l| l.contains("fwmark") && l.contains(&format!("lookup {table_id}")))
|
||||||
|
{
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("rule")
|
||||||
|
.arg("add")
|
||||||
|
.arg("fwmark")
|
||||||
|
.arg(&table_str)
|
||||||
|
.arg("lookup")
|
||||||
|
.arg(&table_str)
|
||||||
|
.arg("priority")
|
||||||
|
.arg("50")
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn poll_ip_info(
|
||||||
|
ip4_proxy: &Ip4ConfigProxy<'_>,
|
||||||
|
ip6_proxy: &Ip6ConfigProxy<'_>,
|
||||||
|
dhcp4_proxy: &Option<Dhcp4ConfigProxy<'_>>,
|
||||||
|
policy_guard: &Option<PolicyRoutingCleanup>,
|
||||||
|
iface: &GatewayId,
|
||||||
|
prev_attempt: &mut Option<Instant>,
|
||||||
|
db: Option<&TypedPatchDb<Database>>,
|
||||||
|
write_to: &Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>,
|
||||||
|
device_type: Option<NetworkInterfaceType>,
|
||||||
|
name: &InternedString,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let addresses = ip4_proxy
|
||||||
|
.address_data()
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.chain(ip6_proxy.address_data().await?)
|
||||||
|
.collect_vec();
|
||||||
|
let lan_ip: OrdSet<IpAddr> = [
|
||||||
|
Some(ip4_proxy.gateway().await?)
|
||||||
|
.filter(|g| !g.is_empty())
|
||||||
|
.and_then(|g| g.parse::<IpAddr>().log_err()),
|
||||||
|
Some(ip6_proxy.gateway().await?)
|
||||||
|
.filter(|g| !g.is_empty())
|
||||||
|
.and_then(|g| g.parse::<IpAddr>().log_err()),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|a| a)
|
||||||
|
.collect();
|
||||||
|
let mut ntp_servers = OrdSet::new();
|
||||||
|
let mut dns_servers = OrdSet::new();
|
||||||
|
if let Some(dhcp4_proxy) = dhcp4_proxy {
|
||||||
|
let dhcp = dhcp4_proxy.options().await?;
|
||||||
|
if let Some(ntp) = dhcp.ntp_servers {
|
||||||
|
ntp_servers.extend(ntp.split_whitespace().map(InternedString::intern));
|
||||||
|
}
|
||||||
|
if let Some(dns) = dhcp.domain_name_servers {
|
||||||
|
dns_servers.extend(
|
||||||
|
dns.split_ascii_whitespace()
|
||||||
|
.filter_map(|s| s.parse::<IpAddr>().log_err())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let scope_id = if_nametoindex(iface.as_str()).with_kind(ErrorKind::Network)?;
|
||||||
|
let subnets: OrdSet<IpNet> = addresses.into_iter().map(IpNet::try_from).try_collect()?;
|
||||||
|
|
||||||
|
// Policy routing: ensure replies exit the same interface they arrived on,
|
||||||
|
// eliminating the need for MASQUERADE.
|
||||||
|
if let Some(guard) = policy_guard {
|
||||||
|
apply_policy_routing(guard, iface, &lan_ip).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ifconfig_url = if let Some(db) = db {
|
||||||
|
db.peek()
|
||||||
|
.await
|
||||||
|
.as_public()
|
||||||
|
.as_server_info()
|
||||||
|
.as_ifconfig_url()
|
||||||
|
.de()
|
||||||
|
.unwrap_or_else(|_| crate::db::model::public::default_ifconfig_url())
|
||||||
|
} else {
|
||||||
|
crate::db::model::public::default_ifconfig_url()
|
||||||
|
};
|
||||||
|
let wan_ip = if prev_attempt.map_or(true, |i| i.elapsed() > Duration::from_secs(300))
|
||||||
|
&& !subnets.is_empty()
|
||||||
|
&& !matches!(
|
||||||
|
device_type,
|
||||||
|
Some(NetworkInterfaceType::Bridge | NetworkInterfaceType::Loopback)
|
||||||
|
) {
|
||||||
|
*prev_attempt = Some(Instant::now());
|
||||||
|
match get_wan_ipv4(iface.as_str(), &ifconfig_url).await {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!(
|
||||||
|
"{}",
|
||||||
|
t!(
|
||||||
|
"net.gateway.failed-to-determine-wan-ip",
|
||||||
|
iface = iface.to_string(),
|
||||||
|
error = e.to_string()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let mut ip_info = IpInfo {
|
||||||
|
name: name.clone(),
|
||||||
|
scope_id,
|
||||||
|
device_type,
|
||||||
|
subnets,
|
||||||
|
lan_ip,
|
||||||
|
wan_ip,
|
||||||
|
ntp_servers,
|
||||||
|
dns_servers,
|
||||||
|
};
|
||||||
|
|
||||||
|
write_to.send_if_modified(|m: &mut OrdMap<GatewayId, NetworkInterfaceInfo>| {
|
||||||
|
let (name, secure, gateway_type, prev_wan_ip) =
|
||||||
|
m.get(iface).map_or((None, None, None, None), |i| {
|
||||||
|
(
|
||||||
|
i.name.clone(),
|
||||||
|
i.secure,
|
||||||
|
i.gateway_type,
|
||||||
|
i.ip_info.as_ref().and_then(|i| i.wan_ip),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
ip_info.wan_ip = ip_info.wan_ip.or(prev_wan_ip);
|
||||||
|
let ip_info = Arc::new(ip_info);
|
||||||
|
m.insert(
|
||||||
|
iface.clone(),
|
||||||
|
NetworkInterfaceInfo {
|
||||||
|
name,
|
||||||
|
secure,
|
||||||
|
ip_info: Some(ip_info.clone()),
|
||||||
|
gateway_type,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.filter(|old| &old.ip_info == &Some(ip_info))
|
||||||
|
.is_none()
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip(_connection, device_proxy, watch_activation))]
|
#[instrument(skip(_connection, device_proxy, watch_activation))]
|
||||||
async fn watch_activated(
|
async fn watch_activated(
|
||||||
_connection: &Connection,
|
_connection: &Connection,
|
||||||
@@ -1287,8 +1360,7 @@ impl NetworkInterfaceController {
|
|||||||
.as_network_mut()
|
.as_network_mut()
|
||||||
.as_gateways_mut()
|
.as_gateways_mut()
|
||||||
.ser(info)?;
|
.ser(info)?;
|
||||||
let hostname =
|
let hostname = crate::hostname::ServerHostname::load(db.as_public().as_server_info())?;
|
||||||
crate::hostname::Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
|
||||||
let ports = db.as_private().as_available_ports().de()?;
|
let ports = db.as_private().as_available_ports().de()?;
|
||||||
for host in all_hosts(db) {
|
for host in all_hosts(db) {
|
||||||
host?.update_addresses(&hostname, info, &ports)?;
|
host?.update_addresses(&hostname, info, &ports)?;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use ts_rs::TS;
|
|||||||
use crate::GatewayId;
|
use crate::GatewayId;
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::DatabaseModel;
|
use crate::db::model::DatabaseModel;
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::ServerHostname;
|
||||||
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::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -197,7 +197,7 @@ pub async fn add_public_domain<Kind: HostApiKind>(
|
|||||||
.as_public_domains_mut()
|
.as_public_domains_mut()
|
||||||
.insert(&fqdn, &PublicDomainConfig { acme, gateway })?;
|
.insert(&fqdn, &PublicDomainConfig { acme, gateway })?;
|
||||||
handle_duplicates(db)?;
|
handle_duplicates(db)?;
|
||||||
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
let hostname = ServerHostname::load(db.as_public().as_server_info())?;
|
||||||
let gateways = db.as_public().as_server_info().as_network().as_gateways().de()?;
|
let gateways = db.as_public().as_server_info().as_network().as_gateways().de()?;
|
||||||
let ports = db.as_private().as_available_ports().de()?;
|
let ports = db.as_private().as_available_ports().de()?;
|
||||||
Kind::host_for(&inheritance, db)?.update_addresses(&hostname, &gateways, &ports)
|
Kind::host_for(&inheritance, db)?.update_addresses(&hostname, &gateways, &ports)
|
||||||
@@ -230,8 +230,13 @@ pub async fn remove_public_domain<Kind: HostApiKind>(
|
|||||||
Kind::host_for(&inheritance, db)?
|
Kind::host_for(&inheritance, db)?
|
||||||
.as_public_domains_mut()
|
.as_public_domains_mut()
|
||||||
.remove(&fqdn)?;
|
.remove(&fqdn)?;
|
||||||
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
let hostname = ServerHostname::load(db.as_public().as_server_info())?;
|
||||||
let gateways = db.as_public().as_server_info().as_network().as_gateways().de()?;
|
let gateways = db
|
||||||
|
.as_public()
|
||||||
|
.as_server_info()
|
||||||
|
.as_network()
|
||||||
|
.as_gateways()
|
||||||
|
.de()?;
|
||||||
let ports = db.as_private().as_available_ports().de()?;
|
let ports = db.as_private().as_available_ports().de()?;
|
||||||
Kind::host_for(&inheritance, db)?.update_addresses(&hostname, &gateways, &ports)
|
Kind::host_for(&inheritance, db)?.update_addresses(&hostname, &gateways, &ports)
|
||||||
})
|
})
|
||||||
@@ -262,8 +267,13 @@ pub async fn add_private_domain<Kind: HostApiKind>(
|
|||||||
.upsert(&fqdn, || Ok(BTreeSet::new()))?
|
.upsert(&fqdn, || Ok(BTreeSet::new()))?
|
||||||
.mutate(|d| Ok(d.insert(gateway)))?;
|
.mutate(|d| Ok(d.insert(gateway)))?;
|
||||||
handle_duplicates(db)?;
|
handle_duplicates(db)?;
|
||||||
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
let hostname = ServerHostname::load(db.as_public().as_server_info())?;
|
||||||
let gateways = db.as_public().as_server_info().as_network().as_gateways().de()?;
|
let gateways = db
|
||||||
|
.as_public()
|
||||||
|
.as_server_info()
|
||||||
|
.as_network()
|
||||||
|
.as_gateways()
|
||||||
|
.de()?;
|
||||||
let ports = db.as_private().as_available_ports().de()?;
|
let ports = db.as_private().as_available_ports().de()?;
|
||||||
Kind::host_for(&inheritance, db)?.update_addresses(&hostname, &gateways, &ports)
|
Kind::host_for(&inheritance, db)?.update_addresses(&hostname, &gateways, &ports)
|
||||||
})
|
})
|
||||||
@@ -284,8 +294,13 @@ pub async fn remove_private_domain<Kind: HostApiKind>(
|
|||||||
Kind::host_for(&inheritance, db)?
|
Kind::host_for(&inheritance, db)?
|
||||||
.as_private_domains_mut()
|
.as_private_domains_mut()
|
||||||
.mutate(|d| Ok(d.remove(&domain)))?;
|
.mutate(|d| Ok(d.remove(&domain)))?;
|
||||||
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
let hostname = ServerHostname::load(db.as_public().as_server_info())?;
|
||||||
let gateways = db.as_public().as_server_info().as_network().as_gateways().de()?;
|
let gateways = db
|
||||||
|
.as_public()
|
||||||
|
.as_server_info()
|
||||||
|
.as_network()
|
||||||
|
.as_gateways()
|
||||||
|
.de()?;
|
||||||
let ports = db.as_private().as_available_ports().de()?;
|
let ports = db.as_private().as_available_ports().de()?;
|
||||||
Kind::host_for(&inheritance, db)?.update_addresses(&hostname, &gateways, &ports)
|
Kind::host_for(&inheritance, db)?.update_addresses(&hostname, &gateways, &ports)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use ts_rs::TS;
|
|||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::db::model::DatabaseModel;
|
use crate::db::model::DatabaseModel;
|
||||||
use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType};
|
use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType};
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::ServerHostname;
|
||||||
use crate::net::forward::AvailablePorts;
|
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, Bindings, binding};
|
use crate::net::host::binding::{BindInfo, BindOptions, Bindings, binding};
|
||||||
@@ -82,7 +82,7 @@ impl Host {
|
|||||||
impl Model<Host> {
|
impl Model<Host> {
|
||||||
pub fn update_addresses(
|
pub fn update_addresses(
|
||||||
&mut self,
|
&mut self,
|
||||||
mdns: &Hostname,
|
mdns: &ServerHostname,
|
||||||
gateways: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
gateways: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
available_ports: &AvailablePorts,
|
available_ports: &AvailablePorts,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use tokio_rustls::rustls::ClientConfig as TlsClientConfig;
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::ServerHostname;
|
||||||
use crate::net::dns::DnsController;
|
use crate::net::dns::DnsController;
|
||||||
use crate::net::forward::{
|
use crate::net::forward::{
|
||||||
ForwardRequirements, InterfacePortForwardController, START9_BRIDGE_IFACE, add_iptables_rule,
|
ForwardRequirements, InterfacePortForwardController, START9_BRIDGE_IFACE, add_iptables_rule,
|
||||||
@@ -651,7 +651,7 @@ impl NetService {
|
|||||||
.as_network()
|
.as_network()
|
||||||
.as_gateways()
|
.as_gateways()
|
||||||
.de()?;
|
.de()?;
|
||||||
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
let hostname = ServerHostname::load(db.as_public().as_server_info())?;
|
||||||
let mut ports = db.as_private().as_available_ports().de()?;
|
let mut ports = db.as_private().as_available_ports().de()?;
|
||||||
let host = host_for(db, pkg_id.as_ref(), &id)?;
|
let host = host_for(db, pkg_id.as_ref(), &id)?;
|
||||||
host.add_binding(&mut ports, internal_port, options)?;
|
host.add_binding(&mut ports, internal_port, options)?;
|
||||||
@@ -676,7 +676,7 @@ impl NetService {
|
|||||||
.as_network()
|
.as_network()
|
||||||
.as_gateways()
|
.as_gateways()
|
||||||
.de()?;
|
.de()?;
|
||||||
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
let hostname = ServerHostname::load(db.as_public().as_server_info())?;
|
||||||
let ports = db.as_private().as_available_ports().de()?;
|
let ports = db.as_private().as_available_ports().de()?;
|
||||||
if let Some(ref pkg_id) = pkg_id {
|
if let Some(ref pkg_id) = pkg_id {
|
||||||
for (host_id, host) in db
|
for (host_id, host) in db
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ use crate::SOURCE_DATE;
|
|||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
use crate::db::{DbAccess, DbAccessMut};
|
use crate::db::{DbAccess, DbAccessMut};
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::ServerHostname;
|
||||||
use crate::init::check_time_is_synchronized;
|
use crate::init::check_time_is_synchronized;
|
||||||
use crate::net::gateway::GatewayInfo;
|
use crate::net::gateway::GatewayInfo;
|
||||||
use crate::net::tls::TlsHandler;
|
use crate::net::tls::TlsHandler;
|
||||||
@@ -283,7 +283,7 @@ pub fn gen_nistp256() -> Result<PKey<Private>, Error> {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub fn make_root_cert(
|
pub fn make_root_cert(
|
||||||
root_key: &PKey<Private>,
|
root_key: &PKey<Private>,
|
||||||
hostname: &Hostname,
|
hostname: &ServerHostname,
|
||||||
start_time: SystemTime,
|
start_time: SystemTime,
|
||||||
) -> Result<X509, Error> {
|
) -> Result<X509, Error> {
|
||||||
let mut builder = X509Builder::new()?;
|
let mut builder = X509Builder::new()?;
|
||||||
@@ -300,7 +300,8 @@ pub fn make_root_cert(
|
|||||||
builder.set_serial_number(&*rand_serial()?)?;
|
builder.set_serial_number(&*rand_serial()?)?;
|
||||||
|
|
||||||
let mut subject_name_builder = X509NameBuilder::new()?;
|
let mut subject_name_builder = X509NameBuilder::new()?;
|
||||||
subject_name_builder.append_entry_by_text("CN", &format!("{} Local Root CA", &*hostname.0))?;
|
subject_name_builder
|
||||||
|
.append_entry_by_text("CN", &format!("{} Local Root CA", hostname.as_ref()))?;
|
||||||
subject_name_builder.append_entry_by_text("O", "Start9")?;
|
subject_name_builder.append_entry_by_text("O", "Start9")?;
|
||||||
subject_name_builder.append_entry_by_text("OU", "StartOS")?;
|
subject_name_builder.append_entry_by_text("OU", "StartOS")?;
|
||||||
let subject_name = subject_name_builder.build();
|
let subject_name = subject_name_builder.build();
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ use tokio_util::io::ReaderStream;
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::context::{DiagnosticContext, InitContext, RpcContext, SetupContext};
|
use crate::context::{DiagnosticContext, InitContext, RpcContext, SetupContext};
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::ServerHostname;
|
||||||
use crate::middleware::auth::Auth;
|
use crate::middleware::auth::Auth;
|
||||||
use crate::middleware::auth::session::ValidSessionToken;
|
use crate::middleware::auth::session::ValidSessionToken;
|
||||||
use crate::middleware::cors::Cors;
|
use crate::middleware::cors::Cors;
|
||||||
@@ -105,8 +105,9 @@ impl UiContext for RpcContext {
|
|||||||
get(move || {
|
get(move || {
|
||||||
let ctx = self.clone();
|
let ctx = self.clone();
|
||||||
async move {
|
async move {
|
||||||
ctx.account
|
ctx.account.peek(|account| {
|
||||||
.peek(|account| cert_send(&account.root_ca_cert, &account.hostname))
|
cert_send(&account.root_ca_cert, &account.hostname.hostname)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -419,7 +420,7 @@ pub fn bad_request() -> Response {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cert_send(cert: &X509, hostname: &Hostname) -> Result<Response, Error> {
|
fn cert_send(cert: &X509, hostname: &ServerHostname) -> Result<Response, Error> {
|
||||||
let pem = cert.to_pem()?;
|
let pem = cert.to_pem()?;
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(StatusCode::OK)
|
.status(StatusCode::OK)
|
||||||
@@ -435,7 +436,7 @@ fn cert_send(cert: &X509, hostname: &Hostname) -> Result<Response, Error> {
|
|||||||
.header(http::header::CONTENT_LENGTH, pem.len())
|
.header(http::header::CONTENT_LENGTH, pem.len())
|
||||||
.header(
|
.header(
|
||||||
http::header::CONTENT_DISPOSITION,
|
http::header::CONTENT_DISPOSITION,
|
||||||
format!("attachment; filename={}.crt", &hostname.0),
|
format!("attachment; filename={}.crt", hostname.as_ref()),
|
||||||
)
|
)
|
||||||
.body(Body::from(pem))
|
.body(Body::from(pem))
|
||||||
.with_kind(ErrorKind::Network)
|
.with_kind(ErrorKind::Network)
|
||||||
|
|||||||
@@ -171,20 +171,14 @@ where
|
|||||||
let (metadata, stream) = ready!(self.accept.poll_accept(cx)?);
|
let (metadata, stream) = ready!(self.accept.poll_accept(cx)?);
|
||||||
let mut tls_handler = self.tls_handler.clone();
|
let mut tls_handler = self.tls_handler.clone();
|
||||||
let mut fut = async move {
|
let mut fut = async move {
|
||||||
let res = match tokio::time::timeout(
|
let res = match tokio::time::timeout(Duration::from_secs(15), async {
|
||||||
Duration::from_secs(15),
|
let mut acceptor =
|
||||||
async {
|
LazyConfigAcceptor::new(Acceptor::default(), BackTrackingIO::new(stream));
|
||||||
let mut acceptor = LazyConfigAcceptor::new(
|
let mut mid: tokio_rustls::StartHandshake<BackTrackingIO<AcceptStream>> =
|
||||||
Acceptor::default(),
|
match (&mut acceptor).await {
|
||||||
BackTrackingIO::new(stream),
|
|
||||||
);
|
|
||||||
let mut mid: tokio_rustls::StartHandshake<
|
|
||||||
BackTrackingIO<AcceptStream>,
|
|
||||||
> = match (&mut acceptor).await {
|
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let mut stream =
|
let mut stream = acceptor.take_io().or_not_found("acceptor io")?;
|
||||||
acceptor.take_io().or_not_found("acceptor io")?;
|
|
||||||
let (_, buf) = stream.rewind();
|
let (_, buf) = stream.rewind();
|
||||||
if std::str::from_utf8(buf)
|
if std::str::from_utf8(buf)
|
||||||
.ok()
|
.ok()
|
||||||
@@ -208,42 +202,39 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let hello = mid.client_hello();
|
let hello = mid.client_hello();
|
||||||
if let Some(cfg) = tls_handler.get_config(&hello, &metadata).await {
|
if let Some(cfg) = tls_handler.get_config(&hello, &metadata).await {
|
||||||
let buffered = mid.io.stop_buffering();
|
let buffered = mid.io.stop_buffering();
|
||||||
mid.io
|
mid.io
|
||||||
.write_all(&buffered)
|
.write_all(&buffered)
|
||||||
.await
|
.await
|
||||||
.with_kind(ErrorKind::Network)?;
|
.with_kind(ErrorKind::Network)?;
|
||||||
return Ok(match mid.into_stream(Arc::new(cfg)).await {
|
return Ok(match mid.into_stream(Arc::new(cfg)).await {
|
||||||
Ok(stream) => {
|
Ok(stream) => {
|
||||||
let s = stream.get_ref().1;
|
let s = stream.get_ref().1;
|
||||||
Some((
|
Some((
|
||||||
TlsMetadata {
|
TlsMetadata {
|
||||||
inner: metadata,
|
inner: metadata,
|
||||||
tls_info: TlsHandshakeInfo {
|
tls_info: TlsHandshakeInfo {
|
||||||
sni: s
|
sni: s.server_name().map(InternedString::intern),
|
||||||
.server_name()
|
alpn: s
|
||||||
.map(InternedString::intern),
|
.alpn_protocol()
|
||||||
alpn: s
|
.map(|a| MaybeUtf8String(a.to_vec())),
|
||||||
.alpn_protocol()
|
|
||||||
.map(|a| MaybeUtf8String(a.to_vec())),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Box::pin(stream) as AcceptStream,
|
},
|
||||||
))
|
Box::pin(stream) as AcceptStream,
|
||||||
}
|
))
|
||||||
Err(e) => {
|
}
|
||||||
tracing::trace!("Error completing TLS handshake: {e}");
|
Err(e) => {
|
||||||
tracing::trace!("{e:?}");
|
tracing::trace!("Error completing TLS handshake: {e}");
|
||||||
None
|
tracing::trace!("{e:?}");
|
||||||
}
|
None
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
},
|
})
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
|
|||||||
@@ -175,8 +175,13 @@ pub async fn remove_tunnel(
|
|||||||
|
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
let hostname = crate::hostname::Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
let hostname = crate::hostname::ServerHostname::load(db.as_public().as_server_info())?;
|
||||||
let gateways = db.as_public().as_server_info().as_network().as_gateways().de()?;
|
let gateways = db
|
||||||
|
.as_public()
|
||||||
|
.as_server_info()
|
||||||
|
.as_network()
|
||||||
|
.as_gateways()
|
||||||
|
.de()?;
|
||||||
let ports = db.as_private().as_available_ports().de()?;
|
let ports = db.as_private().as_available_ports().de()?;
|
||||||
for host in all_hosts(db) {
|
for host in all_hosts(db) {
|
||||||
let host = host?;
|
let host = host?;
|
||||||
@@ -194,8 +199,13 @@ pub async fn remove_tunnel(
|
|||||||
|
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
let hostname = crate::hostname::Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
let hostname = crate::hostname::ServerHostname::load(db.as_public().as_server_info())?;
|
||||||
let gateways = db.as_public().as_server_info().as_network().as_gateways().de()?;
|
let gateways = db
|
||||||
|
.as_public()
|
||||||
|
.as_server_info()
|
||||||
|
.as_network()
|
||||||
|
.as_gateways()
|
||||||
|
.de()?;
|
||||||
let ports = db.as_private().as_available_ports().de()?;
|
let ports = db.as_private().as_available_ports().de()?;
|
||||||
for host in all_hosts(db) {
|
for host in all_hosts(db) {
|
||||||
let host = host?;
|
let host = host?;
|
||||||
|
|||||||
@@ -161,7 +161,10 @@ pub struct WifiAddParams {
|
|||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn add(ctx: RpcContext, WifiAddParams { ssid, password }: WifiAddParams) -> Result<(), Error> {
|
pub async fn add(
|
||||||
|
ctx: RpcContext,
|
||||||
|
WifiAddParams { ssid, password }: WifiAddParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let wifi_manager = ctx.wifi_manager.clone();
|
let wifi_manager = ctx.wifi_manager.clone();
|
||||||
if !ssid.is_ascii() {
|
if !ssid.is_ascii() {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
@@ -240,7 +243,10 @@ pub struct WifiSsidParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn connect(ctx: RpcContext, WifiSsidParams { ssid }: WifiSsidParams) -> Result<(), Error> {
|
pub async fn connect(
|
||||||
|
ctx: RpcContext,
|
||||||
|
WifiSsidParams { ssid }: WifiSsidParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let wifi_manager = ctx.wifi_manager.clone();
|
let wifi_manager = ctx.wifi_manager.clone();
|
||||||
if !ssid.is_ascii() {
|
if !ssid.is_ascii() {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
|
|||||||
@@ -579,9 +579,8 @@ fn check_matching_info_short() {
|
|||||||
use crate::s9pk::manifest::{Alerts, Description};
|
use crate::s9pk::manifest::{Alerts, Description};
|
||||||
use crate::util::DataUrl;
|
use crate::util::DataUrl;
|
||||||
|
|
||||||
let lang_map = |s: &str| {
|
let lang_map =
|
||||||
LocaleString::LanguageMap([("en".into(), s.into())].into_iter().collect())
|
|s: &str| LocaleString::LanguageMap([("en".into(), s.into())].into_iter().collect());
|
||||||
};
|
|
||||||
|
|
||||||
let info = PackageVersionInfo {
|
let info = PackageVersionInfo {
|
||||||
metadata: PackageMetadata {
|
metadata: PackageMetadata {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use ts_rs::TS;
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::PackageId;
|
use crate::PackageId;
|
||||||
use crate::service::effects::plugin::PluginId;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::asset::RegistryAsset;
|
use crate::registry::asset::RegistryAsset;
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
@@ -22,6 +21,7 @@ use crate::s9pk::manifest::{
|
|||||||
Alerts, Description, HardwareRequirements, LocaleString, current_version,
|
Alerts, Description, HardwareRequirements, LocaleString, current_version,
|
||||||
};
|
};
|
||||||
use crate::s9pk::merkle_archive::source::FileSource;
|
use crate::s9pk::merkle_archive::source::FileSource;
|
||||||
|
use crate::service::effects::plugin::PluginId;
|
||||||
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||||
use crate::sign::{AnySignature, AnyVerifyingKey};
|
use crate::sign::{AnySignature, AnyVerifyingKey};
|
||||||
use crate::util::{DataUrl, VersionString};
|
use crate::util::{DataUrl, VersionString};
|
||||||
|
|||||||
@@ -72,7 +72,13 @@ impl Service {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
self.actor
|
self.actor
|
||||||
.send(id, GetActionInput { id: action_id, prefill })
|
.send(
|
||||||
|
id,
|
||||||
|
GetActionInput {
|
||||||
|
id: action_id,
|
||||||
|
prefill,
|
||||||
|
},
|
||||||
|
)
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,7 +151,9 @@ async fn get_action_input(
|
|||||||
.get_action_input(procedure_id, action_id, prefill)
|
.get_action_input(procedure_id, action_id, prefill)
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
context.get_action_input(procedure_id, action_id, prefill).await
|
context
|
||||||
|
.get_action_input(procedure_id, action_id, prefill)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -178,10 +178,7 @@ pub fn handler<C: Context>() -> ParentHandler<C> {
|
|||||||
ParentHandler::<C>::new().subcommand(
|
ParentHandler::<C>::new().subcommand(
|
||||||
"url",
|
"url",
|
||||||
ParentHandler::<C>::new()
|
ParentHandler::<C>::new()
|
||||||
.subcommand(
|
.subcommand("register", from_fn_async(net::plugin::register).no_cli())
|
||||||
"register",
|
|
||||||
from_fn_async(net::plugin::register).no_cli(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"export-url",
|
"export-url",
|
||||||
from_fn_async(net::plugin::export_url).no_cli(),
|
from_fn_async(net::plugin::export_url).no_cli(),
|
||||||
|
|||||||
@@ -43,9 +43,8 @@ pub async fn cleanup(ctx: &RpcContext, id: &PackageId, soft: bool) -> Result<(),
|
|||||||
for host in all_hosts(d) {
|
for host in all_hosts(d) {
|
||||||
let host = host?;
|
let host = host?;
|
||||||
for (_, bind) in host.as_bindings_mut().as_entries_mut()? {
|
for (_, bind) in host.as_bindings_mut().as_entries_mut()? {
|
||||||
bind.as_addresses_mut()
|
bind.as_addresses_mut().as_available_mut().mutate(
|
||||||
.as_available_mut()
|
|available: &mut BTreeSet<HostnameInfo>| {
|
||||||
.mutate(|available: &mut BTreeSet<HostnameInfo>| {
|
|
||||||
available.retain(|h| {
|
available.retain(|h| {
|
||||||
!matches!(
|
!matches!(
|
||||||
&h.metadata,
|
&h.metadata,
|
||||||
@@ -54,7 +53,8 @@ pub async fn cleanup(ctx: &RpcContext, id: &PackageId, soft: bool) -> Result<(),
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
},
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Some(pde))
|
Ok(Some(pde))
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ use crate::disk::mount::filesystem::ReadWrite;
|
|||||||
use crate::disk::mount::filesystem::cifs::Cifs;
|
use crate::disk::mount::filesystem::cifs::Cifs;
|
||||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||||
use crate::disk::util::{DiskInfo, StartOsRecoveryInfo, pvscan, recovery_info};
|
use crate::disk::util::{DiskInfo, StartOsRecoveryInfo, pvscan, recovery_info};
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::ServerHostnameInfo;
|
||||||
use crate::init::{InitPhases, InitResult, init};
|
use crate::init::{InitPhases, InitResult, init};
|
||||||
use crate::net::ssl::root_ca_start_time;
|
use crate::net::ssl::root_ca_start_time;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -116,7 +116,7 @@ async fn setup_init(
|
|||||||
ctx: &SetupContext,
|
ctx: &SetupContext,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
kiosk: Option<bool>,
|
kiosk: Option<bool>,
|
||||||
hostname: Option<InternedString>,
|
hostname: Option<ServerHostnameInfo>,
|
||||||
init_phases: InitPhases,
|
init_phases: InitPhases,
|
||||||
) -> Result<(AccountInfo, InitResult), Error> {
|
) -> Result<(AccountInfo, InitResult), Error> {
|
||||||
let init_result = init(&ctx.webserver, &ctx.config.peek(|c| c.clone()), init_phases).await?;
|
let init_result = init(&ctx.webserver, &ctx.config.peek(|c| c.clone()), init_phases).await?;
|
||||||
@@ -132,7 +132,7 @@ async fn setup_init(
|
|||||||
account.set_password(password)?;
|
account.set_password(password)?;
|
||||||
}
|
}
|
||||||
if let Some(hostname) = hostname {
|
if let Some(hostname) = hostname {
|
||||||
account.hostname = Hostname::validate(hostname)?;
|
account.hostname = hostname;
|
||||||
}
|
}
|
||||||
account.save(m)?;
|
account.save(m)?;
|
||||||
let info = m.as_public_mut().as_server_info_mut();
|
let info = m.as_public_mut().as_server_info_mut();
|
||||||
@@ -176,6 +176,7 @@ pub struct AttachParams {
|
|||||||
pub guid: InternedString,
|
pub guid: InternedString,
|
||||||
#[ts(optional)]
|
#[ts(optional)]
|
||||||
pub kiosk: Option<bool>,
|
pub kiosk: Option<bool>,
|
||||||
|
pub name: Option<InternedString>,
|
||||||
pub hostname: Option<InternedString>,
|
pub hostname: Option<InternedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +187,7 @@ pub async fn attach(
|
|||||||
password,
|
password,
|
||||||
guid: disk_guid,
|
guid: disk_guid,
|
||||||
kiosk,
|
kiosk,
|
||||||
|
name,
|
||||||
hostname,
|
hostname,
|
||||||
}: AttachParams,
|
}: AttachParams,
|
||||||
) -> Result<SetupProgress, Error> {
|
) -> Result<SetupProgress, Error> {
|
||||||
@@ -240,14 +242,10 @@ pub async fn attach(
|
|||||||
}
|
}
|
||||||
disk_phase.complete();
|
disk_phase.complete();
|
||||||
|
|
||||||
let (account, net_ctrl) = setup_init(
|
let hostname = ServerHostnameInfo::new_opt(name, hostname)?;
|
||||||
&setup_ctx,
|
|
||||||
password,
|
let (account, net_ctrl) =
|
||||||
kiosk,
|
setup_init(&setup_ctx, password, kiosk, hostname, init_phases).await?;
|
||||||
hostname.filter(|h| !h.is_empty()),
|
|
||||||
init_phases,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let rpc_ctx = RpcContext::init(
|
let rpc_ctx = RpcContext::init(
|
||||||
&setup_ctx.webserver,
|
&setup_ctx.webserver,
|
||||||
@@ -260,7 +258,7 @@ pub async fn attach(
|
|||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
SetupResult {
|
SetupResult {
|
||||||
hostname: account.hostname,
|
hostname: account.hostname.hostname,
|
||||||
root_ca: Pem(account.root_ca_cert),
|
root_ca: Pem(account.root_ca_cert),
|
||||||
needs_restart: setup_ctx.install_rootfs.peek(|a| a.is_some()),
|
needs_restart: setup_ctx.install_rootfs.peek(|a| a.is_some()),
|
||||||
},
|
},
|
||||||
@@ -420,6 +418,7 @@ pub struct SetupExecuteParams {
|
|||||||
recovery_source: Option<RecoverySource<EncryptedWire>>,
|
recovery_source: Option<RecoverySource<EncryptedWire>>,
|
||||||
#[ts(optional)]
|
#[ts(optional)]
|
||||||
kiosk: Option<bool>,
|
kiosk: Option<bool>,
|
||||||
|
name: Option<InternedString>,
|
||||||
hostname: Option<InternedString>,
|
hostname: Option<InternedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,6 +430,7 @@ pub async fn execute(
|
|||||||
password,
|
password,
|
||||||
recovery_source,
|
recovery_source,
|
||||||
kiosk,
|
kiosk,
|
||||||
|
name,
|
||||||
hostname,
|
hostname,
|
||||||
}: SetupExecuteParams,
|
}: SetupExecuteParams,
|
||||||
) -> Result<SetupProgress, Error> {
|
) -> Result<SetupProgress, Error> {
|
||||||
@@ -462,17 +462,10 @@ pub async fn execute(
|
|||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let hostname = ServerHostnameInfo::new_opt(name, hostname)?;
|
||||||
|
|
||||||
let setup_ctx = ctx.clone();
|
let setup_ctx = ctx.clone();
|
||||||
ctx.run_setup(move || {
|
ctx.run_setup(move || execute_inner(setup_ctx, guid, password, recovery, kiosk, hostname))?;
|
||||||
execute_inner(
|
|
||||||
setup_ctx,
|
|
||||||
guid,
|
|
||||||
password,
|
|
||||||
recovery,
|
|
||||||
kiosk,
|
|
||||||
hostname.filter(|h| !h.is_empty()),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(ctx.progress().await)
|
Ok(ctx.progress().await)
|
||||||
}
|
}
|
||||||
@@ -487,7 +480,7 @@ pub async fn complete(ctx: SetupContext) -> Result<SetupResult, Error> {
|
|||||||
guid_file.sync_all().await?;
|
guid_file.sync_all().await?;
|
||||||
Command::new("systemd-firstboot")
|
Command::new("systemd-firstboot")
|
||||||
.arg("--root=/media/startos/config/overlay/")
|
.arg("--root=/media/startos/config/overlay/")
|
||||||
.arg(format!("--hostname={}", res.hostname.0))
|
.arg(format!("--hostname={}", res.hostname.as_ref()))
|
||||||
.invoke(ErrorKind::ParseSysInfo)
|
.invoke(ErrorKind::ParseSysInfo)
|
||||||
.await?;
|
.await?;
|
||||||
Command::new("sync").invoke(ErrorKind::Filesystem).await?;
|
Command::new("sync").invoke(ErrorKind::Filesystem).await?;
|
||||||
@@ -561,7 +554,7 @@ pub async fn execute_inner(
|
|||||||
password: String,
|
password: String,
|
||||||
recovery_source: Option<RecoverySource<String>>,
|
recovery_source: Option<RecoverySource<String>>,
|
||||||
kiosk: Option<bool>,
|
kiosk: Option<bool>,
|
||||||
hostname: Option<InternedString>,
|
hostname: Option<ServerHostnameInfo>,
|
||||||
) -> Result<(SetupResult, RpcContext), Error> {
|
) -> Result<(SetupResult, RpcContext), Error> {
|
||||||
let progress = &ctx.progress;
|
let progress = &ctx.progress;
|
||||||
let restore_phase = match recovery_source.as_ref() {
|
let restore_phase = match recovery_source.as_ref() {
|
||||||
@@ -619,7 +612,7 @@ async fn fresh_setup(
|
|||||||
guid: InternedString,
|
guid: InternedString,
|
||||||
password: &str,
|
password: &str,
|
||||||
kiosk: Option<bool>,
|
kiosk: Option<bool>,
|
||||||
hostname: Option<InternedString>,
|
hostname: Option<ServerHostnameInfo>,
|
||||||
SetupExecuteProgress {
|
SetupExecuteProgress {
|
||||||
init_phases,
|
init_phases,
|
||||||
rpc_ctx_phases,
|
rpc_ctx_phases,
|
||||||
@@ -663,7 +656,7 @@ async fn fresh_setup(
|
|||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
SetupResult {
|
SetupResult {
|
||||||
hostname: account.hostname,
|
hostname: account.hostname.hostname,
|
||||||
root_ca: Pem(account.root_ca_cert),
|
root_ca: Pem(account.root_ca_cert),
|
||||||
needs_restart: ctx.install_rootfs.peek(|a| a.is_some()),
|
needs_restart: ctx.install_rootfs.peek(|a| a.is_some()),
|
||||||
},
|
},
|
||||||
@@ -680,7 +673,7 @@ async fn recover(
|
|||||||
server_id: String,
|
server_id: String,
|
||||||
recovery_password: String,
|
recovery_password: String,
|
||||||
kiosk: Option<bool>,
|
kiosk: Option<bool>,
|
||||||
hostname: Option<InternedString>,
|
hostname: Option<ServerHostnameInfo>,
|
||||||
progress: SetupExecuteProgress,
|
progress: SetupExecuteProgress,
|
||||||
) -> Result<(SetupResult, RpcContext), Error> {
|
) -> Result<(SetupResult, RpcContext), Error> {
|
||||||
let recovery_source = TmpMountGuard::mount(&recovery_source, ReadWrite).await?;
|
let recovery_source = TmpMountGuard::mount(&recovery_source, ReadWrite).await?;
|
||||||
@@ -705,7 +698,7 @@ async fn migrate(
|
|||||||
old_guid: &str,
|
old_guid: &str,
|
||||||
password: String,
|
password: String,
|
||||||
kiosk: Option<bool>,
|
kiosk: Option<bool>,
|
||||||
hostname: Option<InternedString>,
|
hostname: Option<ServerHostnameInfo>,
|
||||||
SetupExecuteProgress {
|
SetupExecuteProgress {
|
||||||
init_phases,
|
init_phases,
|
||||||
restore_phase,
|
restore_phase,
|
||||||
@@ -798,7 +791,7 @@ async fn migrate(
|
|||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
SetupResult {
|
SetupResult {
|
||||||
hostname: account.hostname,
|
hostname: account.hostname.hostname,
|
||||||
root_ca: Pem(account.root_ca_cert),
|
root_ca: Pem(account.root_ca_cert),
|
||||||
needs_restart: ctx.install_rootfs.peek(|a| a.is_some()),
|
needs_restart: ctx.install_rootfs.peek(|a| a.is_some()),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use tracing::instrument;
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::ServerHostname;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::io::create_file;
|
use crate::util::io::create_file;
|
||||||
use crate::util::serde::{HandlerExtSerde, Pem, WithIoFormat, display_serializable};
|
use crate::util::serde::{HandlerExtSerde, Pem, WithIoFormat, display_serializable};
|
||||||
@@ -125,7 +125,10 @@ pub struct SshAddParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn add(ctx: RpcContext, SshAddParams { key }: SshAddParams) -> Result<SshKeyResponse, Error> {
|
pub async fn add(
|
||||||
|
ctx: RpcContext,
|
||||||
|
SshAddParams { key }: SshAddParams,
|
||||||
|
) -> Result<SshKeyResponse, Error> {
|
||||||
let mut key = WithTimeData::new(key);
|
let mut key = WithTimeData::new(key);
|
||||||
let fingerprint = InternedString::intern(key.0.fingerprint_md5());
|
let fingerprint = InternedString::intern(key.0.fingerprint_md5());
|
||||||
let (keys, res) = ctx
|
let (keys, res) = ctx
|
||||||
@@ -238,7 +241,7 @@ pub async fn list(ctx: RpcContext) -> Result<Vec<SshKeyResponse>, Error> {
|
|||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn sync_keys<P: AsRef<Path>>(
|
pub async fn sync_keys<P: AsRef<Path>>(
|
||||||
hostname: &Hostname,
|
hostname: &ServerHostname,
|
||||||
privkey: &Pem<ssh_key::PrivateKey>,
|
privkey: &Pem<ssh_key::PrivateKey>,
|
||||||
pubkeys: &SshKeys,
|
pubkeys: &SshKeys,
|
||||||
ssh_dir: P,
|
ssh_dir: P,
|
||||||
@@ -284,8 +287,8 @@ pub async fn sync_keys<P: AsRef<Path>>(
|
|||||||
.to_openssh()
|
.to_openssh()
|
||||||
.with_kind(ErrorKind::OpenSsh)?
|
.with_kind(ErrorKind::OpenSsh)?
|
||||||
+ " start9@"
|
+ " start9@"
|
||||||
+ &*hostname.0)
|
+ hostname.as_ref())
|
||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
f.write_all(b"\n").await?;
|
f.write_all(b"\n").await?;
|
||||||
|
|||||||
@@ -474,7 +474,10 @@ pub async fn add_forward(
|
|||||||
})
|
})
|
||||||
.map(|s| s.prefix_len())
|
.map(|s| s.prefix_len())
|
||||||
.unwrap_or(32);
|
.unwrap_or(32);
|
||||||
let rc = ctx.forward.add_forward(source, target, prefix, None).await?;
|
let rc = ctx
|
||||||
|
.forward
|
||||||
|
.add_forward(source, target, prefix, None)
|
||||||
|
.await?;
|
||||||
ctx.active_forwards.mutate(|m| {
|
ctx.active_forwards.mutate(|m| {
|
||||||
m.insert(source, rc);
|
m.insert(source, rc);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use tokio_rustls::rustls::server::ClientHello;
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::ServerHostname;
|
||||||
use crate::net::ssl::{SANInfo, root_ca_start_time};
|
use crate::net::ssl::{SANInfo, root_ca_start_time};
|
||||||
use crate::net::tls::TlsHandler;
|
use crate::net::tls::TlsHandler;
|
||||||
use crate::net::web_server::Accept;
|
use crate::net::web_server::Accept;
|
||||||
@@ -292,7 +292,7 @@ pub async fn generate_certificate(
|
|||||||
let root_key = crate::net::ssl::gen_nistp256()?;
|
let root_key = crate::net::ssl::gen_nistp256()?;
|
||||||
let root_cert = crate::net::ssl::make_root_cert(
|
let root_cert = crate::net::ssl::make_root_cert(
|
||||||
&root_key,
|
&root_key,
|
||||||
&Hostname("start-tunnel".into()),
|
&ServerHostname::new("start-tunnel".into())?,
|
||||||
root_ca_start_time().await,
|
root_ca_start_time().await,
|
||||||
)?;
|
)?;
|
||||||
let int_key = crate::net::ssl::gen_nistp256()?;
|
let int_key = crate::net::ssl::gen_nistp256()?;
|
||||||
@@ -523,27 +523,27 @@ pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
|
|||||||
println!(concat!(
|
println!(concat!(
|
||||||
"To access your Web URL securely, trust your Root CA (displayed above) on your client device(s):\n",
|
"To access your Web URL securely, trust your Root CA (displayed above) on your client device(s):\n",
|
||||||
" - MacOS\n",
|
" - MacOS\n",
|
||||||
" 1. Open the Terminal app\n",
|
" 1. Open the Terminal app\n",
|
||||||
" 2. Paste the following command (**DO NOT** click Return): pbcopy < ~/Desktop/ca.crt\n",
|
" 2. Paste the following command (**DO NOT** click Return): pbcopy < ~/Desktop/ca.crt\n",
|
||||||
" 3. Copy your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
|
" 3. Copy your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
|
||||||
" 4. Back in Terminal, click Return. ca.crt is saved to your Desktop\n",
|
" 4. Back in Terminal, click Return. ca.crt is saved to your Desktop\n",
|
||||||
" 5. Complete by trusting your Root CA: https://docs.start9.com/device-guides/mac/ca.html\n",
|
" 5. Complete by trusting your Root CA: https://docs.start9.com/device-guides/mac/ca.html\n",
|
||||||
" - Linux\n",
|
" - Linux\n",
|
||||||
" 1. Open gedit, nano, or any editor\n",
|
" 1. Open gedit, nano, or any editor\n",
|
||||||
" 2. Copy/paste your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
|
" 2. Copy/paste your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
|
||||||
" 3. Name the file ca.crt and save as plaintext\n",
|
" 3. Name the file ca.crt and save as plaintext\n",
|
||||||
" 4. Complete by trusting your Root CA: https://docs.start9.com/device-guides/linux/ca.html\n",
|
" 4. Complete by trusting your Root CA: https://docs.start9.com/device-guides/linux/ca.html\n",
|
||||||
" - Windows\n",
|
" - Windows\n",
|
||||||
" 1. Open the Notepad app\n",
|
" 1. Open the Notepad app\n",
|
||||||
" 2. Copy/paste your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
|
" 2. Copy/paste your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
|
||||||
" 3. Name the file ca.crt and save as plaintext\n",
|
" 3. Name the file ca.crt and save as plaintext\n",
|
||||||
" 4. Complete by trusting your Root CA: https://docs.start9.com/device-guides/windows/ca.html\n",
|
" 4. Complete by trusting your Root CA: https://docs.start9.com/device-guides/windows/ca.html\n",
|
||||||
" - Android/Graphene\n",
|
" - Android/Graphene\n",
|
||||||
" 1. Send the ca.crt file (created above) to yourself\n",
|
" 1. Send the ca.crt file (created above) to yourself\n",
|
||||||
" 2. Complete by trusting your Root CA: https://docs.start9.com/device-guides/android/ca.html\n",
|
" 2. Complete by trusting your Root CA: https://docs.start9.com/device-guides/android/ca.html\n",
|
||||||
" - iOS\n",
|
" - iOS\n",
|
||||||
" 1. Send the ca.crt file (created above) to yourself\n",
|
" 1. Send the ca.crt file (created above) to yourself\n",
|
||||||
" 2. Complete by trusting your Root CA: https://docs.start9.com/device-guides/ios/ca.html\n",
|
" 2. Complete by trusting your Root CA: https://docs.start9.com/device-guides/ios/ca.html\n",
|
||||||
));
|
));
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ use crate::backup::target::cifs::CifsTargets;
|
|||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::disk::mount::filesystem::cifs::Cifs;
|
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::{ServerHostname, ServerHostnameInfo};
|
||||||
use crate::net::forward::AvailablePorts;
|
use crate::net::forward::AvailablePorts;
|
||||||
use crate::net::keys::KeyStore;
|
use crate::net::keys::KeyStore;
|
||||||
use crate::notifications::Notifications;
|
use crate::notifications::Notifications;
|
||||||
@@ -166,11 +166,7 @@ impl VersionT for Version {
|
|||||||
|
|
||||||
Ok((account, ssh_keys, cifs))
|
Ok((account, ssh_keys, cifs))
|
||||||
}
|
}
|
||||||
fn up(
|
fn up(self, db: &mut Value, (account, ssh_keys, cifs): Self::PreUpRes) -> Result<Value, Error> {
|
||||||
self,
|
|
||||||
db: &mut Value,
|
|
||||||
(account, ssh_keys, cifs): Self::PreUpRes,
|
|
||||||
) -> Result<Value, Error> {
|
|
||||||
let prev_package_data = db["package-data"].clone();
|
let prev_package_data = db["package-data"].clone();
|
||||||
|
|
||||||
let wifi = json!({
|
let wifi = json!({
|
||||||
@@ -435,12 +431,12 @@ async fn previous_account_info(pg: &sqlx::Pool<sqlx::Postgres>) -> Result<Accoun
|
|||||||
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"))?,
|
||||||
hostname: Hostname(
|
hostname: ServerHostnameInfo::from_hostname(ServerHostname::new(
|
||||||
account_query
|
account_query
|
||||||
.try_get::<String, _>("hostname")
|
.try_get::<String, _>("hostname")
|
||||||
.with_ctx(|_| (ErrorKind::Database, "hostname"))?
|
.with_ctx(|_| (ErrorKind::Database, "hostname"))?
|
||||||
.into(),
|
.into(),
|
||||||
),
|
)?),
|
||||||
root_ca_key: PKey::private_key_from_pem(
|
root_ca_key: PKey::private_key_from_pem(
|
||||||
&account_query
|
&account_query
|
||||||
.try_get::<String, _>("root_ca_key_pem")
|
.try_get::<String, _>("root_ca_key_pem")
|
||||||
@@ -502,4 +498,3 @@ async fn previous_ssh_keys(pg: &sqlx::Pool<sqlx::Postgres>) -> Result<SshKeys, E
|
|||||||
};
|
};
|
||||||
Ok(ssh_keys)
|
Ok(ssh_keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,10 @@ impl VersionT for Version {
|
|||||||
async fn post_up(self, ctx: &RpcContext, _input: Value) -> Result<(), Error> {
|
async fn post_up(self, ctx: &RpcContext, _input: Value) -> Result<(), Error> {
|
||||||
Command::new("systemd-firstboot")
|
Command::new("systemd-firstboot")
|
||||||
.arg("--root=/media/startos/config/overlay/")
|
.arg("--root=/media/startos/config/overlay/")
|
||||||
.arg(ctx.account.peek(|a| format!("--hostname={}", a.hostname.0)))
|
.arg(
|
||||||
|
ctx.account
|
||||||
|
.peek(|a| format!("--hostname={}", a.hostname.hostname.as_ref())),
|
||||||
|
)
|
||||||
.invoke(ErrorKind::ParseSysInfo)
|
.invoke(ErrorKind::ParseSysInfo)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -169,6 +169,33 @@ impl VersionT for Version {
|
|||||||
// Migrate SMTP: rename server->host, login->username, add security field
|
// Migrate SMTP: rename server->host, login->username, add security field
|
||||||
migrate_smtp(db);
|
migrate_smtp(db);
|
||||||
|
|
||||||
|
// Delete ui.name (moved to serverInfo.name)
|
||||||
|
if let Some(ui) = db
|
||||||
|
.get_mut("public")
|
||||||
|
.and_then(|p| p.get_mut("ui"))
|
||||||
|
.and_then(|u| u.as_object_mut())
|
||||||
|
{
|
||||||
|
ui.remove("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate serverInfo.name from serverInfo.hostname
|
||||||
|
if let Some(hostname) = db
|
||||||
|
.get("public")
|
||||||
|
.and_then(|p| p.get("serverInfo"))
|
||||||
|
.and_then(|s| s.get("hostname"))
|
||||||
|
.and_then(|h| h.as_str())
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
{
|
||||||
|
let name = denormalize_hostname(&hostname);
|
||||||
|
if let Some(server_info) = db
|
||||||
|
.get_mut("public")
|
||||||
|
.and_then(|p| p.get_mut("serverInfo"))
|
||||||
|
.and_then(|s| s.as_object_mut())
|
||||||
|
{
|
||||||
|
server_info.insert("name".into(), Value::String(name.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(migration_data)
|
Ok(migration_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,6 +291,23 @@ fn migrate_smtp(db: &mut Value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn denormalize_hostname(s: &str) -> String {
|
||||||
|
let mut cap = true;
|
||||||
|
s.chars()
|
||||||
|
.map(|c| {
|
||||||
|
if c == '-' {
|
||||||
|
cap = true;
|
||||||
|
' '
|
||||||
|
} else if cap {
|
||||||
|
cap = false;
|
||||||
|
c.to_ascii_uppercase()
|
||||||
|
} else {
|
||||||
|
c
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
fn migrate_host(host: Option<&mut Value>) {
|
fn migrate_host(host: Option<&mut Value>) {
|
||||||
let Some(host) = host.and_then(|h| h.as_object_mut()) else {
|
let Some(host) = host.and_then(|h| h.as_object_mut()) else {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ export type AttachParams = {
|
|||||||
password: EncryptedWire | null
|
password: EncryptedWire | null
|
||||||
guid: string
|
guid: string
|
||||||
kiosk?: boolean
|
kiosk?: boolean
|
||||||
|
name: string | null
|
||||||
hostname: string | null
|
hostname: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 Hostname = string
|
export type ServerHostname = string
|
||||||
@@ -10,6 +10,7 @@ export type ServerInfo = {
|
|||||||
arch: string
|
arch: string
|
||||||
platform: string
|
platform: string
|
||||||
id: string
|
id: string
|
||||||
|
name: string
|
||||||
hostname: string
|
hostname: string
|
||||||
version: string
|
version: string
|
||||||
packageVersionCompat: string
|
packageVersionCompat: string
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
// 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 SetServerHostnameParams = { hostname: string }
|
export type SetServerHostnameParams = {
|
||||||
|
name: string | null
|
||||||
|
hostname: string | null
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ export type SetupExecuteParams = {
|
|||||||
password: EncryptedWire
|
password: EncryptedWire
|
||||||
recoverySource: RecoverySource<EncryptedWire> | null
|
recoverySource: RecoverySource<EncryptedWire> | null
|
||||||
kiosk?: boolean
|
kiosk?: boolean
|
||||||
|
name: string | null
|
||||||
hostname: string | null
|
hostname: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// 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 { Hostname } from './Hostname'
|
import type { ServerHostname } from './ServerHostname'
|
||||||
|
|
||||||
export type StartOsRecoveryInfo = {
|
export type StartOsRecoveryInfo = {
|
||||||
hostname: Hostname
|
hostname: ServerHostname
|
||||||
version: string
|
version: string
|
||||||
timestamp: string
|
timestamp: string
|
||||||
passwordHash: string | null
|
passwordHash: string | null
|
||||||
|
|||||||
@@ -130,7 +130,6 @@ export { HealthCheckId } from './HealthCheckId'
|
|||||||
export { HostId } from './HostId'
|
export { HostId } from './HostId'
|
||||||
export { HostnameInfo } from './HostnameInfo'
|
export { HostnameInfo } from './HostnameInfo'
|
||||||
export { HostnameMetadata } from './HostnameMetadata'
|
export { HostnameMetadata } from './HostnameMetadata'
|
||||||
export { Hostname } from './Hostname'
|
|
||||||
export { Hosts } from './Hosts'
|
export { Hosts } from './Hosts'
|
||||||
export { Host } from './Host'
|
export { Host } from './Host'
|
||||||
export { IdMap } from './IdMap'
|
export { IdMap } from './IdMap'
|
||||||
@@ -237,6 +236,7 @@ export { RestorePackageParams } from './RestorePackageParams'
|
|||||||
export { RunActionParams } from './RunActionParams'
|
export { RunActionParams } from './RunActionParams'
|
||||||
export { Security } from './Security'
|
export { Security } from './Security'
|
||||||
export { ServerBackupReport } from './ServerBackupReport'
|
export { ServerBackupReport } from './ServerBackupReport'
|
||||||
|
export { ServerHostname } from './ServerHostname'
|
||||||
export { ServerInfo } from './ServerInfo'
|
export { ServerInfo } from './ServerInfo'
|
||||||
export { ServerSpecs } from './ServerSpecs'
|
export { ServerSpecs } from './ServerSpecs'
|
||||||
export { ServerStatus } from './ServerStatus'
|
export { ServerStatus } from './ServerStatus'
|
||||||
|
|||||||
Reference in New Issue
Block a user