mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
Compare commits
11 Commits
v0.4.0-alp
...
v0.4.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5aa9c045e1 | ||
|
|
6f1900f3bb | ||
|
|
bc62de795e | ||
|
|
c62ca4b183 | ||
|
|
876e5bc683 | ||
|
|
b99f3b73cd | ||
|
|
7eecf29449 | ||
|
|
1d331d7810 | ||
|
|
68414678d8 | ||
|
|
2f6b9dac26 | ||
|
|
d1812d875b |
8
Makefile
8
Makefile
@@ -30,8 +30,10 @@ ALL_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE
|
||||
echo cargo-deps/aarch64-unknown-linux-musl/release/pi-beep; \
|
||||
fi) \
|
||||
$(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then \
|
||||
echo cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console; \
|
||||
echo cargo-deps/$(ARCH)-unknown-linux-musl/release/flamegraph; \
|
||||
fi') \
|
||||
$(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)console($$|-) ]]; then \
|
||||
echo cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console; \
|
||||
fi')
|
||||
REBUILD_TYPES = 1
|
||||
|
||||
@@ -139,9 +141,11 @@ install: $(ALL_TARGETS)
|
||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-cli)
|
||||
if [ "$(PLATFORM)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-musl/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi
|
||||
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then \
|
||||
$(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); \
|
||||
$(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/flamegraph,$(DESTDIR)/usr/bin/flamegraph); \
|
||||
fi
|
||||
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)console($$|-) ]]'; then \
|
||||
$(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); \
|
||||
fi
|
||||
$(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs,$(DESTDIR)/usr/bin/startos-backup-fs)
|
||||
$(call ln,/usr/bin/startos-backup-fs,$(DESTDIR)/usr/sbin/mount.backup-fs)
|
||||
|
||||
|
||||
2
container-runtime/package-lock.json
generated
2
container-runtime/package-lock.json
generated
@@ -38,7 +38,7 @@
|
||||
},
|
||||
"../sdk/dist": {
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.38",
|
||||
"version": "0.4.0-beta.41",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^3.0.0",
|
||||
|
||||
34
core/Cargo.lock
generated
34
core/Cargo.lock
generated
@@ -881,6 +881,17 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-on-stack-overflow"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fd2d70527f3737a1ad17355e260706c1badebabd1fa06a7a053407380df841b"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"libc",
|
||||
"nix 0.23.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "barrage"
|
||||
version = "0.2.3"
|
||||
@@ -4632,6 +4643,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tracing",
|
||||
"ts-rs",
|
||||
"typeid",
|
||||
"yasi",
|
||||
"zbus",
|
||||
]
|
||||
@@ -4678,6 +4690,19 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset 0.6.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.24.3"
|
||||
@@ -7265,7 +7290,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "start-os"
|
||||
version = "0.4.0-alpha.10"
|
||||
version = "0.4.0-alpha.11"
|
||||
dependencies = [
|
||||
"aes 0.7.5",
|
||||
"arti-client",
|
||||
@@ -7275,6 +7300,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"axum 0.8.4",
|
||||
"backhand",
|
||||
"backtrace-on-stack-overflow",
|
||||
"barrage",
|
||||
"base32",
|
||||
"base64 0.22.1",
|
||||
@@ -9249,6 +9275,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
|
||||
@@ -46,7 +46,7 @@ if [ -n "$FEATURES" ]; then
|
||||
fi
|
||||
|
||||
RUSTFLAGS=""
|
||||
if [[ "${ENVIRONMENT:-}" =~ (^|-)unstable($|-) ]]; then
|
||||
if [[ "${ENVIRONMENT:-}" =~ (^|-)console($|-) ]]; then
|
||||
RUSTFLAGS="--cfg tokio_unstable"
|
||||
fi
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ cd ..
|
||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
|
||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
||||
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||
RUSTFLAGS="--cfg tokio_unstable"
|
||||
fi
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ cd ..
|
||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
|
||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
||||
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||
RUSTFLAGS="--cfg tokio_unstable"
|
||||
fi
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ cd ..
|
||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
|
||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
||||
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||
RUSTFLAGS="--cfg tokio_unstable"
|
||||
fi
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ cd ..
|
||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
|
||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
||||
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||
RUSTFLAGS="--cfg tokio_unstable"
|
||||
fi
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ cd ..
|
||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
|
||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
||||
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||
RUSTFLAGS="--cfg tokio_unstable"
|
||||
fi
|
||||
|
||||
|
||||
@@ -35,5 +35,6 @@ ts-rs = "9"
|
||||
thiserror = "2.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tracing = "0.1.39"
|
||||
typeid = "1"
|
||||
yasi = { version = "0.1.6", features = ["serde", "ts-rs"] }
|
||||
zbus = "5"
|
||||
|
||||
@@ -188,6 +188,7 @@ impl Display for ErrorKind {
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub source: color_eyre::eyre::Error,
|
||||
pub debug: Option<color_eyre::eyre::Error>,
|
||||
pub kind: ErrorKind,
|
||||
pub revision: Option<Revision>,
|
||||
pub task: Option<JoinHandle<()>>,
|
||||
@@ -199,9 +200,15 @@ impl Display for Error {
|
||||
}
|
||||
}
|
||||
impl Error {
|
||||
pub fn new<E: Into<color_eyre::eyre::Error>>(source: E, kind: ErrorKind) -> Self {
|
||||
pub fn new<E: Into<color_eyre::eyre::Error> + std::fmt::Debug + 'static>(
|
||||
source: E,
|
||||
kind: ErrorKind,
|
||||
) -> Self {
|
||||
let debug = (typeid::of::<E>() == typeid::of::<color_eyre::eyre::Error>())
|
||||
.then(|| eyre!("{source:?}"));
|
||||
Error {
|
||||
source: source.into(),
|
||||
debug,
|
||||
kind,
|
||||
revision: None,
|
||||
task: None,
|
||||
@@ -209,11 +216,8 @@ impl Error {
|
||||
}
|
||||
pub fn clone_output(&self) -> Self {
|
||||
Error {
|
||||
source: ErrorData {
|
||||
details: format!("{}", self.source),
|
||||
debug: format!("{:?}", self.source),
|
||||
}
|
||||
.into(),
|
||||
source: eyre!("{}", self.source),
|
||||
debug: self.debug.as_ref().map(|e| eyre!("{e}")),
|
||||
kind: self.kind,
|
||||
revision: self.revision.clone(),
|
||||
task: None,
|
||||
@@ -539,25 +543,24 @@ where
|
||||
impl<T, E> ResultExt<T, E> for Result<T, E>
|
||||
where
|
||||
color_eyre::eyre::Error: From<E>,
|
||||
E: std::fmt::Debug + 'static,
|
||||
{
|
||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
||||
self.map_err(|e| Error {
|
||||
source: e.into(),
|
||||
kind,
|
||||
revision: None,
|
||||
task: None,
|
||||
})
|
||||
self.map_err(|e| Error::new(e, kind))
|
||||
}
|
||||
|
||||
fn with_ctx<F: FnOnce(&E) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
||||
self.map_err(|e| {
|
||||
let (kind, ctx) = f(&e);
|
||||
let debug = (typeid::of::<E>() == typeid::of::<color_eyre::eyre::Error>())
|
||||
.then(|| eyre!("{ctx}: {e:?}"));
|
||||
let source = color_eyre::eyre::Error::from(e);
|
||||
let ctx = format!("{}: {}", ctx, source);
|
||||
let source = source.wrap_err(ctx);
|
||||
let with_ctx = format!("{ctx}: {source}");
|
||||
let source = source.wrap_err(with_ctx);
|
||||
Error {
|
||||
kind,
|
||||
source,
|
||||
debug,
|
||||
revision: None,
|
||||
task: None,
|
||||
}
|
||||
@@ -578,25 +581,24 @@ where
|
||||
}
|
||||
impl<T> ResultExt<T, Error> for Result<T, Error> {
|
||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
||||
self.map_err(|e| Error {
|
||||
source: e.source,
|
||||
kind,
|
||||
revision: e.revision,
|
||||
task: e.task,
|
||||
})
|
||||
self.map_err(|e| Error { kind, ..e })
|
||||
}
|
||||
|
||||
fn with_ctx<F: FnOnce(&Error) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
||||
self.map_err(|e| {
|
||||
let (kind, ctx) = f(&e);
|
||||
let source = e.source;
|
||||
let ctx = format!("{}: {}", ctx, source);
|
||||
let source = source.wrap_err(ctx);
|
||||
let with_ctx = format!("{ctx}: {source}");
|
||||
let source = source.wrap_err(with_ctx);
|
||||
let debug = e.debug.map(|e| {
|
||||
let with_ctx = format!("{ctx}: {e}");
|
||||
e.wrap_err(with_ctx)
|
||||
});
|
||||
Error {
|
||||
kind,
|
||||
source,
|
||||
revision: e.revision,
|
||||
task: e.task,
|
||||
debug,
|
||||
..e
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ cd ..
|
||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
|
||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
||||
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||
RUSTFLAGS="--cfg tokio_unstable"
|
||||
fi
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ keywords = [
|
||||
name = "start-os"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/Start9Labs/start-os"
|
||||
version = "0.4.0-alpha.10" # VERSION_BUMP
|
||||
version = "0.4.0-alpha.11" # VERSION_BUMP
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
@@ -48,13 +48,14 @@ cli-registry = []
|
||||
cli-startd = []
|
||||
cli-tunnel = []
|
||||
default = ["cli", "startd", "registry", "cli-container", "tunnel"]
|
||||
dev = []
|
||||
dev = ["backtrace-on-stack-overflow"]
|
||||
docker = []
|
||||
registry = []
|
||||
startd = ["mail-send"]
|
||||
test = []
|
||||
tunnel = []
|
||||
unstable = ["console-subscriber", "tokio/tracing"]
|
||||
console = ["console-subscriber", "tokio/tracing"]
|
||||
unstable = ["backtrace-on-stack-overflow"]
|
||||
|
||||
[dependencies]
|
||||
arti-client = { version = "0.33", features = [
|
||||
@@ -82,6 +83,7 @@ async-trait = "0.1.74"
|
||||
axum = { version = "0.8.4", features = ["ws"] }
|
||||
barrage = "0.2.3"
|
||||
backhand = "0.21.0"
|
||||
backtrace-on-stack-overflow = { version = "0.3.0", optional = true }
|
||||
base32 = "0.5.0"
|
||||
base64 = "0.22.1"
|
||||
base64ct = "1.6.0"
|
||||
|
||||
@@ -132,8 +132,6 @@ async fn inner_main(
|
||||
.await?;
|
||||
rpc_ctx.shutdown().await?;
|
||||
|
||||
tracing::info!("RPC Context is dropped");
|
||||
|
||||
Ok(shutdown)
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,11 @@ pub struct RpcContextSeed {
|
||||
pub start_time: Instant,
|
||||
pub crons: SyncMutex<BTreeMap<Guid, NonDetachingJoinHandle<()>>>,
|
||||
}
|
||||
impl Drop for RpcContextSeed {
|
||||
fn drop(&mut self) {
|
||||
tracing::info!("RpcContext is dropped");
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Hardware {
|
||||
pub devices: Vec<LshwDevice>,
|
||||
@@ -269,7 +274,7 @@ impl RpcContext {
|
||||
self.crons.mutate(|c| std::mem::take(c));
|
||||
self.services.shutdown_all().await?;
|
||||
self.is_closed.store(true, Ordering::SeqCst);
|
||||
tracing::info!("RPC Context is shutdown");
|
||||
tracing::info!("RpcContext is shutdown");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -202,8 +202,10 @@ pub struct NetworkInfo {
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct DnsSettings {
|
||||
pub dhcp_servers: Vec<SocketAddr>,
|
||||
pub static_servers: Option<Vec<SocketAddr>>,
|
||||
#[ts(type = "string[]")]
|
||||
pub dhcp_servers: VecDeque<SocketAddr>,
|
||||
#[ts(type = "string[] | null")]
|
||||
pub static_servers: Option<VecDeque<SocketAddr>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||
|
||||
@@ -31,7 +31,7 @@ pub async fn write_developer_key(
|
||||
secret_key: secret.to_bytes(),
|
||||
public_key: Some(PublicKeyBytes(VerifyingKey::from(secret).to_bytes())),
|
||||
};
|
||||
let mut file = create_file_mod(path, 0o046).await?;
|
||||
let mut file = create_file_mod(path, 0o640).await?;
|
||||
file.write_all(
|
||||
keypair_bytes
|
||||
.to_pkcs8_pem(base64ct::LineEnding::default())
|
||||
|
||||
@@ -10,8 +10,8 @@ use tracing::instrument;
|
||||
|
||||
use super::filesystem::{FileSystem, MountType, ReadOnly, ReadWrite};
|
||||
use super::util::unmount;
|
||||
use crate::Error;
|
||||
use crate::util::{Invoke, Never};
|
||||
use crate::Error;
|
||||
|
||||
pub const TMP_MOUNTPOINT: &'static str = "/media/startos/tmp";
|
||||
|
||||
@@ -74,7 +74,7 @@ impl MountGuard {
|
||||
}
|
||||
pub async fn unmount(mut self, delete_mountpoint: bool) -> Result<(), Error> {
|
||||
if self.mounted {
|
||||
unmount(&self.mountpoint, false).await?;
|
||||
unmount(&self.mountpoint, !cfg!(feature = "unstable")).await?;
|
||||
if delete_mountpoint {
|
||||
match tokio::fs::remove_dir(&self.mountpoint).await {
|
||||
Err(e) if e.raw_os_error() == Some(39) => Ok(()), // directory not empty
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
fn main() {
|
||||
#[cfg(feature = "backtrace-on-stack-overflow")]
|
||||
unsafe {
|
||||
backtrace_on_stack_overflow::enable()
|
||||
};
|
||||
startos::bins::startbox()
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ pub trait AuthContext: SignatureAuthContext {
|
||||
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str;
|
||||
fn init_auth_cookie() -> impl Future<Output = Result<(), Error>> + Send {
|
||||
async {
|
||||
let mut file = create_file_mod(Self::LOCAL_AUTH_COOKIE_PATH, 0o046).await?;
|
||||
let mut file = create_file_mod(Self::LOCAL_AUTH_COOKIE_PATH, 0o640).await?;
|
||||
file.write_all(BASE64.encode(random::<[u8; 32]>()).as_bytes())
|
||||
.await?;
|
||||
file.sync_all().await?;
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
use hickory_client::client::Client;
|
||||
use hickory_client::proto::runtime::TokioRuntimeProvider;
|
||||
use hickory_client::proto::tcp::TcpClientStream;
|
||||
use hickory_client::proto::udp::UdpClientStream;
|
||||
use hickory_client::proto::xfer::{DnsExchangeBackground, DnsRequestOptions};
|
||||
use hickory_client::proto::xfer::DnsRequestOptions;
|
||||
use hickory_client::proto::DnsHandle;
|
||||
use hickory_server::authority::MessageResponseBuilder;
|
||||
use hickory_server::proto::op::{Header, ResponseCode};
|
||||
@@ -24,6 +25,7 @@ use imbl::OrdMap;
|
||||
use imbl_value::InternedString;
|
||||
use itertools::Itertools;
|
||||
use models::{GatewayId, OptionExt, PackageId};
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use rpc_toolkit::{
|
||||
from_fn_async, from_fn_blocking, Context, HandlerArgs, HandlerExt, ParentHandler,
|
||||
};
|
||||
@@ -36,6 +38,7 @@ use crate::db::model::public::NetworkInterfaceInfo;
|
||||
use crate::db::model::Database;
|
||||
use crate::net::gateway::NetworkInterfaceWatcher;
|
||||
use crate::prelude::*;
|
||||
use crate::util::actor::background::BackgroundJobQueue;
|
||||
use crate::util::io::file_string_stream;
|
||||
use crate::util::serde::{display_serializable, HandlerExtSerde};
|
||||
use crate::util::sync::{SyncRwLock, Watch};
|
||||
@@ -161,97 +164,146 @@ impl DnsClient {
|
||||
Self {
|
||||
client: client.clone(),
|
||||
_thread: tokio::spawn(async move {
|
||||
loop {
|
||||
if let Err::<(), Error>(e) = async {
|
||||
let mut stream = file_string_stream("/run/systemd/resolve/resolv.conf")
|
||||
.filter_map(|a| futures::future::ready(a.transpose()))
|
||||
.boxed();
|
||||
let mut conf: String = stream
|
||||
.next()
|
||||
.await
|
||||
.or_not_found("/run/systemd/resolve/resolv.conf")??;
|
||||
let mut prev_nameservers = Vec::new();
|
||||
let mut bg = BTreeMap::<SocketAddr, BoxFuture<_>>::new();
|
||||
loop {
|
||||
let nameservers = conf
|
||||
.lines()
|
||||
.map(|l| l.trim())
|
||||
.filter_map(|l| l.strip_prefix("nameserver "))
|
||||
.skip(2)
|
||||
.map(|n| {
|
||||
n.parse::<SocketAddr>()
|
||||
.or_else(|_| n.parse::<IpAddr>().map(|a| (a, 53).into()))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let static_nameservers = db
|
||||
.mutate(|db| {
|
||||
let dns = db
|
||||
.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_network_mut()
|
||||
.as_dns_mut();
|
||||
dns.as_dhcp_servers_mut().ser(&nameservers)?;
|
||||
dns.as_static_servers().de()
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
let nameservers = static_nameservers.unwrap_or(nameservers);
|
||||
if nameservers != prev_nameservers {
|
||||
let mut existing: BTreeMap<_, _> =
|
||||
client.peek(|c| c.iter().cloned().collect());
|
||||
let mut new = Vec::with_capacity(nameservers.len());
|
||||
for addr in &nameservers {
|
||||
if let Some(existing) = existing.remove(addr) {
|
||||
new.push((*addr, existing));
|
||||
} else {
|
||||
let client = if let Ok((client, bg_thread)) =
|
||||
Client::connect(
|
||||
UdpClientStream::builder(
|
||||
*addr,
|
||||
TokioRuntimeProvider::new(),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
let (bg, mut runner) = BackgroundJobQueue::new();
|
||||
runner
|
||||
.run_while(async move {
|
||||
let dhcp_ns_db = db.clone();
|
||||
bg.add_job(async move {
|
||||
loop {
|
||||
if let Err(e) = async {
|
||||
let mut stream =
|
||||
file_string_stream("/run/systemd/resolve/resolv.conf")
|
||||
.filter_map(|a| futures::future::ready(a.transpose()))
|
||||
.boxed();
|
||||
while let Some(conf) = stream.next().await {
|
||||
let conf: String = conf?;
|
||||
let mut nameservers = conf
|
||||
.lines()
|
||||
.map(|l| l.trim())
|
||||
.filter_map(|l| l.strip_prefix("nameserver "))
|
||||
.map(|n| {
|
||||
n.parse::<SocketAddr>().or_else(|_| {
|
||||
n.parse::<IpAddr>().map(|a| (a, 53).into())
|
||||
})
|
||||
})
|
||||
.collect::<Result<VecDeque<_>, _>>()?;
|
||||
if nameservers
|
||||
.front()
|
||||
.map_or(false, |addr| addr.ip().is_loopback())
|
||||
{
|
||||
bg.insert(*addr, bg_thread.boxed());
|
||||
client
|
||||
} else {
|
||||
let (stream, sender) = TcpClientStream::new(
|
||||
*addr,
|
||||
None,
|
||||
Some(Duration::from_secs(30)),
|
||||
TokioRuntimeProvider::new(),
|
||||
);
|
||||
let (client, bg_thread) =
|
||||
Client::new(stream, sender, None)
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
bg.insert(*addr, bg_thread.boxed());
|
||||
client
|
||||
};
|
||||
new.push((*addr, client));
|
||||
nameservers.pop_front();
|
||||
}
|
||||
if nameservers.front().map_or(false, |addr| {
|
||||
addr.ip() == IpAddr::from([1, 1, 1, 1])
|
||||
}) {
|
||||
nameservers.pop_front();
|
||||
}
|
||||
dhcp_ns_db
|
||||
.mutate(|db| {
|
||||
let dns = db
|
||||
.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_network_mut()
|
||||
.as_dns_mut();
|
||||
dns.as_dhcp_servers_mut().ser(&nameservers)
|
||||
})
|
||||
.await
|
||||
.result?
|
||||
}
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("{e}");
|
||||
tracing::debug!("{e:?}");
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
bg.retain(|n, _| nameservers.iter().any(|a| a == n));
|
||||
prev_nameservers = nameservers;
|
||||
client.replace(new);
|
||||
}
|
||||
tokio::select! {
|
||||
c = stream.next() => conf = c.or_not_found("/run/systemd/resolve/resolv.conf")??,
|
||||
_ = futures::future::join(
|
||||
futures::future::join_all(bg.values_mut()),
|
||||
futures::future::pending::<()>(),
|
||||
) => (),
|
||||
});
|
||||
loop {
|
||||
if let Err::<(), Error>(e) = async {
|
||||
let mut static_changed = db
|
||||
.subscribe(
|
||||
"/public/serverInfo/network/dns/staticServers"
|
||||
.parse::<JsonPointer>()
|
||||
.with_kind(ErrorKind::Database)?,
|
||||
)
|
||||
.await;
|
||||
let mut prev_nameservers = VecDeque::new();
|
||||
let mut bg = BTreeMap::<SocketAddr, BoxFuture<_>>::new();
|
||||
loop {
|
||||
let dns = db
|
||||
.peek()
|
||||
.await
|
||||
.into_public()
|
||||
.into_server_info()
|
||||
.into_network()
|
||||
.into_dns();
|
||||
let nameservers = dns
|
||||
.as_static_servers()
|
||||
.transpose_ref()
|
||||
.unwrap_or_else(|| dns.as_dhcp_servers())
|
||||
.de()?;
|
||||
if nameservers != prev_nameservers {
|
||||
let mut existing: BTreeMap<_, _> =
|
||||
client.peek(|c| c.iter().cloned().collect());
|
||||
let mut new = Vec::with_capacity(nameservers.len());
|
||||
for addr in &nameservers {
|
||||
if let Some(existing) = existing.remove(addr) {
|
||||
new.push((*addr, existing));
|
||||
} else {
|
||||
let client = if let Ok((client, bg_thread)) =
|
||||
Client::connect(
|
||||
UdpClientStream::builder(
|
||||
*addr,
|
||||
TokioRuntimeProvider::new(),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
bg.insert(*addr, bg_thread.boxed());
|
||||
client
|
||||
} else {
|
||||
let (stream, sender) = TcpClientStream::new(
|
||||
*addr,
|
||||
None,
|
||||
Some(Duration::from_secs(30)),
|
||||
TokioRuntimeProvider::new(),
|
||||
);
|
||||
let (client, bg_thread) =
|
||||
Client::new(stream, sender, None)
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
bg.insert(*addr, bg_thread.boxed());
|
||||
client
|
||||
};
|
||||
new.push((*addr, client));
|
||||
}
|
||||
}
|
||||
bg.retain(|n, _| nameservers.iter().any(|a| a == n));
|
||||
prev_nameservers = nameservers;
|
||||
client.replace(new);
|
||||
}
|
||||
futures::future::select(
|
||||
static_changed.recv().boxed(),
|
||||
futures::future::join(
|
||||
futures::future::join_all(bg.values_mut()),
|
||||
futures::future::pending::<()>(),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("{e}");
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("{e}");
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
})
|
||||
.into(),
|
||||
}
|
||||
@@ -269,6 +321,12 @@ impl DnsClient {
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref LOCALHOST: Name = Name::from_ascii("localhost").unwrap();
|
||||
static ref STARTOS: Name = Name::from_ascii("startos").unwrap();
|
||||
static ref EMBASSY: Name = Name::from_ascii("embassy").unwrap();
|
||||
}
|
||||
|
||||
struct Resolver {
|
||||
client: DnsClient,
|
||||
net_iface: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>,
|
||||
@@ -276,9 +334,12 @@ struct Resolver {
|
||||
}
|
||||
impl Resolver {
|
||||
fn resolve(&self, name: &Name, src: IpAddr) -> Option<Vec<IpAddr>> {
|
||||
if name.zone_of(&*LOCALHOST) {
|
||||
return Some(vec![Ipv4Addr::LOCALHOST.into(), Ipv6Addr::LOCALHOST.into()]);
|
||||
}
|
||||
self.resolve.peek(|r| {
|
||||
if r.private_domains
|
||||
.get(&*name.to_lowercase().to_ascii())
|
||||
.get(&*name.to_lowercase().to_utf8().trim_end_matches('.'))
|
||||
.map_or(false, |d| d.strong_count() > 0)
|
||||
{
|
||||
if let Some(res) = self.net_iface.peek(|i| {
|
||||
@@ -295,36 +356,30 @@ impl Resolver {
|
||||
return Some(res);
|
||||
}
|
||||
}
|
||||
match name.iter().next_back() {
|
||||
Some(b"embassy") | Some(b"startos") => {
|
||||
if let Some(pkg) = name.iter().rev().skip(1).next() {
|
||||
if let Some(ip) = r.services.get(&Some(
|
||||
std::str::from_utf8(pkg)
|
||||
.unwrap_or_default()
|
||||
.parse()
|
||||
.unwrap_or_default(),
|
||||
)) {
|
||||
Some(
|
||||
ip.iter()
|
||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||
.map(|(ip, _)| (*ip).into())
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Some(ip) = r.services.get(&None) {
|
||||
Some(
|
||||
ip.iter()
|
||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||
.map(|(ip, _)| (*ip).into())
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if STARTOS.zone_of(name) || EMBASSY.zone_of(name) {
|
||||
let Ok(pkg) = name
|
||||
.trim_to(2)
|
||||
.iter()
|
||||
.next()
|
||||
.map(std::str::from_utf8)
|
||||
.transpose()
|
||||
.map_err(|_| ())
|
||||
.and_then(|s| s.map(PackageId::from_str).transpose().map_err(|_| ()))
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
if let Some(ip) = r.services.get(&pkg) {
|
||||
Some(
|
||||
ip.iter()
|
||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||
.map(|(ip, _)| (*ip).into())
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -420,16 +475,22 @@ impl RequestHandler for Resolver {
|
||||
}
|
||||
} else {
|
||||
let query = query.original().clone();
|
||||
let mut streams = self.client.lookup(query, DnsRequestOptions::default());
|
||||
let mut opt = DnsRequestOptions::default();
|
||||
opt.recursion_desired = request.recursion_desired();
|
||||
let mut streams = self.client.lookup(query, opt);
|
||||
let mut err = None;
|
||||
for stream in streams.iter_mut() {
|
||||
match tokio::time::timeout(Duration::from_secs(5), stream.next()).await {
|
||||
Ok(Some(Err(e))) => err = Some(e),
|
||||
Ok(Some(Ok(msg))) => {
|
||||
let mut header = msg.header().clone();
|
||||
header.set_id(request.id());
|
||||
header.set_checking_disabled(request.checking_disabled());
|
||||
header.set_recursion_available(true);
|
||||
return response_handle
|
||||
.send_response(
|
||||
MessageResponseBuilder::from_message_request(&*request).build(
|
||||
Header::response_from_request(request.header()),
|
||||
header,
|
||||
msg.answers(),
|
||||
msg.name_servers(),
|
||||
&msg.soa().map(|s| s.to_owned().into_record_of_rdata()),
|
||||
|
||||
@@ -7,16 +7,19 @@ use helpers::NonDetachingJoinHandle;
|
||||
use id_pool::IdPool;
|
||||
use imbl::OrdMap;
|
||||
use models::GatewayId;
|
||||
use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::process::Command;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::model::public::NetworkInterfaceInfo;
|
||||
use crate::net::gateway::{DynInterfaceFilter, InterfaceFilter};
|
||||
use crate::net::gateway::{DynInterfaceFilter, InterfaceFilter, SecureFilter};
|
||||
use crate::net::utils::ipv6_is_link_local;
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
use crate::util::serde::{display_serializable, HandlerExtSerde};
|
||||
use crate::util::sync::Watch;
|
||||
use crate::util::Invoke;
|
||||
|
||||
pub const START9_BRIDGE_IFACE: &str = "lxcbr0";
|
||||
pub const FIRST_DYNAMIC_PRIVATE_PORT: u16 = 49152;
|
||||
@@ -42,6 +45,42 @@ impl AvailablePorts {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn forward_api<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new().subcommand(
|
||||
"dump-table",
|
||||
from_fn_async(
|
||||
|ctx: RpcContext| async move { ctx.net_controller.forward.dump_table().await },
|
||||
)
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|HandlerArgs { params, .. }, res| {
|
||||
use prettytable::*;
|
||||
|
||||
if let Some(format) = params.format {
|
||||
return display_serializable(format, res);
|
||||
}
|
||||
|
||||
let mut table = Table::new();
|
||||
table.add_row(row![bc => "FROM", "TO", "FILTER / GATEWAY"]);
|
||||
|
||||
for (external, target) in res.0 {
|
||||
table.add_row(row![external, target.target, target.filter]);
|
||||
for (source, gateway) in target.gateways {
|
||||
table.add_row(row![
|
||||
format!("{}:{}", source, external),
|
||||
target.target,
|
||||
gateway
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
table.print_tty(false)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
|
||||
struct ForwardRequest {
|
||||
external: u16,
|
||||
target: SocketAddr,
|
||||
@@ -49,6 +88,7 @@ struct ForwardRequest {
|
||||
rc: Weak<()>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ForwardEntry {
|
||||
external: u16,
|
||||
target: SocketAddr,
|
||||
@@ -96,7 +136,7 @@ impl ForwardEntry {
|
||||
let mut keep = BTreeSet::<SocketAddr>::new();
|
||||
for (iface, info) in ip_info
|
||||
.iter()
|
||||
.chain([NetworkInterfaceInfo::loopback()])
|
||||
// .chain([NetworkInterfaceInfo::loopback()])
|
||||
.filter(|(id, info)| filter_ref.filter(*id, *info))
|
||||
{
|
||||
if let Some(ip_info) = &info.ip_info {
|
||||
@@ -155,10 +195,9 @@ impl ForwardEntry {
|
||||
*self = Self::new(external, target, rc);
|
||||
self.update(ip_info, Some(filter)).await?;
|
||||
} else {
|
||||
if self.prev_filter != filter {
|
||||
self.update(ip_info, Some(filter)).await?;
|
||||
}
|
||||
self.rc = rc;
|
||||
self.update(ip_info, Some(filter).filter(|f| f != &self.prev_filter))
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -174,7 +213,7 @@ impl Drop for ForwardEntry {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone)]
|
||||
struct ForwardState {
|
||||
state: BTreeMap<u16, ForwardEntry>,
|
||||
}
|
||||
@@ -197,7 +236,7 @@ impl ForwardState {
|
||||
for entry in self.state.values_mut() {
|
||||
entry.update(ip_info, None).await?;
|
||||
}
|
||||
self.state.retain(|_, fwd| !fwd.forwards.is_empty());
|
||||
self.state.retain(|_, fwd| fwd.rc.strong_count() > 0);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -209,28 +248,68 @@ fn err_has_exited<T>(_: T) -> Error {
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ForwardTable(pub BTreeMap<u16, ForwardTarget>);
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ForwardTarget {
|
||||
pub target: SocketAddr,
|
||||
pub filter: String,
|
||||
pub gateways: BTreeMap<SocketAddr, GatewayId>,
|
||||
}
|
||||
impl From<&ForwardState> for ForwardTable {
|
||||
fn from(value: &ForwardState) -> Self {
|
||||
Self(
|
||||
value
|
||||
.state
|
||||
.iter()
|
||||
.map(|(external, entry)| {
|
||||
(
|
||||
*external,
|
||||
ForwardTarget {
|
||||
target: entry.target,
|
||||
filter: format!("{:?}", entry.prev_filter),
|
||||
gateways: entry.forwards.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum ForwardCommand {
|
||||
Forward(ForwardRequest, oneshot::Sender<Result<(), Error>>),
|
||||
Sync(oneshot::Sender<Result<(), Error>>),
|
||||
DumpTable(oneshot::Sender<ForwardTable>),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
assert_ne!(
|
||||
false.into_dyn(),
|
||||
SecureFilter { secure: false }.into_dyn().into_dyn()
|
||||
);
|
||||
}
|
||||
|
||||
pub struct PortForwardController {
|
||||
req: mpsc::UnboundedSender<(Option<ForwardRequest>, oneshot::Sender<Result<(), Error>>)>,
|
||||
req: mpsc::UnboundedSender<ForwardCommand>,
|
||||
_thread: NonDetachingJoinHandle<()>,
|
||||
}
|
||||
impl PortForwardController {
|
||||
pub fn new(mut ip_info: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>) -> Self {
|
||||
let (req_send, mut req_recv) = mpsc::unbounded_channel::<(
|
||||
Option<ForwardRequest>,
|
||||
oneshot::Sender<Result<(), Error>>,
|
||||
)>();
|
||||
let (req_send, mut req_recv) = mpsc::unbounded_channel::<ForwardCommand>();
|
||||
let thread = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
||||
let mut state = ForwardState::default();
|
||||
let mut interfaces = ip_info.read_and_mark_seen();
|
||||
loop {
|
||||
tokio::select! {
|
||||
msg = req_recv.recv() => {
|
||||
if let Some((msg, re)) = msg {
|
||||
if let Some(req) = msg {
|
||||
re.send(state.handle_request(req, &interfaces).await).ok();
|
||||
} else {
|
||||
re.send(state.sync(&interfaces).await).ok();
|
||||
}
|
||||
if let Some(cmd) = msg {
|
||||
match cmd {
|
||||
ForwardCommand::Forward(req, re) => re.send(state.handle_request(req, &interfaces).await).ok(),
|
||||
ForwardCommand::Sync(re) => re.send(state.sync(&interfaces).await).ok(),
|
||||
ForwardCommand::DumpTable(re) => re.send((&state).into()).ok(),
|
||||
};
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -250,19 +329,19 @@ impl PortForwardController {
|
||||
pub async fn add(
|
||||
&self,
|
||||
external: u16,
|
||||
filter: impl InterfaceFilter,
|
||||
filter: DynInterfaceFilter,
|
||||
target: SocketAddr,
|
||||
) -> Result<Arc<()>, Error> {
|
||||
let rc = Arc::new(());
|
||||
let (send, recv) = oneshot::channel();
|
||||
self.req
|
||||
.send((
|
||||
Some(ForwardRequest {
|
||||
.send(ForwardCommand::Forward(
|
||||
ForwardRequest {
|
||||
external,
|
||||
target,
|
||||
filter: filter.into_dyn(),
|
||||
filter,
|
||||
rc: Arc::downgrade(&rc),
|
||||
}),
|
||||
},
|
||||
send,
|
||||
))
|
||||
.map_err(err_has_exited)?;
|
||||
@@ -271,13 +350,25 @@ impl PortForwardController {
|
||||
}
|
||||
pub async fn gc(&self) -> Result<(), Error> {
|
||||
let (send, recv) = oneshot::channel();
|
||||
self.req.send((None, send)).map_err(err_has_exited)?;
|
||||
self.req
|
||||
.send(ForwardCommand::Sync(send))
|
||||
.map_err(err_has_exited)?;
|
||||
|
||||
recv.await.map_err(err_has_exited)?
|
||||
}
|
||||
pub async fn dump_table(&self) -> Result<ForwardTable, Error> {
|
||||
let (req, res) = oneshot::channel();
|
||||
self.req
|
||||
.send(ForwardCommand::DumpTable(req))
|
||||
.map_err(err_has_exited)?;
|
||||
res.await.map_err(err_has_exited)
|
||||
}
|
||||
}
|
||||
|
||||
async fn forward(interface: &str, source: SocketAddr, target: SocketAddr) -> Result<(), Error> {
|
||||
if source.is_ipv6() {
|
||||
return Ok(()); // TODO: socat? ip6tables?
|
||||
}
|
||||
Command::new("/usr/lib/startos/scripts/forward-port")
|
||||
.env("iiface", interface)
|
||||
.env("oiface", START9_BRIDGE_IFACE)
|
||||
@@ -291,6 +382,9 @@ async fn forward(interface: &str, source: SocketAddr, target: SocketAddr) -> Res
|
||||
}
|
||||
|
||||
async fn unforward(interface: &str, source: SocketAddr, target: SocketAddr) -> Result<(), Error> {
|
||||
if source.is_ipv6() {
|
||||
return Ok(()); // TODO: socat? ip6tables?
|
||||
}
|
||||
Command::new("/usr/lib/startos/scripts/forward-port")
|
||||
.env("UNDO", "1")
|
||||
.env("iiface", interface)
|
||||
|
||||
@@ -1329,6 +1329,9 @@ impl InterfaceFilter for DynInterfaceFilter {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self.0.as_any()
|
||||
}
|
||||
fn into_dyn(self) -> DynInterfaceFilter {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl DynInterfaceFilter {
|
||||
fn new<T: InterfaceFilter>(value: T) -> Self {
|
||||
|
||||
@@ -135,11 +135,12 @@ impl BindInfo {
|
||||
}
|
||||
impl InterfaceFilter for NetInfo {
|
||||
fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool {
|
||||
if info.public() {
|
||||
self.public_enabled.contains(id)
|
||||
} else {
|
||||
!self.private_disabled.contains(id)
|
||||
}
|
||||
info.ip_info.is_some()
|
||||
&& if info.public() {
|
||||
self.public_enabled.contains(id)
|
||||
} else {
|
||||
!self.private_disabled.contains(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ pub fn net_api<C: Context>() -> ParentHandler<C> {
|
||||
"dns",
|
||||
dns::dns_api::<C>().with_about("Manage and query DNS"),
|
||||
)
|
||||
.subcommand(
|
||||
"forward",
|
||||
forward::forward_api::<C>().with_about("Manage port forwards"),
|
||||
)
|
||||
.subcommand(
|
||||
"gateway",
|
||||
gateway::gateway_api::<C>().with_about("View and edit gateway configurations"),
|
||||
|
||||
@@ -3,13 +3,13 @@ use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use clap::Parser;
|
||||
use clap::builder::ValueParserFactory;
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::const_true;
|
||||
use imbl_value::InternedString;
|
||||
use models::{FromStrParser, PackageId};
|
||||
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
@@ -155,6 +155,16 @@ pub async fn remove(
|
||||
for id in ids {
|
||||
n.remove(&id)?;
|
||||
}
|
||||
let mut unread = 0;
|
||||
for (_, n) in n.as_entries()? {
|
||||
if !n.as_seen().de()? {
|
||||
unread += 1;
|
||||
}
|
||||
}
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_unread_notification_count_mut()
|
||||
.ser(&unread)?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
@@ -179,6 +189,16 @@ pub async fn remove_before(
|
||||
for id in n.keys()?.range(..before) {
|
||||
n.remove(&id)?;
|
||||
}
|
||||
let mut unread = 0;
|
||||
for (_, n) in n.as_entries()? {
|
||||
if !n.as_seen().de()? {
|
||||
unread += 1;
|
||||
}
|
||||
}
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_unread_notification_count_mut()
|
||||
.ser(&unread)?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -15,12 +15,12 @@ use futures::future::BoxFuture;
|
||||
use futures::stream::FusedStream;
|
||||
use futures::{FutureExt, SinkExt, StreamExt, TryStreamExt};
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
use imbl_value::{InternedString, json};
|
||||
use imbl_value::{json, InternedString};
|
||||
use itertools::Itertools;
|
||||
use models::{ActionId, HostId, ImageId, PackageId};
|
||||
use nix::sys::signal::Signal;
|
||||
use persistent_container::{PersistentContainer, Subcontainer};
|
||||
use rpc_toolkit::{CallRemoteHandler, Empty, HandlerArgs, HandlerFor, from_fn_async};
|
||||
use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, HandlerArgs, HandlerFor};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use service_actor::ServiceActor;
|
||||
use start_stop::StartStop;
|
||||
@@ -47,11 +47,11 @@ use crate::service::action::update_tasks;
|
||||
use crate::service::rpc::{ExitParams, InitKind};
|
||||
use crate::service::service_map::InstallProgressHandles;
|
||||
use crate::service::uninstall::cleanup;
|
||||
use crate::util::Never;
|
||||
use crate::util::actor::concurrent::ConcurrentActor;
|
||||
use crate::util::io::{AsyncReadStream, TermSize, create_file, delete_file};
|
||||
use crate::util::io::{create_file, delete_file, AsyncReadStream, TermSize};
|
||||
use crate::util::net::WebSocketExt;
|
||||
use crate::util::serde::Pem;
|
||||
use crate::util::Never;
|
||||
use crate::volume::data_dir;
|
||||
use crate::{CAP_1_KiB, DATA_DIR};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::net::Ipv4Addr;
|
||||
|
||||
use clap::Parser;
|
||||
use ipnet::Ipv4Net;
|
||||
use rpc_toolkit::{Context, Empty, HandlerExt, ParentHandler, from_fn_async};
|
||||
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::context::CliContext;
|
||||
@@ -22,7 +22,7 @@ pub fn tunnel_api<C: Context>() -> ParentHandler<C> {
|
||||
subnet_api::<C>().with_about("Add, remove, or modify subnets"),
|
||||
)
|
||||
// .subcommand(
|
||||
// "forward",
|
||||
// "port-forward",
|
||||
// ParentHandler::<C>::new()
|
||||
// .subcommand(
|
||||
// "add",
|
||||
@@ -77,19 +77,19 @@ pub fn subnet_api<C: Context>() -> ParentHandler<C, SubnetParams> {
|
||||
// .with_call_remote::<CliContext>(),
|
||||
// )
|
||||
// .subcommand(
|
||||
// "add-client",
|
||||
// from_fn_async(add_client)
|
||||
// "add-device",
|
||||
// from_fn_async(add_device)
|
||||
// .with_metadata("sync_db", Value::Bool(true))
|
||||
// .no_display()
|
||||
// .with_about("Add a client to a subnet")
|
||||
// .with_about("Add a device to a subnet")
|
||||
// .with_call_remote::<CliContext>(),
|
||||
// )
|
||||
// .subcommand(
|
||||
// "remove-client",
|
||||
// from_fn_async(remove_client)
|
||||
// "remove-device",
|
||||
// from_fn_async(remove_device)
|
||||
// .with_metadata("sync_db", Value::Bool(true))
|
||||
// .no_display()
|
||||
// .with_about("Remove a client from a subnet")
|
||||
// .with_about("Remove a device from a subnet")
|
||||
// .with_call_remote::<CliContext>(),
|
||||
// )
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use futures::future::{BoxFuture, FusedFuture, abortable, pending};
|
||||
use futures::future::{abortable, pending, BoxFuture, FusedFuture};
|
||||
use futures::stream::{AbortHandle, Abortable, BoxStream};
|
||||
use futures::{Future, FutureExt, Stream, StreamExt};
|
||||
use tokio::sync::watch;
|
||||
|
||||
@@ -912,7 +912,7 @@ impl AsRef<Path> for TmpDir {
|
||||
}
|
||||
impl Drop for TmpDir {
|
||||
fn drop(&mut self) {
|
||||
if self.path.exists() {
|
||||
if self.path != PathBuf::new() && self.path.exists() {
|
||||
let path = std::mem::take(&mut self.path);
|
||||
tokio::spawn(async move {
|
||||
tokio::fs::remove_dir_all(&path).await.log_err();
|
||||
@@ -1575,7 +1575,7 @@ pub fn file_string_stream(
|
||||
loop {
|
||||
match stream.watches().add(
|
||||
&path,
|
||||
WatchMask::MODIFY | WatchMask::MOVE_SELF | WatchMask::MOVED_TO | WatchMask::DELETE_SELF,
|
||||
WatchMask::MODIFY | WatchMask::CLOSE_WRITE | WatchMask::MOVE_SELF | WatchMask::MOVED_TO | WatchMask::DELETE_SELF,
|
||||
) {
|
||||
Ok(_) => break,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
|
||||
@@ -59,7 +59,7 @@ impl StartOSLogger {
|
||||
fn base_subscriber(logfile: LogFile) -> impl Subscriber {
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::{EnvFilter, fmt};
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
|
||||
let filter_layer = || {
|
||||
EnvFilter::builder()
|
||||
@@ -80,10 +80,8 @@ impl StartOSLogger {
|
||||
|
||||
let sub = tracing_subscriber::registry();
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
#[cfg(feature = "console-subscriber")]
|
||||
let sub = sub.with(console_subscriber::spawn());
|
||||
#[cfg(not(feature = "unstable"))]
|
||||
let sub = sub.with(filter_layer());
|
||||
|
||||
let sub = sub.with(fmt_layer).with(ErrorLayer::default());
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ use ::serde::{Deserialize, Serialize};
|
||||
use async_trait::async_trait;
|
||||
use color_eyre::eyre::{self, eyre};
|
||||
use fd_lock_rs::FdLock;
|
||||
use futures::FutureExt;
|
||||
use futures::future::BoxFuture;
|
||||
pub use helpers::NonDetachingJoinHandle;
|
||||
use futures::FutureExt;
|
||||
use helpers::canonicalize;
|
||||
pub use helpers::NonDetachingJoinHandle;
|
||||
use imbl_value::InternedString;
|
||||
use lazy_static::lazy_static;
|
||||
pub use models::VersionString;
|
||||
@@ -25,7 +25,7 @@ use pin_project::pin_project;
|
||||
use sha2::Digest;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, BufReader};
|
||||
use tokio::sync::{Mutex, OwnedMutexGuard, RwLock, oneshot};
|
||||
use tokio::sync::{oneshot, Mutex, OwnedMutexGuard, RwLock};
|
||||
use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
use url::Url;
|
||||
@@ -197,17 +197,17 @@ impl<'a> Invoke<'a> for ExtendedCommand<'a> {
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
async fn invoke(&mut self, error_kind: crate::ErrorKind) -> Result<Vec<u8>, Error> {
|
||||
let cmd_str = self
|
||||
.cmd
|
||||
.as_std()
|
||||
.get_program()
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
self.cmd.kill_on_drop(true);
|
||||
if self.input.is_some() {
|
||||
self.cmd.stdin(Stdio::piped());
|
||||
}
|
||||
if self.pipe.is_empty() {
|
||||
let cmd_str = self
|
||||
.cmd
|
||||
.as_std()
|
||||
.get_program()
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
if self.capture {
|
||||
self.cmd.stdout(Stdio::piped());
|
||||
self.cmd.stderr(Stdio::piped());
|
||||
@@ -256,6 +256,7 @@ impl<'a> Invoke<'a> for ExtendedCommand<'a> {
|
||||
.take()
|
||||
.map(|i| Box::new(i) as Box<dyn AsyncRead + Unpin + Send>);
|
||||
for (idx, cmd) in IntoIterator::into_iter(cmds).enumerate() {
|
||||
let cmd_str = cmd.as_std().get_program().to_string_lossy().into_owned();
|
||||
let last = idx == len - 1;
|
||||
if self.capture || !last {
|
||||
cmd.stdout(Stdio::piped());
|
||||
|
||||
@@ -5,14 +5,14 @@ use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{Future, FutureExt};
|
||||
use imbl_value::{InternedString, to_value};
|
||||
use imbl_value::{to_value, InternedString};
|
||||
use patch_db::json_ptr::ROOT;
|
||||
|
||||
use crate::Error;
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::Database;
|
||||
use crate::prelude::*;
|
||||
use crate::progress::PhaseProgressTrackerHandle;
|
||||
use crate::Error;
|
||||
|
||||
mod v0_3_5;
|
||||
mod v0_3_5_1;
|
||||
@@ -50,8 +50,9 @@ mod v0_4_0_alpha_8;
|
||||
mod v0_4_0_alpha_9;
|
||||
|
||||
mod v0_4_0_alpha_10;
|
||||
mod v0_4_0_alpha_11;
|
||||
|
||||
pub type Current = v0_4_0_alpha_10::Version; // VERSION_BUMP
|
||||
pub type Current = v0_4_0_alpha_11::Version; // VERSION_BUMP
|
||||
|
||||
impl Current {
|
||||
#[instrument(skip(self, db))]
|
||||
@@ -164,7 +165,8 @@ enum Version {
|
||||
V0_4_0_alpha_7(Wrapper<v0_4_0_alpha_7::Version>),
|
||||
V0_4_0_alpha_8(Wrapper<v0_4_0_alpha_8::Version>),
|
||||
V0_4_0_alpha_9(Wrapper<v0_4_0_alpha_9::Version>),
|
||||
V0_4_0_alpha_10(Wrapper<v0_4_0_alpha_10::Version>), // VERSION_BUMP
|
||||
V0_4_0_alpha_10(Wrapper<v0_4_0_alpha_10::Version>),
|
||||
V0_4_0_alpha_11(Wrapper<v0_4_0_alpha_11::Version>), // VERSION_BUMP
|
||||
Other(exver::Version),
|
||||
}
|
||||
|
||||
@@ -217,7 +219,8 @@ impl Version {
|
||||
Self::V0_4_0_alpha_7(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_8(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_9(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_10(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||
Self::V0_4_0_alpha_10(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_11(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||
Self::Other(v) => {
|
||||
return Err(Error::new(
|
||||
eyre!("unknown version {v}"),
|
||||
@@ -262,7 +265,8 @@ impl Version {
|
||||
Version::V0_4_0_alpha_7(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_8(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_9(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_10(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||
Version::V0_4_0_alpha_10(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_11(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||
Version::Other(x) => x.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
37
core/startos/src/version/v0_4_0_alpha_11.rs
Normal file
37
core/startos/src/version/v0_4_0_alpha_11.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{v0_4_0_alpha_10, VersionT};
|
||||
use crate::prelude::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref V0_4_0_alpha_11: exver::Version = exver::Version::new(
|
||||
[0, 4, 0],
|
||||
[PreReleaseSegment::String("alpha".into()), 11.into()]
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Version;
|
||||
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_4_0_alpha_10::Version;
|
||||
type PreUpRes = ();
|
||||
|
||||
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn semver(self) -> exver::Version {
|
||||
V0_4_0_alpha_11.clone()
|
||||
}
|
||||
fn compat(self) -> &'static VersionRange {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
#[instrument]
|
||||
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||
Ok(Value::Null)
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ PLATFORM_CONFIG_EXTRAS=()
|
||||
if [ "${IB_TARGET_PLATFORM}" = "raspberrypi" ]; then
|
||||
PLATFORM_CONFIG_EXTRAS+=( --firmware-binary false )
|
||||
PLATFORM_CONFIG_EXTRAS+=( --firmware-chroot false )
|
||||
PLATFORM_CONFIG_EXTRAS+=( --linux-packages linux-image-6.12.20+rpt )
|
||||
PLATFORM_CONFIG_EXTRAS+=( --linux-packages linux-image-6.12.47+rpt )
|
||||
PLATFORM_CONFIG_EXTRAS+=( --linux-flavours "rpi-v8 rpi-2712" )
|
||||
elif [ "${IB_TARGET_PLATFORM}" = "rockchip64" ]; then
|
||||
PLATFORM_CONFIG_EXTRAS+=( --linux-flavours rockchip64 )
|
||||
@@ -204,8 +204,8 @@ if [ "${IB_TARGET_PLATFORM}" = "raspberrypi" ]; then
|
||||
echo "Configuring raspi kernel '\$v'"
|
||||
extract-ikconfig "/usr/lib/modules/\$v/kernel/kernel/configs.ko.xz" > /boot/config-\$v
|
||||
done
|
||||
mkinitramfs -c gzip -o /boot/initramfs8 6.12.25-v8+
|
||||
mkinitramfs -c gzip -o /boot/initramfs_2712 6.12.25-v8-16k+
|
||||
mkinitramfs -c gzip -o /boot/initramfs8 6.12.47-v8+
|
||||
mkinitramfs -c gzip -o /boot/initramfs_2712 6.12.47-v8-16k+
|
||||
fi
|
||||
|
||||
useradd --shell /bin/bash -G startos -m start9
|
||||
@@ -231,7 +231,8 @@ lb chroot
|
||||
lb installer
|
||||
lb binary_chroot
|
||||
lb chroot_prep install all mode-apt-install-binary mode-archives-chroot
|
||||
ln -sf /run/systemd/resolve/stub-resolv.conf chroot/chroot/etc/resolv.conf
|
||||
echo "nameserver 127.0.0.1" > chroot/chroot/etc/resolv.conf
|
||||
echo "nameserver 1.1.1.1" >> chroot/chroot/etc/resolv.conf # Cloudflare DNS Fallback
|
||||
lb binary_rootfs
|
||||
|
||||
cp $prep_results_dir/binary/live/filesystem.squashfs $RESULTS_DIR/$IMAGE_BASENAME.squashfs
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type DnsSettings = {
|
||||
dhcpServers: Array<string>
|
||||
staticServers: Array<string> | null
|
||||
dhcpServers: string[]
|
||||
staticServers: string[] | null
|
||||
}
|
||||
|
||||
@@ -21,23 +21,81 @@ type FilterKinds = "onion" | "local" | "domain" | "ip" | "ipv4" | "ipv6"
|
||||
export type Filter = {
|
||||
visibility?: "public" | "private"
|
||||
kind?: FilterKinds | FilterKinds[]
|
||||
predicate?: (h: HostnameInfo) => boolean
|
||||
exclude?: Filter
|
||||
}
|
||||
|
||||
type VisibilityFilter<V extends "public" | "private"> = V extends "public"
|
||||
? (HostnameInfo & { public: true }) | VisibilityFilter<Exclude<V, "public">>
|
||||
: V extends "private"
|
||||
?
|
||||
| (HostnameInfo & { public: false })
|
||||
| VisibilityFilter<Exclude<V, "private">>
|
||||
: never
|
||||
type KindFilter<K extends FilterKinds> = K extends "onion"
|
||||
? (HostnameInfo & { kind: "onion" }) | KindFilter<Exclude<K, "onion">>
|
||||
: K extends "local"
|
||||
?
|
||||
| (HostnameInfo & { kind: "ip"; hostname: { kind: "local" } })
|
||||
| KindFilter<Exclude<K, "local">>
|
||||
: K extends "domain"
|
||||
?
|
||||
| (HostnameInfo & { kind: "ip"; hostname: { kind: "domain" } })
|
||||
| KindFilter<Exclude<K, "domain">>
|
||||
: K extends "ipv4"
|
||||
?
|
||||
| (HostnameInfo & { kind: "ip"; hostname: { kind: "ipv4" } })
|
||||
| KindFilter<Exclude<K, "ipv4">>
|
||||
: K extends "ipv6"
|
||||
?
|
||||
| (HostnameInfo & { kind: "ip"; hostname: { kind: "ipv6" } })
|
||||
| KindFilter<Exclude<K, "ipv6">>
|
||||
: K extends "ip"
|
||||
? KindFilter<Exclude<K, "ip"> | "ipv4" | "ipv6">
|
||||
: never
|
||||
|
||||
type FilterReturnTy<F extends Filter> = F extends {
|
||||
visibility: infer V extends "public" | "private"
|
||||
}
|
||||
? VisibilityFilter<V> & FilterReturnTy<Omit<F, "visibility">>
|
||||
: F extends {
|
||||
kind: (infer K extends FilterKinds) | (infer K extends FilterKinds)[]
|
||||
}
|
||||
? KindFilter<K> & FilterReturnTy<Omit<F, "kind">>
|
||||
: F extends {
|
||||
predicate: (h: HostnameInfo) => h is infer H extends HostnameInfo
|
||||
}
|
||||
? H & FilterReturnTy<Omit<F, "predicate">>
|
||||
: F extends { exclude: infer E extends Filter } // MUST BE LAST
|
||||
? HostnameInfo extends FilterReturnTy<E>
|
||||
? HostnameInfo
|
||||
: Exclude<HostnameInfo, FilterReturnTy<E>>
|
||||
: HostnameInfo
|
||||
|
||||
type Formats = "hostname-info" | "urlstring" | "url"
|
||||
type FormatReturnTy<Format extends Formats> = Format extends "hostname-info"
|
||||
? HostnameInfo
|
||||
type FormatReturnTy<
|
||||
F extends Filter,
|
||||
Format extends Formats,
|
||||
> = Format extends "hostname-info"
|
||||
? FilterReturnTy<F> | FormatReturnTy<F, Exclude<Format, "hostname-info">>
|
||||
: Format extends "url"
|
||||
? URL
|
||||
: UrlString
|
||||
? URL | FormatReturnTy<F, Exclude<Format, "url">>
|
||||
: Format extends "urlstring"
|
||||
? UrlString | FormatReturnTy<F, Exclude<Format, "urlstring">>
|
||||
: never
|
||||
|
||||
export type Filled = {
|
||||
hostnames: HostnameInfo[]
|
||||
|
||||
filter: <Format extends Formats = "urlstring">(
|
||||
filter: Filter,
|
||||
toUrls: (h: HostnameInfo) => {
|
||||
url: UrlString | null
|
||||
sslUrl: UrlString | null
|
||||
}
|
||||
|
||||
filter: <F extends Filter, Format extends Formats = "urlstring">(
|
||||
filter: F,
|
||||
format?: Format,
|
||||
) => FormatReturnTy<Format>[]
|
||||
) => FormatReturnTy<F, Format>[]
|
||||
|
||||
publicHostnames: HostnameInfo[]
|
||||
onionHostnames: HostnameInfo[]
|
||||
@@ -83,8 +141,8 @@ const negate =
|
||||
const unique = <A>(values: A[]) => Array.from(new Set(values))
|
||||
export const addressHostToUrl = (
|
||||
{ scheme, sslScheme, username, suffix }: AddressInfo,
|
||||
host: HostnameInfo,
|
||||
): UrlString[] => {
|
||||
hostname: HostnameInfo,
|
||||
): { url: UrlString | null; sslUrl: UrlString | null } => {
|
||||
const res = []
|
||||
const fmt = (scheme: string | null, host: HostnameInfo, port: number) => {
|
||||
const excludePort =
|
||||
@@ -109,14 +167,16 @@ export const addressHostToUrl = (
|
||||
username ? `${username}@` : ""
|
||||
}${hostname}${excludePort ? "" : `:${port}`}${suffix}`
|
||||
}
|
||||
if (host.hostname.sslPort !== null) {
|
||||
res.push(fmt(sslScheme, host, host.hostname.sslPort))
|
||||
let url = null
|
||||
if (hostname.hostname.sslPort !== null) {
|
||||
url = fmt(sslScheme, hostname, hostname.hostname.sslPort)
|
||||
}
|
||||
if (host.hostname.port !== null) {
|
||||
res.push(fmt(scheme, host, host.hostname.port))
|
||||
let sslUrl = null
|
||||
if (hostname.hostname.port !== null) {
|
||||
sslUrl = fmt(scheme, hostname, hostname.hostname.port)
|
||||
}
|
||||
|
||||
return res
|
||||
return { url, sslUrl }
|
||||
}
|
||||
|
||||
function filterRec(
|
||||
@@ -124,6 +184,10 @@ function filterRec(
|
||||
filter: Filter,
|
||||
invert: boolean,
|
||||
): HostnameInfo[] {
|
||||
if (filter.predicate) {
|
||||
const pred = filter.predicate
|
||||
hostnames = hostnames.filter((h) => invert !== pred(h))
|
||||
}
|
||||
if (filter.visibility === "public")
|
||||
hostnames = hostnames.filter(
|
||||
(h) => invert !== (h.kind === "onion" || h.public),
|
||||
@@ -164,19 +228,28 @@ export const filledAddress = (
|
||||
host: Host,
|
||||
addressInfo: AddressInfo,
|
||||
): FilledAddressInfo => {
|
||||
const toUrl = addressHostToUrl.bind(null, addressInfo)
|
||||
const toUrls = addressHostToUrl.bind(null, addressInfo)
|
||||
const toUrlArray = (h: HostnameInfo) => {
|
||||
const u = toUrls(h)
|
||||
return [u.url, u.sslUrl].filter((u) => u !== null)
|
||||
}
|
||||
const hostnames = host.hostnameInfo[addressInfo.internalPort] ?? []
|
||||
|
||||
return {
|
||||
...addressInfo,
|
||||
hostnames,
|
||||
filter: <T extends Formats = "urlstring">(filter: Filter, format?: T) => {
|
||||
const res = filterRec(hostnames, filter, false)
|
||||
if (format === "hostname-info") return res as FormatReturnTy<T>[]
|
||||
const urls = res.flatMap(toUrl)
|
||||
if (format === "url")
|
||||
return urls.map((u) => new URL(u)) as FormatReturnTy<T>[]
|
||||
return urls as FormatReturnTy<T>[]
|
||||
toUrls,
|
||||
filter: <F extends Filter, Format extends Formats = "urlstring">(
|
||||
filter: F,
|
||||
format?: Format,
|
||||
) => {
|
||||
const filtered = filterRec(hostnames, filter, false)
|
||||
let res: FormatReturnTy<F, Format>[] = filtered as any
|
||||
if (format === "hostname-info") return res
|
||||
const urls = filtered.flatMap(toUrlArray)
|
||||
if (format === "url") res = urls.map((u) => new URL(u)) as any
|
||||
else res = urls as any
|
||||
return res
|
||||
},
|
||||
get publicHostnames() {
|
||||
return hostnames.filter((h) => h.kind === "onion" || h.public)
|
||||
@@ -215,28 +288,28 @@ export const filledAddress = (
|
||||
)
|
||||
},
|
||||
get urls() {
|
||||
return this.hostnames.flatMap(toUrl)
|
||||
return this.hostnames.flatMap(toUrlArray)
|
||||
},
|
||||
get publicUrls() {
|
||||
return this.publicHostnames.flatMap(toUrl)
|
||||
return this.publicHostnames.flatMap(toUrlArray)
|
||||
},
|
||||
get onionUrls() {
|
||||
return this.onionHostnames.flatMap(toUrl)
|
||||
return this.onionHostnames.flatMap(toUrlArray)
|
||||
},
|
||||
get localUrls() {
|
||||
return this.localHostnames.flatMap(toUrl)
|
||||
return this.localHostnames.flatMap(toUrlArray)
|
||||
},
|
||||
get ipUrls() {
|
||||
return this.ipHostnames.flatMap(toUrl)
|
||||
return this.ipHostnames.flatMap(toUrlArray)
|
||||
},
|
||||
get ipv4Urls() {
|
||||
return this.ipv4Hostnames.flatMap(toUrl)
|
||||
return this.ipv4Hostnames.flatMap(toUrlArray)
|
||||
},
|
||||
get ipv6Urls() {
|
||||
return this.ipv6Hostnames.flatMap(toUrl)
|
||||
return this.ipv6Hostnames.flatMap(toUrlArray)
|
||||
},
|
||||
get nonIpUrls() {
|
||||
return this.nonIpHostnames.flatMap(toUrl)
|
||||
return this.nonIpHostnames.flatMap(toUrlArray)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ import {
|
||||
} from "../../base/lib/inits"
|
||||
import { DropGenerator } from "../../base/lib/util/Drop"
|
||||
|
||||
export const OSVersion = testTypeVersion("0.4.0-alpha.10")
|
||||
export const OSVersion = testTypeVersion("0.4.0-alpha.11")
|
||||
|
||||
// prettier-ignore
|
||||
type AnyNeverCond<T extends any[], Then, Else> =
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as CP from "node:child_process"
|
||||
|
||||
export { Daemon } from "./Daemon"
|
||||
export { CommandController } from "./CommandController"
|
||||
import { HealthDaemon } from "./HealthDaemon"
|
||||
import { EXIT_SUCCESS, HealthDaemon } from "./HealthDaemon"
|
||||
import { Daemon } from "./Daemon"
|
||||
import { CommandController } from "./CommandController"
|
||||
import { HealthCheck } from "../health/HealthCheck"
|
||||
@@ -91,6 +91,10 @@ type NewDaemonParams<
|
||||
subcontainer: C
|
||||
}
|
||||
|
||||
type OptionalParamSync<T> = T | (() => T | null)
|
||||
type OptionalParamAsync<T> = () => Promise<T | null>
|
||||
type OptionalParam<T> = OptionalParamSync<T> | OptionalParamAsync<T>
|
||||
|
||||
type AddDaemonParams<
|
||||
Manifest extends T.SDKManifest,
|
||||
Ids extends string,
|
||||
@@ -160,7 +164,6 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
readonly started:
|
||||
| ((onTerm: () => PromiseLike<void>) => PromiseLike<null>)
|
||||
| null,
|
||||
readonly daemons: Promise<Daemon<Manifest>>[],
|
||||
readonly ids: Ids[],
|
||||
readonly healthDaemons: HealthDaemon<Manifest>[],
|
||||
) {}
|
||||
@@ -189,9 +192,37 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
options.started,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
)
|
||||
}
|
||||
|
||||
private addDaemonImpl<Id extends string>(
|
||||
id: Id,
|
||||
daemon: Promise<
|
||||
Daemon<Manifest, SubContainer<Manifest, T.Effects> | null>
|
||||
> | null,
|
||||
requires: Ids[],
|
||||
ready: Ready | typeof EXIT_SUCCESS,
|
||||
) {
|
||||
const healthDaemon = new HealthDaemon(
|
||||
daemon,
|
||||
requires
|
||||
.map((x) => this.ids.indexOf(x))
|
||||
.filter((x) => x >= 0)
|
||||
.map((id) => this.healthDaemons[id]),
|
||||
id,
|
||||
ready,
|
||||
this.effects,
|
||||
)
|
||||
const ids = [...this.ids, id] as (Ids | Id)[]
|
||||
const healthDaemons = [...this.healthDaemons, healthDaemon]
|
||||
return new Daemons<Manifest, Ids | Id>(
|
||||
this.effects,
|
||||
this.started,
|
||||
ids,
|
||||
healthDaemons,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the complete list of daemons, including the one defined here
|
||||
* @param id
|
||||
@@ -205,36 +236,42 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
ErrorDuplicateId<Id> extends Id ? never :
|
||||
Id extends Ids ? ErrorDuplicateId<Id> :
|
||||
Id,
|
||||
options: AddDaemonParams<Manifest, Ids, Id, C>,
|
||||
options: OptionalParamSync<AddDaemonParams<Manifest, Ids, Id, C>>,
|
||||
): Daemons<Manifest, Ids | Id>
|
||||
addDaemon<Id extends string, C extends SubContainer<Manifest> | null>(
|
||||
// prettier-ignore
|
||||
id:
|
||||
"" extends Id ? never :
|
||||
ErrorDuplicateId<Id> extends Id ? never :
|
||||
Id extends Ids ? ErrorDuplicateId<Id> :
|
||||
Id,
|
||||
options: OptionalParamAsync<AddDaemonParams<Manifest, Ids, Id, C>>,
|
||||
): Promise<Daemons<Manifest, Ids | Id>>
|
||||
addDaemon<Id extends string, C extends SubContainer<Manifest> | null>(
|
||||
id: Id,
|
||||
options: OptionalParam<AddDaemonParams<Manifest, Ids, Id, C>>,
|
||||
) {
|
||||
const daemon =
|
||||
"daemon" in options
|
||||
? Promise.resolve(options.daemon)
|
||||
: Daemon.of<Manifest>()<C>(
|
||||
this.effects,
|
||||
options.subcontainer,
|
||||
options.exec,
|
||||
)
|
||||
const healthDaemon = new HealthDaemon(
|
||||
daemon,
|
||||
options.requires
|
||||
.map((x) => this.ids.indexOf(x))
|
||||
.filter((x) => x >= 0)
|
||||
.map((id) => this.healthDaemons[id]),
|
||||
id,
|
||||
options.ready,
|
||||
this.effects,
|
||||
)
|
||||
const daemons = [...this.daemons, daemon]
|
||||
const ids = [...this.ids, id] as (Ids | Id)[]
|
||||
const healthDaemons = [...this.healthDaemons, healthDaemon]
|
||||
return new Daemons<Manifest, Ids | Id>(
|
||||
this.effects,
|
||||
this.started,
|
||||
daemons,
|
||||
ids,
|
||||
healthDaemons,
|
||||
)
|
||||
const prev = this
|
||||
const res = (options: AddDaemonParams<Manifest, Ids, Id, C> | null) => {
|
||||
if (!options) return prev
|
||||
const daemon =
|
||||
"daemon" in options
|
||||
? Promise.resolve(options.daemon)
|
||||
: Daemon.of<Manifest>()<C>(
|
||||
this.effects,
|
||||
options.subcontainer,
|
||||
options.exec,
|
||||
)
|
||||
return prev.addDaemonImpl(id, daemon, options.requires, options.ready)
|
||||
}
|
||||
if (options instanceof Function) {
|
||||
const opts = options()
|
||||
if (opts instanceof Promise) {
|
||||
return opts.then(res)
|
||||
}
|
||||
return res(opts)
|
||||
}
|
||||
return res(options)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,40 +282,45 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
* @returns a new Daemons object
|
||||
*/
|
||||
addOneshot<Id extends string, C extends SubContainer<Manifest> | null>(
|
||||
id: "" extends Id
|
||||
? never
|
||||
: ErrorDuplicateId<Id> extends Id
|
||||
? never
|
||||
: Id extends Ids
|
||||
? ErrorDuplicateId<Id>
|
||||
: Id,
|
||||
options: AddOneshotParams<Manifest, Ids, Id, C>,
|
||||
// prettier-ignore
|
||||
id:
|
||||
"" extends Id ? never :
|
||||
ErrorDuplicateId<Id> extends Id ? never :
|
||||
Id extends Ids ? ErrorDuplicateId<Id> :
|
||||
Id,
|
||||
options: OptionalParamSync<AddOneshotParams<Manifest, Ids, Id, C>>,
|
||||
): Daemons<Manifest, Ids | Id>
|
||||
addOneshot<Id extends string, C extends SubContainer<Manifest> | null>(
|
||||
// prettier-ignore
|
||||
id:
|
||||
"" extends Id ? never :
|
||||
ErrorDuplicateId<Id> extends Id ? never :
|
||||
Id extends Ids ? ErrorDuplicateId<Id> :
|
||||
Id,
|
||||
options: OptionalParamAsync<AddOneshotParams<Manifest, Ids, Id, C>>,
|
||||
): Promise<Daemons<Manifest, Ids | Id>>
|
||||
addOneshot<Id extends string, C extends SubContainer<Manifest> | null>(
|
||||
id: Id,
|
||||
options: OptionalParam<AddOneshotParams<Manifest, Ids, Id, C>>,
|
||||
) {
|
||||
const daemon = Oneshot.of<Manifest>()<C>(
|
||||
this.effects,
|
||||
options.subcontainer,
|
||||
options.exec,
|
||||
)
|
||||
const healthDaemon = new HealthDaemon<Manifest>(
|
||||
daemon,
|
||||
options.requires
|
||||
.map((x) => this.ids.indexOf(x))
|
||||
.filter((x) => x >= 0)
|
||||
.map((id) => this.healthDaemons[id]),
|
||||
id,
|
||||
"EXIT_SUCCESS",
|
||||
this.effects,
|
||||
)
|
||||
const daemons = [...this.daemons, daemon]
|
||||
const ids = [...this.ids, id] as (Ids | Id)[]
|
||||
const healthDaemons = [...this.healthDaemons, healthDaemon]
|
||||
return new Daemons<Manifest, Ids | Id>(
|
||||
this.effects,
|
||||
this.started,
|
||||
daemons,
|
||||
ids,
|
||||
healthDaemons,
|
||||
)
|
||||
const prev = this
|
||||
const res = (options: AddOneshotParams<Manifest, Ids, Id, C> | null) => {
|
||||
if (!options) return prev
|
||||
const daemon = Oneshot.of<Manifest>()<C>(
|
||||
this.effects,
|
||||
options.subcontainer,
|
||||
options.exec,
|
||||
)
|
||||
return prev.addDaemonImpl(id, daemon, options.requires, EXIT_SUCCESS)
|
||||
}
|
||||
if (options instanceof Function) {
|
||||
const opts = options()
|
||||
if (opts instanceof Promise) {
|
||||
return opts.then(res)
|
||||
}
|
||||
return res(opts)
|
||||
}
|
||||
return res(options)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -288,35 +330,40 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
* @returns a new Daemons object
|
||||
*/
|
||||
addHealthCheck<Id extends string>(
|
||||
id: "" extends Id
|
||||
? never
|
||||
: ErrorDuplicateId<Id> extends Id
|
||||
? never
|
||||
: Id extends Ids
|
||||
? ErrorDuplicateId<Id>
|
||||
: Id,
|
||||
options: AddHealthCheckParams<Ids, Id>,
|
||||
// prettier-ignore
|
||||
id:
|
||||
"" extends Id ? never :
|
||||
ErrorDuplicateId<Id> extends Id ? never :
|
||||
Id extends Ids ? ErrorDuplicateId<Id> :
|
||||
Id,
|
||||
options: OptionalParamSync<AddHealthCheckParams<Ids, Id>>,
|
||||
): Daemons<Manifest, Ids | Id>
|
||||
addHealthCheck<Id extends string>(
|
||||
// prettier-ignore
|
||||
id:
|
||||
"" extends Id ? never :
|
||||
ErrorDuplicateId<Id> extends Id ? never :
|
||||
Id extends Ids ? ErrorDuplicateId<Id> :
|
||||
Id,
|
||||
options: OptionalParamAsync<AddHealthCheckParams<Ids, Id>>,
|
||||
): Promise<Daemons<Manifest, Ids | Id>>
|
||||
addHealthCheck<Id extends string>(
|
||||
id: Id,
|
||||
options: OptionalParam<AddHealthCheckParams<Ids, Id>>,
|
||||
) {
|
||||
const healthDaemon = new HealthDaemon<Manifest>(
|
||||
null,
|
||||
options.requires
|
||||
.map((x) => this.ids.indexOf(x))
|
||||
.filter((x) => x >= 0)
|
||||
.map((id) => this.healthDaemons[id]),
|
||||
id,
|
||||
options.ready,
|
||||
this.effects,
|
||||
)
|
||||
const daemons = [...this.daemons]
|
||||
const ids = [...this.ids, id] as (Ids | Id)[]
|
||||
const healthDaemons = [...this.healthDaemons, healthDaemon]
|
||||
return new Daemons<Manifest, Ids | Id>(
|
||||
this.effects,
|
||||
this.started,
|
||||
daemons,
|
||||
ids,
|
||||
healthDaemons,
|
||||
)
|
||||
const prev = this
|
||||
const res = (options: AddHealthCheckParams<Ids, Id> | null) => {
|
||||
if (!options) return prev
|
||||
return prev.addDaemonImpl(id, null, options.requires, options.ready)
|
||||
}
|
||||
if (options instanceof Function) {
|
||||
const opts = options()
|
||||
if (opts instanceof Promise) {
|
||||
return opts.then(res)
|
||||
}
|
||||
return res(opts)
|
||||
}
|
||||
return res(options)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -353,7 +400,6 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
const daemons = await new Daemons<Manifest, Ids>(
|
||||
this.effects,
|
||||
this.started,
|
||||
[...this.daemons, daemon],
|
||||
this.ids,
|
||||
[...this.healthDaemons, healthDaemon],
|
||||
).build()
|
||||
|
||||
4
sdk/package/package-lock.json
generated
4
sdk/package/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.38",
|
||||
"version": "0.4.0-beta.41",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.38",
|
||||
"version": "0.4.0-beta.41",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^3.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.38",
|
||||
"version": "0.4.0-beta.41",
|
||||
"description": "Software development kit to facilitate packaging services for StartOS",
|
||||
"main": "./package/lib/index.js",
|
||||
"types": "./package/lib/index.d.ts",
|
||||
|
||||
8693
web/package-lock.json
generated
8693
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "startos-ui",
|
||||
"version": "0.4.0-alpha.10",
|
||||
"version": "0.4.0-alpha.11",
|
||||
"author": "Start9 Labs, Inc",
|
||||
"homepage": "https://start9.com/",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -34,6 +34,12 @@ import { i18nPipe } from '../../i18n/i18n.pipe'
|
||||
<logs-window />
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
section {
|
||||
border-radius: 0.25rem;
|
||||
padding: 1rem;
|
||||
@@ -48,7 +54,7 @@ import { i18nPipe } from '../../i18n/i18n.pipe'
|
||||
logs-window {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 18rem;
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
margin: 0 1.5rem auto;
|
||||
text-align: left;
|
||||
|
||||
@@ -502,7 +502,7 @@ export default {
|
||||
531: 'Fehler beim Initialisieren des Servers',
|
||||
532: 'Abgeschlossen',
|
||||
533: 'Gateways',
|
||||
535: 'Gateway hinzufügen',
|
||||
535: 'StartTunnel-Gateway hinzufügen',
|
||||
536: 'Umbenennen',
|
||||
537: 'Zugriff',
|
||||
538: 'Öffentliche Domains',
|
||||
@@ -535,10 +535,8 @@ export default {
|
||||
569: 'Wählen Sie eine Zertifizierungsstelle aus, um SSL/TLS-Zertifikate für diese Domain auszustellen.',
|
||||
570: 'Andere',
|
||||
571: 'Ein Name zur einfachen Identifizierung des Gateways',
|
||||
572: 'Wählen Sie diese Option, wenn das Gateway für den privaten Zugriff nur für autorisierte Clients konfiguriert ist. StartTunnel ist ein privates Gateway.',
|
||||
573: 'Wählen Sie diese Option, wenn das Gateway für uneingeschränkten öffentlichen Zugriff konfiguriert ist.',
|
||||
574: 'Datei',
|
||||
575: 'Wireguard-Konfigurationsdatei',
|
||||
575: 'StartTunnel-Konfigurationsdatei',
|
||||
576: 'Kopieren/Einfügen',
|
||||
577: 'Dateiinhalt',
|
||||
578: 'Öffentlicher Schlüssel',
|
||||
@@ -550,7 +548,7 @@ export default {
|
||||
584: 'Verbindungen können manchmal langsam oder unzuverlässig sein',
|
||||
585: 'Öffentlich, wenn Sie die Adresse öffentlich teilen, andernfalls privat',
|
||||
586: 'Erfordert ein Tor-fähiges Gerät oder einen Browser',
|
||||
587: 'Nur nützlich für Clients, die HTTPS erzwingen',
|
||||
587: 'Nur nützlich für Clients, die SSL erzwingen',
|
||||
588: 'Ideal für anonyme, zensurresistente Bereitstellung und Fernzugriff',
|
||||
589: 'Ideal für lokalen Zugriff',
|
||||
590: 'Erfordert die Verbindung mit demselben lokalen Netzwerk (LAN) wie Ihr Server, entweder physisch oder über VPN',
|
||||
@@ -589,4 +587,5 @@ export default {
|
||||
623: 'Alternative Implementierungen',
|
||||
624: 'Versionen',
|
||||
625: 'Eine andere Version auswählen',
|
||||
626: 'Hochladen',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -501,7 +501,7 @@ export const ENGLISH = {
|
||||
'Error initializing server': 531,
|
||||
'Finished': 532, // an in, complete
|
||||
'Gateways': 533, // as in, a device or software that connects two different networks
|
||||
'Add gateway': 535, // as in, add a new network gateway to StartOS
|
||||
'Add StartTunnel Gateway': 535, // as in, add a new StartTunnel network gateway to StartOS
|
||||
'Rename': 536,
|
||||
'Access': 537, // as in, public or private access, almost "permission"
|
||||
'Public Domains': 538, // as in, internet domains
|
||||
@@ -534,10 +534,8 @@ export const ENGLISH = {
|
||||
'Select a Certificate Authority to issue SSL/TLS certificates for this domain': 569,
|
||||
'Other': 570, // as in, a list option to indicate none of the options listed
|
||||
'A name to easily identify the gateway': 571,
|
||||
'select this option if the gateway is configured for private access to authorized clients only. StartTunnel is a private gateway.': 572,
|
||||
'select this option if the gateway is configured for unfettered public access.': 573,
|
||||
'File': 574, // as in, a computer file
|
||||
'Wireguard Config File': 575,
|
||||
'StartTunnel Config File': 575,
|
||||
'Copy/Paste': 576,
|
||||
'File Contents': 577,
|
||||
'Public Key': 578, // as in, a cryptographic public key
|
||||
@@ -549,7 +547,7 @@ export const ENGLISH = {
|
||||
'Connections can be slow or unreliable at times': 584,
|
||||
'Public if you share the address publicly, otherwise private': 585,
|
||||
'Requires using a Tor-enabled device or browser': 586,
|
||||
'Only useful for clients that enforce HTTPS': 587,
|
||||
'Only useful for clients that require SSL': 587,
|
||||
'Ideal for anonymous, censorship-resistant hosting and remote access': 588,
|
||||
'Ideal for local access': 589,
|
||||
'Requires being connected to the same Local Area Network (LAN) as your server, either physically or via VPN': 590,
|
||||
@@ -588,4 +586,5 @@ export const ENGLISH = {
|
||||
'Alternative Implementations': 623,
|
||||
'Versions': 624,
|
||||
'Select another version': 625,
|
||||
'Upload': 626, // as in, upload a file
|
||||
} as const
|
||||
|
||||
@@ -502,7 +502,7 @@ export default {
|
||||
531: 'Error al inicializar el servidor',
|
||||
532: 'Finalizado',
|
||||
533: 'Puertas de enlace',
|
||||
535: 'Agregar puerta de enlace',
|
||||
535: 'Agregar puerta de enlace StartTunnel',
|
||||
536: 'Renombrar',
|
||||
537: 'Acceso',
|
||||
538: 'Dominios públicos',
|
||||
@@ -535,10 +535,8 @@ export default {
|
||||
569: 'Selecciona una Autoridad Certificadora para emitir certificados SSL/TLS para este dominio.',
|
||||
570: 'Otro',
|
||||
571: 'Un nombre para identificar fácilmente la puerta de enlace',
|
||||
572: 'Selecciona esta opción si la puerta de enlace está configurada para acceso privado solo a clientes autorizados. StartTunnel es una puerta de enlace privada.',
|
||||
573: 'Selecciona esta opción si la puerta de enlace está configurada para acceso público sin restricciones.',
|
||||
574: 'Archivo',
|
||||
575: 'Archivo de configuración de Wireguard',
|
||||
575: 'Archivo de configuración de StartTunnel',
|
||||
576: 'Copiar/Pegar',
|
||||
577: 'Contenido del archivo',
|
||||
578: 'Clave pública',
|
||||
@@ -550,7 +548,7 @@ export default {
|
||||
584: 'Las conexiones pueden ser lentas o poco confiables a veces',
|
||||
585: 'Público si compartes la dirección públicamente, de lo contrario privado',
|
||||
586: 'Requiere un dispositivo o navegador habilitado para Tor',
|
||||
587: 'Solo útil para clientes que imponen HTTPS',
|
||||
587: 'Solo útil para clientes que imponen SSL',
|
||||
588: 'Ideal para alojamiento y acceso remoto anónimo y resistente a la censura',
|
||||
589: 'Ideal para acceso local',
|
||||
590: 'Requiere estar conectado a la misma red de área local (LAN) que tu servidor, ya sea físicamente o mediante VPN',
|
||||
@@ -589,4 +587,5 @@ export default {
|
||||
623: 'Implementaciones alternativas',
|
||||
624: 'Versiones',
|
||||
625: 'Seleccionar otra versión',
|
||||
626: 'Subir',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -502,7 +502,7 @@ export default {
|
||||
531: "Erreur lors de l'initialisation du serveur",
|
||||
532: 'Terminé',
|
||||
533: 'Passerelles',
|
||||
535: 'Ajouter une passerelle',
|
||||
535: 'Ajouter une passerelle StartTunnel',
|
||||
536: 'Renommer',
|
||||
537: 'Accès',
|
||||
538: 'Domaines publics',
|
||||
@@ -535,10 +535,8 @@ export default {
|
||||
569: 'Sélectionnez une Autorité de Certification pour émettre des certificats SSL/TLS pour ce domaine.',
|
||||
570: 'Autre',
|
||||
571: 'Un nom pour identifier facilement la passerelle',
|
||||
572: 'Sélectionnez cette option si la passerelle est configurée pour un accès privé uniquement aux clients autorisés. StartTunnel est une passerelle privée.',
|
||||
573: 'Sélectionnez cette option si la passerelle est configurée pour un accès public illimité.',
|
||||
574: 'Fichier',
|
||||
575: 'Fichier de configuration Wireguard',
|
||||
575: 'Fichier de configuration StartTunnel',
|
||||
576: 'Copier/Coller',
|
||||
577: 'Contenu du fichier',
|
||||
578: 'Clé publique',
|
||||
@@ -550,7 +548,7 @@ export default {
|
||||
584: 'Les connexions peuvent parfois être lentes ou peu fiables',
|
||||
585: 'Public si vous partagez l’adresse publiquement, sinon privé',
|
||||
586: 'Nécessite un appareil ou un navigateur compatible Tor',
|
||||
587: 'Utile uniquement pour les clients qui imposent HTTPS',
|
||||
587: 'Utile uniquement pour les clients qui imposent SSL',
|
||||
588: 'Idéal pour l’hébergement et l’accès à distance anonymes et résistants à la censure',
|
||||
589: 'Idéal pour un accès local',
|
||||
590: 'Nécessite d’être connecté au même réseau local (LAN) que votre serveur, soit physiquement, soit via VPN',
|
||||
@@ -589,4 +587,5 @@ export default {
|
||||
623: 'Implémentations alternatives',
|
||||
624: 'Versions',
|
||||
625: 'Sélectionner une autre version',
|
||||
626: 'Téléverser',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -502,7 +502,7 @@ export default {
|
||||
531: 'Błąd inicjalizacji serwera',
|
||||
532: 'Zakończono',
|
||||
533: 'Bramy sieciowe',
|
||||
535: 'Dodaj bramę',
|
||||
535: 'Dodaj bramę StartTunnel',
|
||||
536: 'Zmień nazwę',
|
||||
537: 'Dostęp',
|
||||
538: 'Domeny publiczne',
|
||||
@@ -535,10 +535,8 @@ export default {
|
||||
569: 'Wybierz Urząd Certyfikacji, aby wystawić certyfikaty SSL/TLS dla tej domeny.',
|
||||
570: 'Inne',
|
||||
571: 'Nazwa ułatwiająca identyfikację bramy',
|
||||
572: 'Wybierz tę opcję, jeśli brama jest skonfigurowana do prywatnego dostępu tylko dla autoryzowanych klientów. StartTunnel to prywatna brama.',
|
||||
573: 'Wybierz tę opcję, jeśli brama jest skonfigurowana do nieograniczonego publicznego dostępu.',
|
||||
574: 'Plik',
|
||||
575: 'Plik konfiguracyjny Wireguard',
|
||||
575: 'Plik konfiguracyjny StartTunnel',
|
||||
576: 'Kopiuj/Wklej',
|
||||
577: 'Zawartość pliku',
|
||||
578: 'Klucz publiczny',
|
||||
@@ -550,7 +548,7 @@ export default {
|
||||
584: 'Połączenia mogą być czasami wolne lub niestabilne',
|
||||
585: 'Publiczne, jeśli udostępniasz adres publicznie, w przeciwnym razie prywatne',
|
||||
586: 'Wymaga urządzenia lub przeglądarki obsługującej Tor',
|
||||
587: 'Przydatne tylko dla klientów wymuszających HTTPS',
|
||||
587: 'Przydatne tylko dla klientów wymuszających SSL',
|
||||
588: 'Idealne do anonimowego, odpornego na cenzurę hostingu i zdalnego dostępu',
|
||||
589: 'Idealne do dostępu lokalnego',
|
||||
590: 'Wymaga połączenia z tą samą siecią lokalną (LAN) co serwer, fizycznie lub przez VPN',
|
||||
@@ -589,4 +587,5 @@ export default {
|
||||
623: 'Alternatywne implementacje',
|
||||
624: 'Wersje',
|
||||
625: 'Wybierz inną wersję',
|
||||
626: 'Prześlij',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -110,6 +110,7 @@ body {
|
||||
animation-fill-mode: forwards;
|
||||
text-align: left;
|
||||
width: 1em;
|
||||
margin-left: -.3rem;
|
||||
}
|
||||
|
||||
@keyframes ellipsis-dot {
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
var(--tui-status-warning) 24%,
|
||||
transparent
|
||||
);
|
||||
--tui-status-info: rgba(128, 89, 229, 1);
|
||||
--tui-status-info: rgba(53, 96, 240, 1);
|
||||
--tui-status-info-pale: color-mix(
|
||||
in hsl,
|
||||
var(--tui-status-info) 12%,
|
||||
@@ -168,3 +168,8 @@ tui-textfield [tuiTooltip] {
|
||||
align-self: center !important;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove after migrating to v5
|
||||
[tuiOption] {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import { StateService } from 'src/app/services/state.service'
|
||||
<app-initializing [progress]="progress()" />
|
||||
`,
|
||||
providers: [provideSetupLogsService(ApiService)],
|
||||
styles: ':host { padding: 1rem; }',
|
||||
styles: ':host { height: 100%; }',
|
||||
imports: [InitializingComponent],
|
||||
})
|
||||
export default class InitializingPage {
|
||||
|
||||
@@ -24,8 +24,8 @@ import { TuiBadge } from '@taiga-ui/kit'
|
||||
{{ 'Address details' | i18n }}
|
||||
</button>
|
||||
</td>
|
||||
<td [style.width.rem]="6">{{ address.type }}</td>
|
||||
<td [style.width.rem]="5">
|
||||
<td>{{ address.type }}</td>
|
||||
<td>
|
||||
@if (address.access === 'public') {
|
||||
<tui-badge size="s" appearance="primary-success">
|
||||
{{ 'public' | i18n }}
|
||||
@@ -38,20 +38,41 @@ import { TuiBadge } from '@taiga-ui/kit'
|
||||
-
|
||||
}
|
||||
</td>
|
||||
<td [style.width.rem]="10" [style.order]="-1">
|
||||
{{ address.gatewayName || '-' }}
|
||||
<td [style.order]="-1">
|
||||
<div [title]="address.gatewayName">
|
||||
{{ address.gatewayName || '-' }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div [title]="address.url">{{ address.url }}</div>
|
||||
</td>
|
||||
<td>{{ address.url }}</td>
|
||||
<td
|
||||
actions
|
||||
[disabled]="!isRunning()"
|
||||
[href]="address.url"
|
||||
[style.width.rem]="7"
|
||||
[style.width.rem]="5"
|
||||
(onDetails)="viewDetails(address.bullets)"
|
||||
></td>
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
white-space: nowrap;
|
||||
|
||||
td:last-child {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
td {
|
||||
width: auto !important;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { i18nKey, i18nPipe } from '@start9labs/shared'
|
||||
|
||||
type AddressWithInfo = {
|
||||
url: string
|
||||
ssl: boolean
|
||||
info: T.HostnameInfo
|
||||
gateway?: GatewayPlus
|
||||
}
|
||||
@@ -132,12 +133,26 @@ export class InterfaceService {
|
||||
|
||||
if (!hostnamesInfos.length) return addresses
|
||||
|
||||
const allAddressesWithInfo: AddressWithInfo[] = hostnamesInfos.flatMap(h =>
|
||||
utils.addressHostToUrl(serviceInterface.addressInfo, h).map(url => ({
|
||||
url,
|
||||
info: h,
|
||||
gateway: gateways.find(g => h.kind === 'ip' && h.gateway.id === g.id),
|
||||
})),
|
||||
const allAddressesWithInfo: AddressWithInfo[] = hostnamesInfos.flatMap(
|
||||
h => {
|
||||
const { url, sslUrl } = utils.addressHostToUrl(
|
||||
serviceInterface.addressInfo,
|
||||
h,
|
||||
)
|
||||
const info = h
|
||||
const gateway =
|
||||
h.kind === 'ip'
|
||||
? gateways.find(g => h.gateway.id === g.id)
|
||||
: undefined
|
||||
const res = []
|
||||
if (url) {
|
||||
res.push({ url, ssl: false, info, gateway })
|
||||
}
|
||||
if (sslUrl) {
|
||||
res.push({ url: sslUrl, ssl: true, info, gateway })
|
||||
}
|
||||
return res
|
||||
},
|
||||
)
|
||||
|
||||
const torAddrs = allAddressesWithInfo.filter(filterTor).sort(cmpTor)
|
||||
@@ -166,12 +181,10 @@ export class InterfaceService {
|
||||
}, [] as AddressWithInfo[])
|
||||
|
||||
return {
|
||||
common: bestAddrs.map(a =>
|
||||
this.toDisplayAddress(a, gateways, host.publicDomains),
|
||||
),
|
||||
common: bestAddrs.map(a => this.toDisplayAddress(a, host.publicDomains)),
|
||||
uncommon: allAddressesWithInfo
|
||||
.filter(a => !bestAddrs.includes(a))
|
||||
.map(a => this.toDisplayAddress(a, gateways, host.publicDomains)),
|
||||
.map(a => this.toDisplayAddress(a, host.publicDomains)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,8 +326,7 @@ export class InterfaceService {
|
||||
}
|
||||
|
||||
private toDisplayAddress(
|
||||
{ info, url, gateway }: AddressWithInfo,
|
||||
gateways: GatewayPlus[],
|
||||
{ info, ssl, url, gateway }: AddressWithInfo,
|
||||
publicDomains: Record<string, T.PublicDomainConfig>,
|
||||
): DisplayAddress {
|
||||
let access: DisplayAddress['access']
|
||||
@@ -338,33 +350,29 @@ export class InterfaceService {
|
||||
),
|
||||
this.i18n.transform('Requires using a Tor-enabled device or browser'),
|
||||
]
|
||||
// Tor (HTTPS)
|
||||
if (url.startsWith('https:')) {
|
||||
type = `${type} (HTTPS)`
|
||||
// Tor (SSL)
|
||||
if (ssl) {
|
||||
type = `${type} (SSL)`
|
||||
bullets = [
|
||||
this.i18n.transform('Only useful for clients that enforce HTTPS'),
|
||||
this.i18n.transform('Only useful for clients that require SSL'),
|
||||
rootCaRequired,
|
||||
...bullets,
|
||||
]
|
||||
// Tor (HTTP)
|
||||
// Tor (NON-SSL)
|
||||
} else {
|
||||
bullets.unshift(
|
||||
this.i18n.transform(
|
||||
'Ideal for anonymous, censorship-resistant hosting and remote access',
|
||||
),
|
||||
)
|
||||
if (url.startsWith('http:')) {
|
||||
type = `${type} (HTTP)`
|
||||
}
|
||||
}
|
||||
// ** Not Tor **
|
||||
} else {
|
||||
const port = info.hostname.sslPort || info.hostname.port
|
||||
const gateway = gateways.find(g => g.id === info.gateway.id)!
|
||||
gatewayName = gateway.name
|
||||
gatewayName = info.gateway.name
|
||||
|
||||
const gatewayLanIpv4 = gateway.lanIpv4[0]
|
||||
const isWireguard = gateway.ipInfo.deviceType === 'wireguard'
|
||||
const gatewayLanIpv4 = gateway?.lanIpv4[0]
|
||||
const isWireguard = gateway?.ipInfo.deviceType === 'wireguard'
|
||||
|
||||
const localIdeal = this.i18n.transform('Ideal for local access')
|
||||
const lanRequired = this.i18n.transform(
|
||||
@@ -405,9 +413,9 @@ export class InterfaceService {
|
||||
),
|
||||
rootCaRequired,
|
||||
]
|
||||
if (!gateway.public) {
|
||||
if (!info.gateway.public) {
|
||||
bullets.push(
|
||||
`${portForwarding} "${gatewayName}": ${port} -> ${gateway.subnets.find(s => s.isIpv4())?.address}:${port}`,
|
||||
`${portForwarding} "${gatewayName}": ${port} -> ${gateway?.subnets.find(s => s.isIpv4())?.address}:${port}`,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@@ -439,12 +447,12 @@ export class InterfaceService {
|
||||
if (info.public) {
|
||||
access = 'public'
|
||||
bullets = [
|
||||
`${dnsFor} ${info.hostname.value} ${resolvesTo} ${gateway.ipInfo.wanIp}`,
|
||||
`${dnsFor} ${info.hostname.value} ${resolvesTo} ${gateway?.ipInfo.wanIp}`,
|
||||
]
|
||||
|
||||
if (!gateway.public) {
|
||||
if (!info.gateway.public) {
|
||||
bullets.push(
|
||||
`${portForwarding} "${gatewayName}": ${port} -> ${gateway.subnets.find(s => s.isIpv4())?.address}:${port === 443 ? 5443 : port}`,
|
||||
`${portForwarding} "${gatewayName}": ${port} -> ${gateway?.subnets.find(s => s.isIpv4())?.address}:${port === 443 ? 5443 : port}`,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -97,10 +97,8 @@ export class ServiceHealthCheckComponent {
|
||||
return `${this.i18n.transform('Success')}: ${this.healthCheck.message || 'health check passing'}`
|
||||
case 'loading':
|
||||
case 'failure':
|
||||
return this.healthCheck.message
|
||||
// disabled
|
||||
default:
|
||||
return this.healthCheck.result
|
||||
case 'disabled':
|
||||
return this.healthCheck.message || this.healthCheck.result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,22 +6,26 @@ import {
|
||||
} from '@angular/core'
|
||||
import { i18nKey, i18nPipe } from '@start9labs/shared'
|
||||
import { tuiPure } from '@taiga-ui/cdk'
|
||||
import { TuiIcon, TuiLoader } from '@taiga-ui/core'
|
||||
import { TuiIcon } from '@taiga-ui/core'
|
||||
import { getProgressText } from 'src/app/routes/portal/routes/services/pipes/install-progress.pipe'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
|
||||
import {
|
||||
PrimaryRendering,
|
||||
renderPkgStatus,
|
||||
} from 'src/app/services/pkg-status-rendering.service'
|
||||
|
||||
@Component({
|
||||
selector: 'td[appStatus]',
|
||||
template: `
|
||||
@if (loading) {
|
||||
<tui-loader size="s" />
|
||||
} @else {
|
||||
@if (!healthy) {
|
||||
<tui-icon icon="@tui.triangle-alert" class="g-warning" />
|
||||
}
|
||||
@if (!healthy) {
|
||||
<tui-icon icon="@tui.triangle-alert" class="g-warning" />
|
||||
}
|
||||
|
||||
<b [style.color]="color">{{ status | i18n }}</b>
|
||||
|
||||
@if (showDots) {
|
||||
<span class="loading-dots g-info"></span>
|
||||
}
|
||||
<b [style.color]="color">{{ status | i18n }}{{ dots }}</b>
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
@@ -37,7 +41,7 @@ import { renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiIcon, TuiLoader, i18nPipe],
|
||||
imports: [TuiIcon, i18nPipe],
|
||||
})
|
||||
export class StatusComponent {
|
||||
@Input()
|
||||
@@ -58,10 +62,6 @@ export class StatusComponent {
|
||||
)
|
||||
}
|
||||
|
||||
get loading(): boolean {
|
||||
return this.color === 'var(--tui-status-info)'
|
||||
}
|
||||
|
||||
@tuiPure
|
||||
getStatus(pkg: PackageDataEntry) {
|
||||
return renderPkgStatus(pkg)
|
||||
@@ -72,35 +72,10 @@ export class StatusComponent {
|
||||
return `${this.i18n.transform('Installing')}... ${this.i18n.transform(getProgressText(this.pkg.stateInfo.installingInfo.progress.overall))}` as i18nKey
|
||||
}
|
||||
|
||||
switch (this.getStatus(this.pkg).primary) {
|
||||
case 'running':
|
||||
return 'Running'
|
||||
case 'stopped':
|
||||
return 'Stopped'
|
||||
case 'taskRequired':
|
||||
return 'Task Required'
|
||||
case 'updating':
|
||||
return 'Updating'
|
||||
case 'stopping':
|
||||
return 'Stopping'
|
||||
case 'starting':
|
||||
return 'Starting'
|
||||
case 'backingUp':
|
||||
return 'Backing Up'
|
||||
case 'restarting':
|
||||
return 'Restarting'
|
||||
case 'removing':
|
||||
return 'Removing'
|
||||
case 'restoring':
|
||||
return 'Restoring'
|
||||
case 'error':
|
||||
return 'Error'
|
||||
default:
|
||||
return 'Unknown'
|
||||
}
|
||||
return PrimaryRendering[this.getStatus(this.pkg).primary].display
|
||||
}
|
||||
|
||||
get dots(): '...' | '' {
|
||||
get showDots() {
|
||||
switch (this.getStatus(this.pkg).primary) {
|
||||
case 'updating':
|
||||
case 'stopping':
|
||||
@@ -108,9 +83,9 @@ export class StatusComponent {
|
||||
case 'backingUp':
|
||||
case 'restarting':
|
||||
case 'removing':
|
||||
return '...'
|
||||
return true
|
||||
default:
|
||||
return ''
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import { QrCodeComponent } from 'ng-qrcode'
|
||||
tuiTextfield
|
||||
[readOnly]="true"
|
||||
[ngModel]="member.value"
|
||||
[style.border-inline-end-width.rem]="border"
|
||||
[type]="member.masked && masked ? 'password' : 'text'"
|
||||
/>
|
||||
@if (member.masked) {
|
||||
@@ -129,16 +128,6 @@ export class ActionSuccessMemberComponent {
|
||||
|
||||
masked = true
|
||||
|
||||
get border(): number {
|
||||
let border = 0
|
||||
|
||||
if (this.member.masked) border += 2
|
||||
if (this.member.copyable) border += 2
|
||||
if (this.member.qr) border += 2
|
||||
|
||||
return border
|
||||
}
|
||||
|
||||
show(template: TemplateRef<any>) {
|
||||
const masked = this.masked
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import { SingleResult } from './types'
|
||||
tuiTextfield
|
||||
[readOnly]="true"
|
||||
[ngModel]="single.value"
|
||||
[style.border-inline-end-width.rem]="border"
|
||||
[type]="single.masked && masked ? 'password' : 'text'"
|
||||
/>
|
||||
@if (single.masked) {
|
||||
@@ -105,15 +104,6 @@ export class ActionSuccessSingleComponent {
|
||||
|
||||
masked = true
|
||||
|
||||
get border(): number {
|
||||
let border = 0
|
||||
|
||||
if (this.single.masked) border += 2
|
||||
if (this.single.copyable) border += 2
|
||||
|
||||
return border
|
||||
}
|
||||
|
||||
copy() {
|
||||
const el = this.input.nativeElement
|
||||
|
||||
|
||||
@@ -83,18 +83,10 @@ export default class GatewaysComponent {
|
||||
),
|
||||
required: true,
|
||||
default: null,
|
||||
}),
|
||||
type: ISB.Value.select({
|
||||
name: this.i18n.transform('Type'),
|
||||
description: `-**${this.i18n.transform('private')}**: ${this.i18n.transform('select this option if the gateway is configured for private access to authorized clients only. StartTunnel is a private gateway.')}\n-**${this.i18n.transform('public')}**: ${this.i18n.transform('select this option if the gateway is configured for unfettered public access.')}`,
|
||||
default: 'private',
|
||||
values: {
|
||||
private: this.i18n.transform('private'),
|
||||
public: this.i18n.transform('public'),
|
||||
},
|
||||
placeholder: 'StartTunnel 1',
|
||||
}),
|
||||
config: ISB.Value.union({
|
||||
name: this.i18n.transform('Wireguard Config File'),
|
||||
name: this.i18n.transform('StartTunnel Config File'),
|
||||
default: 'paste',
|
||||
variants: ISB.Variants.of({
|
||||
paste: {
|
||||
@@ -108,7 +100,7 @@ export default class GatewaysComponent {
|
||||
}),
|
||||
},
|
||||
select: {
|
||||
name: this.i18n.transform('Select'),
|
||||
name: this.i18n.transform('Upload'),
|
||||
spec: ISB.InputSpec.of({
|
||||
file: ISB.Value.file({
|
||||
name: this.i18n.transform('File'),
|
||||
@@ -122,7 +114,7 @@ export default class GatewaysComponent {
|
||||
})
|
||||
|
||||
this.formDialog.open(FormComponent, {
|
||||
label: 'Add gateway',
|
||||
label: 'Add StartTunnel Gateway',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(spec),
|
||||
buttons: [
|
||||
@@ -138,7 +130,7 @@ export default class GatewaysComponent {
|
||||
input.config.selection === 'paste'
|
||||
? input.config.value.file
|
||||
: await (input.config.value.file as any as File).text(),
|
||||
public: input.type === 'public',
|
||||
public: false,
|
||||
})
|
||||
return true
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -110,7 +110,7 @@ export namespace Mock {
|
||||
squashfs: {
|
||||
aarch64: {
|
||||
publishedAt: '2025-04-21T20:58:48.140749883Z',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_aarch64.squashfs',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.11/startos-0.4.0-alpha.11-33ae46f~dev_aarch64.squashfs',
|
||||
commitment: {
|
||||
hash: '4elBFVkd/r8hNadKmKtLIs42CoPltMvKe2z3LRqkphk=',
|
||||
size: 1343500288,
|
||||
@@ -122,7 +122,7 @@ export namespace Mock {
|
||||
},
|
||||
'aarch64-nonfree': {
|
||||
publishedAt: '2025-04-21T21:07:00.249285116Z',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_aarch64-nonfree.squashfs',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.11/startos-0.4.0-alpha.11-33ae46f~dev_aarch64-nonfree.squashfs',
|
||||
commitment: {
|
||||
hash: 'MrCEi4jxbmPS7zAiGk/JSKlMsiuKqQy6RbYOxlGHOIQ=',
|
||||
size: 1653075968,
|
||||
@@ -134,7 +134,7 @@ export namespace Mock {
|
||||
},
|
||||
raspberrypi: {
|
||||
publishedAt: '2025-04-21T21:16:12.933319237Z',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_raspberrypi.squashfs',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.11/startos-0.4.0-alpha.11-33ae46f~dev_raspberrypi.squashfs',
|
||||
commitment: {
|
||||
hash: '/XTVQRCqY3RK544PgitlKu7UplXjkmzWoXUh2E4HCw0=',
|
||||
size: 1490731008,
|
||||
@@ -146,7 +146,7 @@ export namespace Mock {
|
||||
},
|
||||
x86_64: {
|
||||
publishedAt: '2025-04-21T21:14:20.246908903Z',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_x86_64.squashfs',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.11/startos-0.4.0-alpha.11-33ae46f~dev_x86_64.squashfs',
|
||||
commitment: {
|
||||
hash: '/6romKTVQGSaOU7FqSZdw0kFyd7P+NBSYNwM3q7Fe44=',
|
||||
size: 1411657728,
|
||||
@@ -158,7 +158,7 @@ export namespace Mock {
|
||||
},
|
||||
'x86_64-nonfree': {
|
||||
publishedAt: '2025-04-21T21:15:17.955265284Z',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_x86_64-nonfree.squashfs',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.11/startos-0.4.0-alpha.11-33ae46f~dev_x86_64-nonfree.squashfs',
|
||||
commitment: {
|
||||
hash: 'HCRq9sr/0t85pMdrEgNBeM4x11zVKHszGnD1GDyZbSE=',
|
||||
size: 1731035136,
|
||||
@@ -385,7 +385,7 @@ export namespace Mock {
|
||||
docsUrl: 'https://bitcoin.org',
|
||||
releaseNotes: 'Even better support for Bitcoin and wallets!',
|
||||
osVersion: '0.3.6',
|
||||
sdkVersion: '0.4.0-beta.38',
|
||||
sdkVersion: '0.4.0-beta.41',
|
||||
gitHash: 'fakehash',
|
||||
icon: BTC_ICON,
|
||||
sourceVersion: null,
|
||||
@@ -420,7 +420,7 @@ export namespace Mock {
|
||||
docsUrl: 'https://bitcoinknots.org',
|
||||
releaseNotes: 'Even better support for Bitcoin and wallets!',
|
||||
osVersion: '0.3.6',
|
||||
sdkVersion: '0.4.0-beta.38',
|
||||
sdkVersion: '0.4.0-beta.41',
|
||||
gitHash: 'fakehash',
|
||||
icon: BTC_ICON,
|
||||
sourceVersion: null,
|
||||
@@ -465,7 +465,7 @@ export namespace Mock {
|
||||
docsUrl: 'https://bitcoin.org',
|
||||
releaseNotes: 'Even better support for Bitcoin and wallets!',
|
||||
osVersion: '0.3.6',
|
||||
sdkVersion: '0.4.0-beta.38',
|
||||
sdkVersion: '0.4.0-beta.41',
|
||||
gitHash: 'fakehash',
|
||||
icon: BTC_ICON,
|
||||
sourceVersion: null,
|
||||
@@ -500,7 +500,7 @@ export namespace Mock {
|
||||
docsUrl: 'https://bitcoinknots.org',
|
||||
releaseNotes: 'Even better support for Bitcoin and wallets!',
|
||||
osVersion: '0.3.6',
|
||||
sdkVersion: '0.4.0-beta.38',
|
||||
sdkVersion: '0.4.0-beta.41',
|
||||
gitHash: 'fakehash',
|
||||
icon: BTC_ICON,
|
||||
sourceVersion: null,
|
||||
@@ -547,7 +547,7 @@ export namespace Mock {
|
||||
docsUrl: 'https://lightning.engineering/',
|
||||
releaseNotes: 'Upstream release to 0.17.5',
|
||||
osVersion: '0.3.6',
|
||||
sdkVersion: '0.4.0-beta.38',
|
||||
sdkVersion: '0.4.0-beta.41',
|
||||
gitHash: 'fakehash',
|
||||
icon: LND_ICON,
|
||||
sourceVersion: null,
|
||||
@@ -595,7 +595,7 @@ export namespace Mock {
|
||||
docsUrl: 'https://lightning.engineering/',
|
||||
releaseNotes: 'Upstream release to 0.17.4',
|
||||
osVersion: '0.3.6',
|
||||
sdkVersion: '0.4.0-beta.38',
|
||||
sdkVersion: '0.4.0-beta.41',
|
||||
gitHash: 'fakehash',
|
||||
icon: LND_ICON,
|
||||
sourceVersion: null,
|
||||
@@ -647,7 +647,7 @@ export namespace Mock {
|
||||
docsUrl: 'https://bitcoin.org',
|
||||
releaseNotes: 'Even better support for Bitcoin and wallets!',
|
||||
osVersion: '0.3.6',
|
||||
sdkVersion: '0.4.0-beta.38',
|
||||
sdkVersion: '0.4.0-beta.41',
|
||||
gitHash: 'fakehash',
|
||||
icon: BTC_ICON,
|
||||
sourceVersion: null,
|
||||
@@ -682,7 +682,7 @@ export namespace Mock {
|
||||
docsUrl: 'https://bitcoinknots.org',
|
||||
releaseNotes: 'Even better support for Bitcoin and wallets!',
|
||||
osVersion: '0.3.6',
|
||||
sdkVersion: '0.4.0-beta.38',
|
||||
sdkVersion: '0.4.0-beta.41',
|
||||
gitHash: 'fakehash',
|
||||
icon: BTC_ICON,
|
||||
sourceVersion: null,
|
||||
@@ -727,7 +727,7 @@ export namespace Mock {
|
||||
docsUrl: 'https://lightning.engineering/',
|
||||
releaseNotes: 'Upstream release and minor fixes.',
|
||||
osVersion: '0.3.6',
|
||||
sdkVersion: '0.4.0-beta.38',
|
||||
sdkVersion: '0.4.0-beta.41',
|
||||
gitHash: 'fakehash',
|
||||
icon: LND_ICON,
|
||||
sourceVersion: null,
|
||||
@@ -775,7 +775,7 @@ export namespace Mock {
|
||||
marketingSite: '',
|
||||
releaseNotes: 'Upstream release and minor fixes.',
|
||||
osVersion: '0.3.6',
|
||||
sdkVersion: '0.4.0-beta.38',
|
||||
sdkVersion: '0.4.0-beta.41',
|
||||
gitHash: 'fakehash',
|
||||
icon: PROXY_ICON,
|
||||
sourceVersion: null,
|
||||
|
||||
@@ -126,13 +126,14 @@ export class DepErrorService {
|
||||
const expected = currentDep?.versionRange || ''
|
||||
|
||||
// incorrect version
|
||||
if (!this.exver.satisfies(depManifest.version, expected)) {
|
||||
if (depManifest.satisfies.some(v => !this.exver.satisfies(v, expected))) {
|
||||
return {
|
||||
expected,
|
||||
type: 'incorrectVersion',
|
||||
received: depManifest.version,
|
||||
}
|
||||
if (
|
||||
!this.exver.satisfies(depManifest.version, expected) &&
|
||||
!depManifest.satisfies.some(v => this.exver.satisfies(v, expected))
|
||||
) {
|
||||
return {
|
||||
expected,
|
||||
type: 'incorrectVersion',
|
||||
received: depManifest.version,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,11 +25,21 @@ export function getInstalledPrimaryStatus({
|
||||
tasks,
|
||||
status,
|
||||
}: T.PackageDataEntry): PrimaryStatus {
|
||||
return Object.values(tasks).some(
|
||||
t => t.active && t.task.severity === 'critical',
|
||||
)
|
||||
? 'taskRequired'
|
||||
: status.main
|
||||
if (
|
||||
Object.values(tasks).some(t => t.active && t.task.severity === 'critical')
|
||||
) {
|
||||
return 'taskRequired'
|
||||
}
|
||||
|
||||
if (
|
||||
Object.values(status.main === 'running' && status.health)
|
||||
.filter(h => !!h)
|
||||
.some(h => h.result === 'starting')
|
||||
) {
|
||||
return 'starting'
|
||||
}
|
||||
|
||||
return status.main
|
||||
}
|
||||
|
||||
function getHealthStatus(status: T.MainStatus): T.HealthStatus | null {
|
||||
@@ -43,14 +53,14 @@ function getHealthStatus(status: T.MainStatus): T.HealthStatus | null {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
if (values.some(h => h.result === 'loading')) {
|
||||
return 'loading'
|
||||
}
|
||||
|
||||
if (values.some(h => h.result === 'starting')) {
|
||||
return 'starting'
|
||||
}
|
||||
|
||||
if (values.some(h => h.result === 'loading')) {
|
||||
return 'loading'
|
||||
}
|
||||
|
||||
return 'success'
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user