mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
Feature/consolidate setup (#3092)
* start consolidating * add start-cli flash-os * combine install and setup and refactor all * use http * undo mock * fix translation * translations * use dialogservice wrapper * better ST messaging on setup * only warn on update if breakages (#3097) * finish setup wizard and ui language-keyboard feature * fix typo * wip: localization * remove start-tunnel readme * switch to posix strings for language internal * revert mock * translate backend strings * fix missing about text * help text for args * feat: add "Add new gateway" option (#3098) * feat: add "Add new gateway" option * Update web/projects/ui/src/app/routes/portal/components/form/controls/select.component.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * add translation --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Matt Hill <mattnine@protonmail.com> * fix dns selection * keyboard keymap also * ability to shutdown after install * revert mock * working setup flow + manifest localization * (mostly) redundant localization on frontend * version bump * omit live medium from disk list and better space management * ignore missing package archive on 035 migration * fix device migration * add i18n helper to sdk * fix install over 0.3.5.1 * fix grub config --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com> Co-authored-by: Alex Inkin <alexander@inkin.ru> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
264
core/Cargo.lock
generated
264
core/Cargo.lock
generated
@@ -239,6 +239,15 @@ dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "archery"
|
||||
version = "1.2.2"
|
||||
@@ -848,6 +857,12 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076"
|
||||
|
||||
[[package]]
|
||||
name = "base62"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1adf9755786e27479693dedd3271691a92b5e242ab139cacb9fb8e7fb5381111"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
@@ -1109,7 +1124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-automata 0.4.13",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -1162,9 +1177,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.52"
|
||||
version = "1.2.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3"
|
||||
checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -2711,9 +2726,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41"
|
||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||
|
||||
[[package]]
|
||||
name = "fixed-capacity-vec"
|
||||
@@ -3094,12 +3109,42 @@ version = "0.32.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "glob-match"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"log",
|
||||
"regex-automata 0.4.13",
|
||||
"regex-syntax 0.8.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globwalk"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"ignore",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-timers"
|
||||
version = "0.3.0"
|
||||
@@ -3740,6 +3785,22 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"globset",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex-automata 0.4.13",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.9"
|
||||
@@ -4125,9 +4186,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.83"
|
||||
version = "0.3.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
|
||||
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
@@ -4254,7 +4315,7 @@ version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
"regex-automata 0.4.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4479,11 +4540,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4784,6 +4845,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "normpath"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "8.2.0"
|
||||
@@ -4818,11 +4888,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5199,6 +5270,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "4.2.3"
|
||||
@@ -6364,10 +6441,19 @@ checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-automata 0.4.13",
|
||||
"regex-syntax 0.8.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
@@ -6501,7 +6587,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rpc-toolkit"
|
||||
version = "0.3.2"
|
||||
source = "git+https://github.com/Start9Labs/rpc-toolkit.git#406ee9e88bf20e3155f150eb755b5b9c2aefd167"
|
||||
source = "git+https://github.com/Start9Labs/rpc-toolkit.git#39e547ff99d997c19f9b6483b28a4394ca5a07bc"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
@@ -6586,10 +6672,64 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
name = "rust-i18n"
|
||||
version = "3.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
checksum = "fda2551fdfaf6cc5ee283adc15e157047b92ae6535cf80f6d4962d05717dc332"
|
||||
dependencies = [
|
||||
"globwalk",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"rust-i18n-macro",
|
||||
"rust-i18n-support",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-i18n-macro"
|
||||
version = "3.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22baf7d7f56656d23ebe24f6bb57a5d40d2bce2a5f1c503e692b5b2fa450f965"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-i18n-support",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-i18n-support"
|
||||
version = "3.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940ed4f52bba4c0152056d771e563b7133ad9607d4384af016a134b58d758f19"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"base62",
|
||||
"globwalk",
|
||||
"itertools 0.11.0",
|
||||
"lazy_static",
|
||||
"normpath",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"siphasher",
|
||||
"toml 0.8.23",
|
||||
"triomphe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
@@ -6666,7 +6806,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki 0.103.8",
|
||||
"rustls-webpki 0.103.9",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -6694,9 +6834,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.13.2"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
|
||||
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
@@ -6716,7 +6856,7 @@ dependencies = [
|
||||
"rustls 0.23.36",
|
||||
"rustls-native-certs",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki 0.103.8",
|
||||
"rustls-webpki 0.103.9",
|
||||
"security-framework 3.5.1",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
@@ -6742,9 +6882,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.8"
|
||||
version = "0.103.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
|
||||
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring",
|
||||
@@ -7106,6 +7246,19 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap 2.13.0",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yml"
|
||||
version = "0.0.12"
|
||||
@@ -7664,7 +7817,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "start-os"
|
||||
version = "0.4.0-alpha.17"
|
||||
version = "0.4.0-alpha.18"
|
||||
dependencies = [
|
||||
"aes 0.7.5",
|
||||
"arti-client",
|
||||
@@ -7763,6 +7916,7 @@ dependencies = [
|
||||
"rpassword",
|
||||
"rpc-toolkit",
|
||||
"rust-argon2",
|
||||
"rust-i18n",
|
||||
"safelog",
|
||||
"semver",
|
||||
"serde",
|
||||
@@ -9402,7 +9556,7 @@ dependencies = [
|
||||
"paste",
|
||||
"pin-project",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki 0.103.8",
|
||||
"rustls-webpki 0.103.9",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -9616,14 +9770,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.22"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
@@ -9653,6 +9807,17 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "triomphe"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
@@ -9854,6 +10019,12 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -10030,9 +10201,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
@@ -10054,9 +10225,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.106"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
|
||||
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -10067,11 +10238,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.56"
|
||||
version = "0.4.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
|
||||
checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
@@ -10080,9 +10252,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.106"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
|
||||
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -10090,9 +10262,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.106"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
|
||||
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -10103,9 +10275,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.106"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
|
||||
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -10204,9 +10376,9 @@ checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.83"
|
||||
version = "0.3.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
|
||||
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -10803,9 +10975,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
|
||||
@@ -15,7 +15,7 @@ license = "MIT"
|
||||
name = "start-os"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/Start9Labs/start-os"
|
||||
version = "0.4.0-alpha.17" # VERSION_BUMP
|
||||
version = "0.4.0-alpha.18" # VERSION_BUMP
|
||||
|
||||
[lib]
|
||||
name = "startos"
|
||||
@@ -213,6 +213,7 @@ reqwest = { version = "0.12.25", features = [
|
||||
reqwest_cookie_store = "0.9.0"
|
||||
rpassword = "7.2.0"
|
||||
rust-argon2 = "3.0.0"
|
||||
rust-i18n = "3.1.5"
|
||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git" }
|
||||
safelog = { version = "0.4.8", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
semver = { version = "1.0.20", features = ["serde"] }
|
||||
@@ -263,7 +264,7 @@ tower-service = "0.3.3"
|
||||
tracing = "0.1.39"
|
||||
tracing-error = "0.2.0"
|
||||
tracing-journald = "0.3.0"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
tracing-subscriber = { version = "=0.3.19", features = ["env-filter"] }
|
||||
ts-rs = "9.0.1"
|
||||
typed-builder = "0.23.2"
|
||||
url = { version = "2.4.1", features = ["serde"] }
|
||||
|
||||
@@ -26,7 +26,7 @@ PROFILE=${PROFILE:-release}
|
||||
if [ "${PROFILE}" = "release" ]; then
|
||||
BUILD_FLAGS="--release"
|
||||
else
|
||||
if [ "$PROFILE" != "debug"]; then
|
||||
if [ "$PROFILE" != "debug" ]; then
|
||||
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||
PROFILE=debug
|
||||
fi
|
||||
|
||||
5322
core/locales/i18n.yaml
Normal file
5322
core/locales/i18n.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@ pub fn action_api<C: Context>() -> ParentHandler<C> {
|
||||
"get-input",
|
||||
from_fn_async(get_action_input)
|
||||
.with_display_serializable()
|
||||
.with_about("Get action input spec")
|
||||
.with_about("about.get-action-input-spec")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -36,14 +36,14 @@ pub fn action_api<C: Context>() -> ParentHandler<C> {
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.with_about("Run service action")
|
||||
.with_about("about.run-service-action")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"clear-task",
|
||||
from_fn_async(clear_task)
|
||||
.no_display()
|
||||
.with_about("Clear a service task")
|
||||
.with_about("about.clear-service-task")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -63,7 +63,9 @@ pub struct ActionInput {
|
||||
#[derive(Deserialize, Serialize, TS, Parser)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetActionInputParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub package_id: PackageId,
|
||||
#[arg(help = "help.arg.action-id")]
|
||||
pub action_id: ActionId,
|
||||
}
|
||||
|
||||
@@ -280,8 +282,11 @@ pub struct RunActionParams {
|
||||
|
||||
#[derive(Parser)]
|
||||
struct CliRunActionParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub package_id: PackageId,
|
||||
#[arg(help = "help.arg.event-id")]
|
||||
pub event_id: Option<Guid>,
|
||||
#[arg(help = "help.arg.action-id")]
|
||||
pub action_id: ActionId,
|
||||
#[command(flatten)]
|
||||
pub input: StdinDeserializable<Option<Value>>,
|
||||
@@ -360,9 +365,11 @@ pub async fn run_action(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ClearTaskParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub package_id: PackageId,
|
||||
#[arg(help = "help.arg.replay-id")]
|
||||
pub replay_id: ReplayId,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.force-clear-task")]
|
||||
#[serde(default)]
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
@@ -51,7 +51,10 @@ pub async fn write_shadow(password: &str) -> Result<(), Error> {
|
||||
match line.split_once(":") {
|
||||
Some((user, rest)) if user == "start9" || user == "kiosk" => {
|
||||
let (_, rest) = rest.split_once(":").ok_or_else(|| {
|
||||
Error::new(eyre!("malformed /etc/shadow"), ErrorKind::ParseSysInfo)
|
||||
Error::new(
|
||||
eyre!("{}", t!("auth.malformed-etc-shadow")),
|
||||
ErrorKind::ParseSysInfo,
|
||||
)
|
||||
})?;
|
||||
shadow_file
|
||||
.write_all(format!("{user}:{hash}:{rest}\n").as_bytes())
|
||||
@@ -81,7 +84,7 @@ impl PasswordType {
|
||||
PasswordType::String(x) => Ok(x),
|
||||
PasswordType::EncryptedWire(x) => x.decrypt(current_secret).ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("Couldn't decode password"),
|
||||
color_eyre::eyre::eyre!("{}", t!("auth.couldnt-decode-password")),
|
||||
crate::ErrorKind::Unknown,
|
||||
)
|
||||
}),
|
||||
@@ -125,19 +128,19 @@ where
|
||||
"login",
|
||||
from_fn_async(cli_login::<AC>)
|
||||
.no_display()
|
||||
.with_about("Log in a new auth session"),
|
||||
.with_about("about.login-new-auth-session"),
|
||||
)
|
||||
.subcommand(
|
||||
"logout",
|
||||
from_fn_async(logout::<AC>)
|
||||
.with_metadata("get_session", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Log out of current auth session")
|
||||
.with_about("about.logout-current-auth-session")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"session",
|
||||
session::<C, AC>().with_about("List or kill auth sessions"),
|
||||
session::<C, AC>().with_about("about.list-or-kill-auth-sessions"),
|
||||
)
|
||||
.subcommand(
|
||||
"reset-password",
|
||||
@@ -147,14 +150,14 @@ where
|
||||
"reset-password",
|
||||
from_fn_async(cli_reset_password)
|
||||
.no_display()
|
||||
.with_about("Reset password"),
|
||||
.with_about("about.reset-password"),
|
||||
)
|
||||
.subcommand(
|
||||
"get-pubkey",
|
||||
from_fn_async(get_pubkey)
|
||||
.with_metadata("authenticated", Value::Bool(false))
|
||||
.no_display()
|
||||
.with_about("Get public key derived from server private key")
|
||||
.with_about("about.get-pubkey-from-server")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -208,12 +211,12 @@ pub fn check_password(hash: &str, password: &str) -> Result<(), Error> {
|
||||
ensure_code!(
|
||||
argon2::verify_encoded(&hash, password.as_bytes()).map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("Password Incorrect"),
|
||||
eyre!("{}", t!("auth.password-incorrect")),
|
||||
crate::ErrorKind::IncorrectPassword,
|
||||
)
|
||||
})?,
|
||||
crate::ErrorKind::IncorrectPassword,
|
||||
"Password Incorrect"
|
||||
t!("auth.password-incorrect")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -327,14 +330,14 @@ where
|
||||
.with_metadata("get_session", Value::Bool(true))
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|handle, result| display_sessions(handle.params, result))
|
||||
.with_about("Display all auth sessions")
|
||||
.with_about("about.display-all-auth-sessions")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"kill",
|
||||
from_fn_async(kill::<AC>)
|
||||
.no_display()
|
||||
.with_about("Terminate existing auth session(s)")
|
||||
.with_about("about.terminate-auth-sessions")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -418,6 +421,7 @@ impl AsLogoutSessionId for KillSessionId {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct KillParams {
|
||||
#[arg(help = "help.arg.session-ids")]
|
||||
ids: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -434,7 +438,9 @@ pub async fn kill<C: SessionAuthContext>(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ResetPasswordParams {
|
||||
#[arg(help = "help.arg.old-password")]
|
||||
old_password: Option<PasswordType>,
|
||||
#[arg(help = "help.arg.new-password")]
|
||||
new_password: Option<PasswordType>,
|
||||
}
|
||||
|
||||
@@ -447,13 +453,13 @@ async fn cli_reset_password(
|
||||
..
|
||||
}: HandlerArgs<CliContext>,
|
||||
) -> Result<(), RpcError> {
|
||||
let old_password = rpassword::prompt_password("Current Password: ")?;
|
||||
let old_password = rpassword::prompt_password(&t!("auth.prompt-current-password"))?;
|
||||
|
||||
let new_password = {
|
||||
let new_password = rpassword::prompt_password("New Password: ")?;
|
||||
if new_password != rpassword::prompt_password("Confirm: ")? {
|
||||
let new_password = rpassword::prompt_password(&t!("auth.prompt-new-password"))?;
|
||||
if new_password != rpassword::prompt_password(&t!("auth.prompt-confirm"))? {
|
||||
return Err(Error::new(
|
||||
eyre!("Passwords do not match"),
|
||||
eyre!("{}", t!("auth.passwords-do-not-match")),
|
||||
crate::ErrorKind::IncorrectPassword,
|
||||
)
|
||||
.into());
|
||||
@@ -486,7 +492,7 @@ pub async fn reset_password_impl(
|
||||
.with_kind(crate::ErrorKind::IncorrectPassword)?
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("Incorrect Password"),
|
||||
eyre!("{}", t!("auth.password-incorrect")),
|
||||
crate::ErrorKind::IncorrectPassword,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -33,11 +33,13 @@ use crate::version::VersionT;
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct BackupParams {
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
target_id: BackupTargetId,
|
||||
#[arg(long = "old-password")]
|
||||
#[arg(long = "old-password", help = "help.arg.old-backup-password")]
|
||||
old_password: Option<crate::auth::PasswordType>,
|
||||
#[arg(long = "package-ids")]
|
||||
#[arg(long = "package-ids", help = "help.arg.package-ids-to-backup")]
|
||||
package_ids: Option<Vec<PackageId>>,
|
||||
#[arg(help = "help.arg.backup-password")]
|
||||
password: crate::auth::PasswordType,
|
||||
}
|
||||
|
||||
@@ -69,8 +71,8 @@ impl BackupStatusGuard {
|
||||
db,
|
||||
None,
|
||||
NotificationLevel::Success,
|
||||
"Backup Complete".to_owned(),
|
||||
"Your backup has completed".to_owned(),
|
||||
t!("backup.bulk.complete-title").to_string(),
|
||||
t!("backup.bulk.complete-message").to_string(),
|
||||
BackupReport {
|
||||
server: ServerBackupReport {
|
||||
attempted: true,
|
||||
@@ -88,9 +90,8 @@ impl BackupStatusGuard {
|
||||
db,
|
||||
None,
|
||||
NotificationLevel::Warning,
|
||||
"Backup Complete".to_owned(),
|
||||
"Your backup has completed, but some package(s) failed to backup"
|
||||
.to_owned(),
|
||||
t!("backup.bulk.complete-title").to_string(),
|
||||
t!("backup.bulk.complete-with-failures").to_string(),
|
||||
BackupReport {
|
||||
server: ServerBackupReport {
|
||||
attempted: true,
|
||||
@@ -103,7 +104,7 @@ impl BackupStatusGuard {
|
||||
.await
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Backup Failed: {}", e);
|
||||
tracing::error!("{}", t!("backup.bulk.failed-error", error = e));
|
||||
tracing::debug!("{:?}", e);
|
||||
let err_string = e.to_string();
|
||||
db.mutate(|db| {
|
||||
@@ -111,8 +112,8 @@ impl BackupStatusGuard {
|
||||
db,
|
||||
None,
|
||||
NotificationLevel::Error,
|
||||
"Backup Failed".to_owned(),
|
||||
"Your backup failed to complete.".to_owned(),
|
||||
t!("backup.bulk.failed-title").to_string(),
|
||||
t!("backup.bulk.failed-message").to_string(),
|
||||
BackupReport {
|
||||
server: ServerBackupReport {
|
||||
attempted: true,
|
||||
@@ -224,7 +225,7 @@ fn assure_backing_up<'a>(
|
||||
.as_backup_progress_mut();
|
||||
if backing_up.transpose_ref().is_some() {
|
||||
return Err(Error::new(
|
||||
eyre!("Server is already backing up!"),
|
||||
eyre!("{}", t!("backup.bulk.already-backing-up")),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
@@ -303,7 +304,7 @@ async fn perform_backup(
|
||||
|
||||
let mut backup_guard = Arc::try_unwrap(backup_guard).map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("leaked reference to BackupMountGuard"),
|
||||
eyre!("{}", t!("backup.bulk.leaked-reference")),
|
||||
ErrorKind::Incoherent,
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -37,12 +37,12 @@ pub fn backup<C: Context>() -> ParentHandler<C> {
|
||||
"create",
|
||||
from_fn_async(backup_bulk::backup_all)
|
||||
.no_display()
|
||||
.with_about("Create backup for all packages")
|
||||
.with_about("about.create-backup-all-packages")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"target",
|
||||
target::target::<C>().with_about("Commands related to a backup target"),
|
||||
target::target::<C>().with_about("about.commands-backup-target"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ pub fn package_backup<C: Context>() -> ParentHandler<C> {
|
||||
"restore",
|
||||
from_fn_async(restore::restore_packages_rpc)
|
||||
.no_display()
|
||||
.with_about("Restore package(s) from backup")
|
||||
.with_about("about.restore-packages-from-backup")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,16 +23,19 @@ use crate::progress::ProgressUnits;
|
||||
use crate::s9pk::S9pk;
|
||||
use crate::service::service_map::DownloadInstallFuture;
|
||||
use crate::setup::SetupExecuteProgress;
|
||||
use crate::system::sync_kiosk;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::system::{save_language, sync_kiosk};
|
||||
use crate::util::serde::{IoFormat, Pem};
|
||||
use crate::{PLATFORM, PackageId};
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct RestorePackageParams {
|
||||
#[arg(help = "help.arg.package-ids")]
|
||||
pub ids: Vec<PackageId>,
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
pub target_id: BackupTargetId,
|
||||
#[arg(help = "help.arg.backup-password")]
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
@@ -63,7 +66,10 @@ pub async fn restore_packages_rpc(
|
||||
match async { res.await?.await }.await {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
tracing::error!("Error restoring package {}: {}", id, err);
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("backup.restore.package-error", id = id, error = err)
|
||||
);
|
||||
tracing::debug!("{:?}", err);
|
||||
}
|
||||
}
|
||||
@@ -75,10 +81,10 @@ pub async fn restore_packages_rpc(
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn recover_full_embassy(
|
||||
pub async fn recover_full_server(
|
||||
ctx: &SetupContext,
|
||||
disk_guid: Arc<String>,
|
||||
start_os_password: String,
|
||||
disk_guid: InternedString,
|
||||
password: String,
|
||||
recovery_source: TmpMountGuard,
|
||||
server_id: &str,
|
||||
recovery_password: &str,
|
||||
@@ -102,7 +108,7 @@ pub async fn recover_full_embassy(
|
||||
)?;
|
||||
|
||||
os_backup.account.password = argon2::hash_encoded(
|
||||
start_os_password.as_bytes(),
|
||||
password.as_bytes(),
|
||||
&rand::random::<[u8; 16]>()[..],
|
||||
&argon2::Config::rfc9106_low_mem(),
|
||||
)
|
||||
@@ -111,16 +117,32 @@ pub async fn recover_full_embassy(
|
||||
let kiosk = Some(kiosk.unwrap_or(true)).filter(|_| &*PLATFORM != "raspberrypi");
|
||||
sync_kiosk(kiosk).await?;
|
||||
|
||||
let language = ctx.language.peek(|a| a.clone());
|
||||
let keyboard = ctx.keyboard.peek(|a| a.clone());
|
||||
|
||||
if let Some(language) = &language {
|
||||
save_language(&**language).await?;
|
||||
}
|
||||
|
||||
if let Some(keyboard) = &keyboard {
|
||||
keyboard.save().await?;
|
||||
}
|
||||
|
||||
let db = ctx.db().await?;
|
||||
db.put(&ROOT, &Database::init(&os_backup.account, kiosk)?)
|
||||
.await?;
|
||||
db.put(
|
||||
&ROOT,
|
||||
&Database::init(&os_backup.account, kiosk, language, keyboard)?,
|
||||
)
|
||||
.await?;
|
||||
drop(db);
|
||||
|
||||
let init_result = init(&ctx.webserver, &ctx.config, init_phases).await?;
|
||||
let config = ctx.config.peek(|c| c.clone());
|
||||
|
||||
let init_result = init(&ctx.webserver, &config, init_phases).await?;
|
||||
|
||||
let rpc_ctx = RpcContext::init(
|
||||
&ctx.webserver,
|
||||
&ctx.config,
|
||||
&config,
|
||||
disk_guid.clone(),
|
||||
Some(init_result),
|
||||
rpc_ctx_phases,
|
||||
@@ -145,7 +167,10 @@ pub async fn recover_full_embassy(
|
||||
match async { res.await?.await }.await {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
tracing::error!("Error restoring package {}: {}", id, err);
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("backup.restore.package-error", id = id, error = err)
|
||||
);
|
||||
tracing::debug!("{:?}", err);
|
||||
}
|
||||
}
|
||||
@@ -155,7 +180,14 @@ pub async fn recover_full_embassy(
|
||||
.await;
|
||||
restore_phase.lock().await.complete();
|
||||
|
||||
Ok(((&os_backup.account).try_into()?, rpc_ctx))
|
||||
Ok((
|
||||
SetupResult {
|
||||
hostname: os_backup.account.hostname,
|
||||
root_ca: Pem(os_backup.account.root_ca_cert),
|
||||
needs_restart: ctx.install_rootfs.peek(|a| a.is_some()),
|
||||
},
|
||||
rpc_ctx,
|
||||
))
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, backup_guard))]
|
||||
|
||||
@@ -52,21 +52,21 @@ pub fn cifs<C: Context>() -> ParentHandler<C> {
|
||||
"add",
|
||||
from_fn_async(add)
|
||||
.no_display()
|
||||
.with_about("Add a new backup target")
|
||||
.with_about("about.add-new-backup-target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"update",
|
||||
from_fn_async(update)
|
||||
.no_display()
|
||||
.with_about("Update an existing backup target")
|
||||
.with_about("about.update-existing-backup-target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"remove",
|
||||
from_fn_async(remove)
|
||||
.no_display()
|
||||
.with_about("Remove an existing backup target")
|
||||
.with_about("about.remove-existing-backup-target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -75,9 +75,13 @@ pub fn cifs<C: Context>() -> ParentHandler<C> {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct AddParams {
|
||||
#[arg(help = "help.arg.cifs-hostname")]
|
||||
pub hostname: String,
|
||||
#[arg(help = "help.arg.cifs-path")]
|
||||
pub path: PathBuf,
|
||||
#[arg(help = "help.arg.cifs-username")]
|
||||
pub username: String,
|
||||
#[arg(help = "help.arg.cifs-password")]
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
@@ -130,10 +134,15 @@ pub async fn add(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct UpdateParams {
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
pub id: BackupTargetId,
|
||||
#[arg(help = "help.arg.cifs-hostname")]
|
||||
pub hostname: String,
|
||||
#[arg(help = "help.arg.cifs-path")]
|
||||
pub path: PathBuf,
|
||||
#[arg(help = "help.arg.cifs-username")]
|
||||
pub username: String,
|
||||
#[arg(help = "help.arg.cifs-password")]
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
@@ -151,7 +160,7 @@ pub async fn update(
|
||||
id
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", id),
|
||||
eyre!("{}", t!("backup.target.cifs.target-not-found", id = id)),
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
@@ -171,7 +180,7 @@ pub async fn update(
|
||||
.as_idx_mut(&id)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", BackupTargetId::Cifs { id }),
|
||||
eyre!("{}", t!("backup.target.cifs.target-not-found", id = BackupTargetId::Cifs { id })),
|
||||
ErrorKind::NotFound,
|
||||
)
|
||||
})?
|
||||
@@ -195,6 +204,7 @@ pub async fn update(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct RemoveParams {
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
pub id: BackupTargetId,
|
||||
}
|
||||
|
||||
@@ -203,7 +213,7 @@ pub async fn remove(ctx: RpcContext, RemoveParams { id }: RemoveParams) -> Resul
|
||||
id
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", id),
|
||||
eyre!("{}", t!("backup.target.cifs.target-not-found", id = id)),
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
@@ -220,7 +230,7 @@ pub fn load(db: &DatabaseModel, id: u32) -> Result<Cifs, Error> {
|
||||
.as_idx(&id)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", id),
|
||||
eyre!("{}", t!("backup.target.cifs.target-not-found-id", id = id)),
|
||||
ErrorKind::NotFound,
|
||||
)
|
||||
})?
|
||||
|
||||
@@ -143,13 +143,13 @@ pub fn target<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"cifs",
|
||||
cifs::cifs::<C>().with_about("Add, remove, or update a backup target"),
|
||||
cifs::cifs::<C>().with_about("about.add-remove-update-backup-target"),
|
||||
)
|
||||
.subcommand(
|
||||
"list",
|
||||
from_fn_async(list)
|
||||
.with_display_serializable()
|
||||
.with_about("List existing backup targets")
|
||||
.with_about("about.list-existing-backup-targets")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -159,20 +159,20 @@ pub fn target<C: Context>() -> ParentHandler<C> {
|
||||
.with_custom_display_fn::<CliContext, _>(|params, info| {
|
||||
display_backup_info(params.params, info)
|
||||
})
|
||||
.with_about("Display package backup information")
|
||||
.with_about("about.display-package-backup-information")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"mount",
|
||||
from_fn_async(mount)
|
||||
.with_about("Mount backup target")
|
||||
.with_about("about.mount-backup-target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"umount",
|
||||
from_fn_async(umount)
|
||||
.no_display()
|
||||
.with_about("Unmount backup target")
|
||||
.with_about("about.unmount-backup-target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -268,8 +268,11 @@ fn display_backup_info(params: WithIoFormat<InfoParams>, info: BackupInfo) -> Re
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct InfoParams {
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
target_id: BackupTargetId,
|
||||
#[arg(help = "help.arg.server-id")]
|
||||
server_id: String,
|
||||
#[arg(help = "help.arg.backup-password")]
|
||||
password: String,
|
||||
}
|
||||
|
||||
@@ -305,11 +308,13 @@ lazy_static::lazy_static! {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct MountParams {
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
target_id: BackupTargetId,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.server-id")]
|
||||
server_id: Option<String>,
|
||||
#[arg(help = "help.arg.backup-password")]
|
||||
password: String, // TODO: rpassword
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.allow-partial-backup")]
|
||||
allow_partial: bool,
|
||||
}
|
||||
|
||||
@@ -385,6 +390,7 @@ pub async fn mount(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct UmountParams {
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
target_id: Option<BackupTargetId>,
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||
|cfg: ContainerClientConfig| Ok(ContainerCliContext::init(cfg)),
|
||||
crate::service::effects::handler(),
|
||||
)
|
||||
.mutate_command(super::translate_cli)
|
||||
.run(args)
|
||||
{
|
||||
match e.data {
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
use rust_i18n::t;
|
||||
|
||||
pub fn renamed(old: &str, new: &str) -> ! {
|
||||
eprintln!("{old} has been renamed to {new}");
|
||||
eprintln!(
|
||||
"{}",
|
||||
t!("bins.deprecated.renamed", old = old, new = new)
|
||||
);
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
pub fn removed(name: &str) -> ! {
|
||||
eprintln!("{name} has been removed");
|
||||
eprintln!("{}", t!("bins.deprecated.removed", name = name));
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::collections::{BTreeMap, VecDeque};
|
||||
use std::ffi::OsString;
|
||||
use std::path::Path;
|
||||
|
||||
use rust_i18n::t;
|
||||
|
||||
pub mod container_cli;
|
||||
pub mod deprecated;
|
||||
pub mod registry;
|
||||
@@ -10,6 +12,85 @@ pub mod start_init;
|
||||
pub mod startd;
|
||||
pub mod tunnel;
|
||||
|
||||
pub fn set_locale_from_env() {
|
||||
let lang = std::env::var("LANG").ok();
|
||||
let lang = lang
|
||||
.as_deref()
|
||||
.map_or("C", |l| l.strip_suffix(".UTF-8").unwrap_or(l));
|
||||
set_locale(lang)
|
||||
}
|
||||
|
||||
pub fn set_locale(lang: &str) {
|
||||
let mut best = None;
|
||||
let prefix = lang.split_inclusive("_").next().unwrap();
|
||||
for l in rust_i18n::available_locales!() {
|
||||
if l == lang {
|
||||
best = Some(l);
|
||||
break;
|
||||
}
|
||||
if best.is_none() && l.starts_with(prefix) {
|
||||
best = Some(l);
|
||||
}
|
||||
}
|
||||
rust_i18n::set_locale(best.unwrap_or(lang));
|
||||
}
|
||||
|
||||
pub fn translate_cli(mut cmd: clap::Command) -> clap::Command {
|
||||
fn translate(s: impl std::fmt::Display) -> String {
|
||||
t!(s.to_string()).into_owned()
|
||||
}
|
||||
if let Some(s) = cmd.get_about() {
|
||||
let s = translate(s);
|
||||
cmd = cmd.about(s);
|
||||
}
|
||||
if let Some(s) = cmd.get_long_about() {
|
||||
let s = translate(s);
|
||||
cmd = cmd.long_about(s);
|
||||
}
|
||||
if let Some(s) = cmd.get_before_help() {
|
||||
let s = translate(s);
|
||||
cmd = cmd.before_help(s);
|
||||
}
|
||||
if let Some(s) = cmd.get_before_long_help() {
|
||||
let s = translate(s);
|
||||
cmd = cmd.before_long_help(s);
|
||||
}
|
||||
if let Some(s) = cmd.get_after_help() {
|
||||
let s = translate(s);
|
||||
cmd = cmd.after_help(s);
|
||||
}
|
||||
if let Some(s) = cmd.get_after_long_help() {
|
||||
let s = translate(s);
|
||||
cmd = cmd.after_long_help(s);
|
||||
}
|
||||
|
||||
let arg_ids = cmd
|
||||
.get_arguments()
|
||||
.map(|a| a.get_id().clone())
|
||||
.collect::<Vec<_>>();
|
||||
for id in arg_ids {
|
||||
cmd = cmd.mut_arg(id, |arg| {
|
||||
let arg = if let Some(s) = arg.get_help() {
|
||||
let s = translate(s);
|
||||
arg.help(s)
|
||||
} else {
|
||||
arg
|
||||
};
|
||||
if let Some(s) = arg.get_long_help() {
|
||||
let s = translate(s);
|
||||
arg.long_help(s)
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
});
|
||||
}
|
||||
for cmd in cmd.get_subcommands_mut() {
|
||||
*cmd = translate_cli(cmd.clone());
|
||||
}
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MultiExecutable {
|
||||
default: Option<&'static str>,
|
||||
@@ -58,7 +139,7 @@ impl MultiExecutable {
|
||||
if let Some((name, _)) = self.bins.get_key_value(name) {
|
||||
self.default = Some(*name);
|
||||
} else {
|
||||
panic!("{name} does not exist in MultiExecutable");
|
||||
panic!("{}", t!("bins.mod.does-not-exist", name = name));
|
||||
}
|
||||
self
|
||||
}
|
||||
@@ -68,6 +149,8 @@ impl MultiExecutable {
|
||||
}
|
||||
|
||||
pub fn execute(&self) {
|
||||
set_locale_from_env();
|
||||
|
||||
let mut popped = Vec::with_capacity(2);
|
||||
let mut args = std::env::args_os().collect::<VecDeque<_>>();
|
||||
|
||||
@@ -96,11 +179,15 @@ impl MultiExecutable {
|
||||
}
|
||||
let args = std::env::args().collect::<VecDeque<_>>();
|
||||
eprintln!(
|
||||
"unknown executable: {}",
|
||||
args.get(1)
|
||||
.or_else(|| args.get(0))
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("N/A")
|
||||
"{}",
|
||||
t!(
|
||||
"bins.mod.unknown-executable",
|
||||
name = args
|
||||
.get(1)
|
||||
.or_else(|| args.get(0))
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("N/A")
|
||||
)
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::ffi::OsString;
|
||||
use clap::Parser;
|
||||
use futures::FutureExt;
|
||||
use rpc_toolkit::CliApp;
|
||||
use rust_i18n::t;
|
||||
use tokio::signal::unix::signal;
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -77,7 +78,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("failed to initialize runtime");
|
||||
.expect(&t!("bins.registry.failed-to-initialize-runtime"));
|
||||
rt.block_on(inner_main(&config))
|
||||
};
|
||||
|
||||
@@ -99,6 +100,7 @@ pub fn cli(args: impl IntoIterator<Item = OsString>) {
|
||||
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||
crate::registry::registry_api(),
|
||||
)
|
||||
.mutate_command(super::translate_cli)
|
||||
.run(args)
|
||||
{
|
||||
match e.data {
|
||||
|
||||
@@ -19,6 +19,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||
crate::main_api(),
|
||||
)
|
||||
.mutate_command(super::translate_cli)
|
||||
.run(args)
|
||||
{
|
||||
match e.data {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::config::ServerConfig;
|
||||
use crate::context::rpc::InitRpcContextPhases;
|
||||
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
||||
use crate::context::{DiagnosticContext, InitContext, RpcContext, SetupContext};
|
||||
use crate::disk::REPAIR_DISK_PATH;
|
||||
use crate::disk::fsck::RepairStrategy;
|
||||
use crate::disk::main::DEFAULT_PASSWORD;
|
||||
@@ -27,7 +25,13 @@ async fn setup_or_init(
|
||||
if let Some(firmware) = check_for_firmware_update()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::warn!("Error checking for firmware update: {e}");
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"bins.start-init.error-checking-firmware",
|
||||
error = e.to_string()
|
||||
)
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
})
|
||||
.ok()
|
||||
@@ -35,14 +39,21 @@ async fn setup_or_init(
|
||||
{
|
||||
let init_ctx = InitContext::init(config).await?;
|
||||
let handle = &init_ctx.progress;
|
||||
let mut update_phase = handle.add_phase("Updating Firmware".into(), Some(10));
|
||||
let mut reboot_phase = handle.add_phase("Rebooting".into(), Some(1));
|
||||
let mut update_phase =
|
||||
handle.add_phase(t!("bins.start-init.updating-firmware").into(), Some(10));
|
||||
let mut reboot_phase = handle.add_phase(t!("bins.start-init.rebooting").into(), Some(1));
|
||||
|
||||
server.serve_ui_for(init_ctx);
|
||||
|
||||
update_phase.start();
|
||||
if let Err(e) = update_firmware(firmware).await {
|
||||
tracing::warn!("Error performing firmware update: {e}");
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"bins.start-init.error-firmware-update",
|
||||
error = e.to_string()
|
||||
)
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
} else {
|
||||
update_phase.complete();
|
||||
@@ -79,40 +90,11 @@ async fn setup_or_init(
|
||||
.invoke(crate::ErrorKind::OpenSsl)
|
||||
.await?;
|
||||
|
||||
if tokio::fs::metadata("/run/live/medium").await.is_ok() {
|
||||
Command::new("sed")
|
||||
.arg("-i")
|
||||
.arg("s/PasswordAuthentication no/PasswordAuthentication yes/g")
|
||||
.arg("/etc/ssh/sshd_config")
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
Command::new("systemctl")
|
||||
.arg("reload")
|
||||
.arg("ssh")
|
||||
.invoke(crate::ErrorKind::OpenSsh)
|
||||
.await?;
|
||||
|
||||
let ctx = InstallContext::init().await?;
|
||||
|
||||
server.serve_ui_for(ctx.clone());
|
||||
|
||||
ctx.shutdown
|
||||
.subscribe()
|
||||
.recv()
|
||||
.await
|
||||
.expect("context dropped");
|
||||
|
||||
return Ok(Err(Shutdown {
|
||||
disk_guid: None,
|
||||
restart: true,
|
||||
}));
|
||||
}
|
||||
|
||||
if tokio::fs::metadata("/media/startos/config/disk.guid")
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
let ctx = SetupContext::init(server, config)?;
|
||||
let ctx = SetupContext::init(server, config.clone())?;
|
||||
|
||||
server.serve_ui_for(ctx.clone());
|
||||
|
||||
@@ -127,7 +109,13 @@ async fn setup_or_init(
|
||||
.invoke(ErrorKind::NotFound)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to kill kiosk: {}", e);
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!(
|
||||
"bins.start-init.failed-to-kill-kiosk",
|
||||
error = e.to_string()
|
||||
)
|
||||
);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
|
||||
@@ -136,7 +124,7 @@ async fn setup_or_init(
|
||||
Some(Err(e)) => return Err(e.clone_output()),
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
eyre!("Setup mode exited before setup completed"),
|
||||
eyre!("{}", t!("bins.start-init.setup-mode-exited")),
|
||||
ErrorKind::Unknown,
|
||||
));
|
||||
}
|
||||
@@ -146,7 +134,8 @@ async fn setup_or_init(
|
||||
let handle = init_ctx.progress.clone();
|
||||
let err_channel = init_ctx.error.clone();
|
||||
|
||||
let mut disk_phase = handle.add_phase("Opening data drive".into(), Some(10));
|
||||
let mut disk_phase =
|
||||
handle.add_phase(t!("bins.start-init.opening-data-drive").into(), Some(10));
|
||||
let init_phases = InitPhases::new(&handle);
|
||||
let rpc_ctx_phases = InitRpcContextPhases::new(&handle);
|
||||
|
||||
@@ -156,9 +145,9 @@ async fn setup_or_init(
|
||||
disk_phase.start();
|
||||
let guid_string = tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
||||
.await?;
|
||||
let disk_guid = Arc::new(String::from(guid_string.trim()));
|
||||
let disk_guid = InternedString::intern(guid_string.trim());
|
||||
let requires_reboot = crate::disk::main::import(
|
||||
&**disk_guid,
|
||||
&*disk_guid,
|
||||
DATA_DIR,
|
||||
if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() {
|
||||
RepairStrategy::Aggressive
|
||||
@@ -178,11 +167,12 @@ async fn setup_or_init(
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, REPAIR_DISK_PATH))?;
|
||||
}
|
||||
disk_phase.complete();
|
||||
tracing::info!("Loaded Disk");
|
||||
tracing::info!("{}", t!("bins.start-init.loaded-disk"));
|
||||
|
||||
if requires_reboot.0 {
|
||||
tracing::info!("Rebooting...");
|
||||
let mut reboot_phase = handle.add_phase("Rebooting".into(), Some(1));
|
||||
tracing::info!("{}", t!("bins.start-init.rebooting"));
|
||||
let mut reboot_phase =
|
||||
handle.add_phase(t!("bins.start-init.rebooting").into(), Some(1));
|
||||
reboot_phase.start();
|
||||
return Ok(Err(Shutdown {
|
||||
disk_guid: Some(disk_guid),
|
||||
@@ -236,11 +226,10 @@ pub async fn main(
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
Some(Arc::new(
|
||||
Some(InternedString::intern(
|
||||
tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
||||
.await?
|
||||
.trim()
|
||||
.to_owned(),
|
||||
.trim(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::cmp::max;
|
||||
use std::ffi::OsString;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use rust_i18n::t;
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
use tokio::signal::unix::signal;
|
||||
use tracing::instrument;
|
||||
@@ -15,11 +15,11 @@ use crate::context::{DiagnosticContext, InitContext, RpcContext};
|
||||
use crate::net::gateway::{BindTcp, SelfContainedNetworkInterfaceListener, UpgradableListener};
|
||||
use crate::net::static_server::refresher;
|
||||
use crate::net::web_server::{Acceptor, WebServer};
|
||||
use crate::prelude::*;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::system::launch_metrics_task;
|
||||
use crate::util::io::append_file;
|
||||
use crate::util::logger::LOGGER;
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn inner_main(
|
||||
@@ -53,11 +53,10 @@ async fn inner_main(
|
||||
let ctx = RpcContext::init(
|
||||
&server.acceptor_setter(),
|
||||
config,
|
||||
Arc::new(
|
||||
InternedString::intern(
|
||||
tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
||||
.await?
|
||||
.trim()
|
||||
.to_owned(),
|
||||
.trim(),
|
||||
),
|
||||
None,
|
||||
rpc_ctx_phases,
|
||||
@@ -114,11 +113,11 @@ async fn inner_main(
|
||||
metrics_task
|
||||
.map_err(|e| {
|
||||
Error::new(
|
||||
eyre!("{}", e).wrap_err("Metrics daemon panicked!"),
|
||||
eyre!("{}", e).wrap_err(t!("bins.startd.metrics-daemon-panicked").to_string()),
|
||||
ErrorKind::Unknown,
|
||||
)
|
||||
})
|
||||
.map_ok(|_| tracing::debug!("Metrics daemon Shutdown"))
|
||||
.map_ok(|_| tracing::debug!("{}", t!("bins.startd.metrics-daemon-shutdown")))
|
||||
.await?;
|
||||
|
||||
let shutdown = shutdown_recv
|
||||
@@ -146,7 +145,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||
.worker_threads(max(1, num_cpus::get()))
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("failed to initialize runtime");
|
||||
.expect(&t!("bins.startd.failed-to-initialize-runtime"));
|
||||
let res = rt.block_on(async {
|
||||
let mut server = WebServer::new(
|
||||
Acceptor::bind_upgradable(SelfContainedNetworkInterfaceListener::bind(BindTcp, 80)),
|
||||
@@ -167,11 +166,10 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
Some(Arc::new(
|
||||
Some(InternedString::intern(
|
||||
tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
||||
.await?
|
||||
.trim()
|
||||
.to_owned(),
|
||||
.trim(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::time::Duration;
|
||||
use clap::Parser;
|
||||
use futures::FutureExt;
|
||||
use rpc_toolkit::CliApp;
|
||||
use rust_i18n::t;
|
||||
use tokio::signal::unix::signal;
|
||||
use tracing::instrument;
|
||||
use visit_rs::Visit;
|
||||
@@ -70,7 +71,7 @@ async fn inner_main(config: &TunnelConfig) -> Result<(), Error> {
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("error adding ssl listener: {e}");
|
||||
tracing::error!("{}", t!("bins.tunnel.error-adding-ssl-listener", error = e.to_string()));
|
||||
tracing::debug!("{e:?}");
|
||||
|
||||
false
|
||||
@@ -92,7 +93,7 @@ async fn inner_main(config: &TunnelConfig) -> Result<(), Error> {
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("error updating webserver bind: {e}");
|
||||
tracing::error!("{}", t!("bins.tunnel.error-updating-webserver-bind", error = e.to_string()));
|
||||
tracing::debug!("{e:?}");
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
}
|
||||
@@ -157,7 +158,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("failed to initialize runtime");
|
||||
.expect(&t!("bins.tunnel.failed-to-initialize-runtime"));
|
||||
rt.block_on(inner_main(&config))
|
||||
};
|
||||
|
||||
@@ -179,6 +180,7 @@ pub fn cli(args: impl IntoIterator<Item = OsString>) {
|
||||
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||
crate::tunnel::api::tunnel_api(),
|
||||
)
|
||||
.mutate_command(super::translate_cli)
|
||||
.run(args)
|
||||
{
|
||||
match e.data {
|
||||
|
||||
@@ -23,7 +23,7 @@ use tracing::instrument;
|
||||
|
||||
use super::setup::CURRENT_SECRET;
|
||||
use crate::context::config::{ClientConfig, local_config_path};
|
||||
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
||||
use crate::context::{DiagnosticContext, InitContext, RpcContext, SetupContext};
|
||||
use crate::developer::{OS_DEVELOPER_KEY_PATH, default_developer_key_path};
|
||||
use crate::middleware::auth::local::LocalAuthContext;
|
||||
use crate::prelude::*;
|
||||
@@ -166,14 +166,14 @@ impl CliContext {
|
||||
.with_kind(crate::ErrorKind::Pem)?;
|
||||
let secret = ed25519_dalek::SecretKey::try_from(&pair.secret_key[..]).map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("pkcs8 key is of incorrect length"),
|
||||
eyre!("{}", t!("context.cli.pkcs8-key-incorrect-length")),
|
||||
ErrorKind::OpenSsl,
|
||||
)
|
||||
})?;
|
||||
return Ok(secret.into())
|
||||
}
|
||||
Err(Error::new(
|
||||
eyre!("Developer Key does not exist! Please run `start-cli init-key` before running this command."),
|
||||
eyre!("{}", t!("context.cli.developer-key-does-not-exist")),
|
||||
crate::ErrorKind::Uninitialized
|
||||
))
|
||||
})
|
||||
@@ -189,14 +189,14 @@ impl CliContext {
|
||||
"http" => "ws",
|
||||
_ => {
|
||||
return Err(Error::new(
|
||||
eyre!("Cannot parse scheme from base URL"),
|
||||
eyre!("{}", t!("context.cli.cannot-parse-scheme-from-base-url")),
|
||||
crate::ErrorKind::ParseUrl,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
};
|
||||
url.set_scheme(ws_scheme)
|
||||
.map_err(|_| Error::new(eyre!("Cannot set URL scheme"), crate::ErrorKind::ParseUrl))?;
|
||||
.map_err(|_| Error::new(eyre!("{}", t!("context.cli.cannot-set-url-scheme")), crate::ErrorKind::ParseUrl))?;
|
||||
url.path_segments_mut()
|
||||
.map_err(|_| eyre!("Url cannot be base"))
|
||||
.with_kind(crate::ErrorKind::ParseUrl)?
|
||||
@@ -394,22 +394,3 @@ impl CallRemote<SetupContext> for CliContext {
|
||||
.await
|
||||
}
|
||||
}
|
||||
impl CallRemote<InstallContext> for CliContext {
|
||||
async fn call_remote(
|
||||
&self,
|
||||
method: &str,
|
||||
_: OrdMap<&'static str, Value>,
|
||||
params: Value,
|
||||
_: Empty,
|
||||
) -> Result<Value, RpcError> {
|
||||
crate::middleware::auth::signature::call_remote(
|
||||
self,
|
||||
self.rpc_url.clone(),
|
||||
HeaderMap::new(),
|
||||
self.rpc_url.host_str(),
|
||||
method,
|
||||
params,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,27 +58,27 @@ pub trait ContextConfig: DeserializeOwned + Default {
|
||||
#[command(rename_all = "kebab-case")]
|
||||
#[command(version = crate::version::Current::default().semver().to_string())]
|
||||
pub struct ClientConfig {
|
||||
#[arg(short = 'c', long)]
|
||||
#[arg(short = 'c', long, help = "help.arg.config-file-path")]
|
||||
pub config: Option<PathBuf>,
|
||||
#[arg(short = 'H', long)]
|
||||
#[arg(short = 'H', long, help = "help.arg.host-url")]
|
||||
pub host: Option<Url>,
|
||||
#[arg(short = 'r', long)]
|
||||
#[arg(short = 'r', long, help = "help.arg.registry-url")]
|
||||
pub registry: Option<Url>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.registry-hostname")]
|
||||
pub registry_hostname: Option<Vec<InternedString>>,
|
||||
#[arg(skip)]
|
||||
pub registry_listen: Option<SocketAddr>,
|
||||
#[arg(short = 't', long)]
|
||||
#[arg(short = 't', long, help = "help.arg.tunnel-address")]
|
||||
pub tunnel: Option<SocketAddr>,
|
||||
#[arg(skip)]
|
||||
pub tunnel_listen: Option<SocketAddr>,
|
||||
#[arg(short = 'p', long)]
|
||||
#[arg(short = 'p', long, help = "help.arg.proxy-url")]
|
||||
pub proxy: Option<Url>,
|
||||
#[arg(skip)]
|
||||
pub socks_listen: Option<SocketAddr>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.cookie-path")]
|
||||
pub cookie_path: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.developer-key-path")]
|
||||
pub developer_key_path: Option<PathBuf>,
|
||||
}
|
||||
impl ContextConfig for ClientConfig {
|
||||
@@ -109,21 +109,19 @@ impl ClientConfig {
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ServerConfig {
|
||||
#[arg(short, long)]
|
||||
#[arg(short, long, help = "help.arg.config-file-path")]
|
||||
pub config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
pub ethernet_interface: Option<String>,
|
||||
#[arg(skip)]
|
||||
pub os_partitions: Option<OsPartitionInfo>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.socks-listen-address")]
|
||||
pub socks_listen: Option<SocketAddr>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.revision-cache-size")]
|
||||
pub revision_cache_size: Option<usize>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.disable-encryption")]
|
||||
pub disable_encryption: Option<bool>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.multi-arch-s9pks")]
|
||||
pub multi_arch_s9pks: Option<bool>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.developer-key-path")]
|
||||
pub developer_key_path: Option<PathBuf>,
|
||||
}
|
||||
impl ContextConfig for ServerConfig {
|
||||
@@ -131,7 +129,6 @@ impl ContextConfig for ServerConfig {
|
||||
self.config.take()
|
||||
}
|
||||
fn merge_with(&mut self, other: Self) {
|
||||
self.ethernet_interface = self.ethernet_interface.take().or(other.ethernet_interface);
|
||||
self.os_partitions = self.os_partitions.take().or(other.os_partitions);
|
||||
self.socks_listen = self.socks_listen.take().or(other.socks_listen);
|
||||
self.revision_cache_size = self
|
||||
|
||||
@@ -6,15 +6,15 @@ use rpc_toolkit::yajrc::RpcError;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::Error;
|
||||
use crate::context::config::ServerConfig;
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::RpcContinuations;
|
||||
use crate::shutdown::Shutdown;
|
||||
|
||||
pub struct DiagnosticContextSeed {
|
||||
pub shutdown: Sender<Shutdown>,
|
||||
pub error: Arc<RpcError>,
|
||||
pub disk_guid: Option<Arc<String>>,
|
||||
pub disk_guid: Option<InternedString>,
|
||||
pub rpc_continuations: RpcContinuations,
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ impl DiagnosticContext {
|
||||
#[instrument(skip_all)]
|
||||
pub fn init(
|
||||
_config: &ServerConfig,
|
||||
disk_guid: Option<Arc<String>>,
|
||||
disk_guid: Option<InternedString>,
|
||||
error: Error,
|
||||
) -> Result<Self, Error> {
|
||||
tracing::error!("Error: {}: Starting diagnostic UI", error);
|
||||
tracing::error!("{}", t!("context.diagnostic.starting-diagnostic-ui", error = error));
|
||||
tracing::debug!("{:?}", error);
|
||||
|
||||
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use rpc_toolkit::Context;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::Error;
|
||||
use crate::net::utils::find_eth_iface;
|
||||
use crate::rpc_continuations::RpcContinuations;
|
||||
|
||||
pub struct InstallContextSeed {
|
||||
pub ethernet_interface: String,
|
||||
pub shutdown: Sender<()>,
|
||||
pub rpc_continuations: RpcContinuations,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InstallContext(Arc<InstallContextSeed>);
|
||||
impl InstallContext {
|
||||
#[instrument(skip_all)]
|
||||
pub async fn init() -> Result<Self, Error> {
|
||||
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
||||
Ok(Self(Arc::new(InstallContextSeed {
|
||||
ethernet_interface: find_eth_iface().await?,
|
||||
shutdown,
|
||||
rpc_continuations: RpcContinuations::new(),
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<RpcContinuations> for InstallContext {
|
||||
fn as_ref(&self) -> &RpcContinuations {
|
||||
&self.rpc_continuations
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for InstallContext {}
|
||||
impl Deref for InstallContext {
|
||||
type Target = InstallContextSeed;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,11 @@ pub mod cli;
|
||||
pub mod config;
|
||||
pub mod diagnostic;
|
||||
pub mod init;
|
||||
pub mod install;
|
||||
pub mod rpc;
|
||||
pub mod setup;
|
||||
|
||||
pub use cli::CliContext;
|
||||
pub use diagnostic::DiagnosticContext;
|
||||
pub use init::InitContext;
|
||||
pub use install::InstallContext;
|
||||
pub use rpc::RpcContext;
|
||||
pub use setup::SetupContext;
|
||||
|
||||
@@ -60,7 +60,7 @@ pub struct RpcContextSeed {
|
||||
pub os_partitions: OsPartitionInfo,
|
||||
pub wifi_interface: Option<String>,
|
||||
pub ethernet_interface: String,
|
||||
pub disk_guid: Arc<String>,
|
||||
pub disk_guid: InternedString,
|
||||
pub ephemeral_sessions: SyncMutex<Sessions>,
|
||||
pub db: TypedPatchDb<Database>,
|
||||
pub sync_db: watch::Sender<u64>,
|
||||
@@ -84,7 +84,7 @@ pub struct RpcContextSeed {
|
||||
}
|
||||
impl Drop for RpcContextSeed {
|
||||
fn drop(&mut self) {
|
||||
tracing::info!("RpcContext is dropped");
|
||||
tracing::info!("{}", t!("context.rpc.rpc-context-dropped"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ impl RpcContext {
|
||||
pub async fn init(
|
||||
webserver: &WebServerAcceptorSetter<UpgradableListener>,
|
||||
config: &ServerConfig,
|
||||
disk_guid: Arc<String>,
|
||||
disk_guid: InternedString,
|
||||
init_result: Option<InitResult>,
|
||||
InitRpcContextPhases {
|
||||
mut load_db,
|
||||
@@ -155,7 +155,7 @@ impl RpcContext {
|
||||
let peek = db.peek().await;
|
||||
let account = AccountInfo::load(&peek)?;
|
||||
load_db.complete();
|
||||
tracing::info!("Opened PatchDB");
|
||||
tracing::info!("{}", t!("context.rpc.opened-patchdb"));
|
||||
|
||||
init_net_ctrl.start();
|
||||
let (net_controller, os_net_service) = if let Some(InitResult {
|
||||
@@ -172,15 +172,15 @@ impl RpcContext {
|
||||
(net_ctrl, os_net_service)
|
||||
};
|
||||
init_net_ctrl.complete();
|
||||
tracing::info!("Initialized Net Controller");
|
||||
tracing::info!("{}", t!("context.rpc.initialized-net-controller"));
|
||||
|
||||
if PLATFORM.ends_with("-nonfree") {
|
||||
if let Err(e) = Command::new("nvidia-smi")
|
||||
.invoke(ErrorKind::ParseSysInfo)
|
||||
.await
|
||||
{
|
||||
tracing::warn!("nvidia-smi: {e}");
|
||||
tracing::info!("The above warning can be ignored if no NVIDIA card is present");
|
||||
tracing::warn!("{}", t!("context.rpc.nvidia-smi-error", error = e));
|
||||
tracing::info!("{}", t!("context.rpc.nvidia-warning-can-be-ignored"));
|
||||
} else {
|
||||
async {
|
||||
let version: InternedString = String::from_utf8(
|
||||
@@ -279,7 +279,7 @@ impl RpcContext {
|
||||
.arg("100000")
|
||||
.invoke(ErrorKind::Filesystem)
|
||||
.await?;
|
||||
tmp.unmount_and_delete().await?;
|
||||
// tmp.unmount_and_delete().await?;
|
||||
}
|
||||
BlockDev::new(&sqfs)
|
||||
.mount(NVIDIA_OVERLAY_PATH, ReadOnly)
|
||||
@@ -335,16 +335,12 @@ impl RpcContext {
|
||||
is_closed: AtomicBool::new(false),
|
||||
os_partitions: config.os_partitions.clone().ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("OS Partition Information Missing"),
|
||||
eyre!("{}", t!("context.rpc.os-partition-info-missing")),
|
||||
ErrorKind::Filesystem,
|
||||
)
|
||||
})?,
|
||||
wifi_interface: wifi_interface.clone(),
|
||||
ethernet_interface: if let Some(eth) = config.ethernet_interface.clone() {
|
||||
eth
|
||||
} else {
|
||||
find_eth_iface().await?
|
||||
},
|
||||
ethernet_interface: find_eth_iface().await?,
|
||||
disk_guid,
|
||||
ephemeral_sessions: SyncMutex::new(Sessions::new()),
|
||||
sync_db: watch::Sender::new(db.sequence().await),
|
||||
@@ -369,9 +365,9 @@ impl RpcContext {
|
||||
current_secret: Arc::new(
|
||||
Jwk::generate_ec_key(josekit::jwk::alg::ec::EcCurve::P256).map_err(|e| {
|
||||
tracing::debug!("{:?}", e);
|
||||
tracing::error!("Couldn't generate ec key");
|
||||
tracing::error!("{}", t!("context.rpc.couldnt-generate-ec-key"));
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("Couldn't generate ec key"),
|
||||
color_eyre::eyre::eyre!("{}", t!("context.rpc.couldnt-generate-ec-key")),
|
||||
crate::ErrorKind::Unknown,
|
||||
)
|
||||
})?,
|
||||
@@ -386,10 +382,10 @@ impl RpcContext {
|
||||
|
||||
let res = Self(seed.clone());
|
||||
res.cleanup_and_initialize(cleanup_init).await?;
|
||||
tracing::info!("Cleaned up transient states");
|
||||
tracing::info!("{}", t!("context.rpc.cleaned-up-transient-states"));
|
||||
|
||||
crate::version::post_init(&res, run_migrations).await?;
|
||||
tracing::info!("Completed migrations");
|
||||
tracing::info!("{}", t!("context.rpc.completed-migrations"));
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -398,7 +394,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!("RpcContext is shutdown");
|
||||
tracing::info!("{}", t!("context.rpc.rpc-context-shutdown"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -467,7 +463,7 @@ impl RpcContext {
|
||||
.await
|
||||
.result
|
||||
{
|
||||
tracing::error!("Error in session cleanup cron: {e}");
|
||||
tracing::error!("{}", t!("context.rpc.error-in-session-cleanup-cron", error = e));
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::time::Duration;
|
||||
use futures::{Future, StreamExt};
|
||||
use imbl_value::InternedString;
|
||||
use josekit::jwk::Jwk;
|
||||
use openssl::x509::X509;
|
||||
use patch_db::PatchDb;
|
||||
use rpc_toolkit::Context;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -15,10 +16,9 @@ use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::MAIN_DATA;
|
||||
use crate::account::AccountInfo;
|
||||
use crate::context::RpcContext;
|
||||
use crate::context::config::ServerConfig;
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::disk::mount::guard::{MountGuard, TmpMountGuard};
|
||||
use crate::hostname::Hostname;
|
||||
use crate::net::gateway::UpgradableListener;
|
||||
use crate::net::web_server::{WebServer, WebServerAcceptorSetter};
|
||||
@@ -27,12 +27,15 @@ use crate::progress::FullProgressTracker;
|
||||
use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations};
|
||||
use crate::setup::SetupProgress;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::system::KeyboardOptions;
|
||||
use crate::util::future::NonDetachingJoinHandle;
|
||||
use crate::util::serde::Pem;
|
||||
use crate::util::sync::SyncMutex;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref CURRENT_SECRET: Jwk = Jwk::generate_ec_key(josekit::jwk::alg::ec::EcCurve::P256).unwrap_or_else(|e| {
|
||||
tracing::debug!("{:?}", e);
|
||||
tracing::error!("Couldn't generate ec key");
|
||||
tracing::error!("{}", t!("context.setup.couldnt-generate-ec-key"));
|
||||
panic!("Couldn't generate ec key")
|
||||
});
|
||||
}
|
||||
@@ -41,40 +44,25 @@ lazy_static::lazy_static! {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct SetupResult {
|
||||
pub tor_addresses: Vec<String>,
|
||||
#[ts(type = "string")]
|
||||
pub hostname: Hostname,
|
||||
#[ts(type = "string")]
|
||||
pub lan_address: InternedString,
|
||||
pub root_ca: String,
|
||||
}
|
||||
impl TryFrom<&AccountInfo> for SetupResult {
|
||||
type Error = Error;
|
||||
fn try_from(value: &AccountInfo) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
tor_addresses: value
|
||||
.tor_keys
|
||||
.iter()
|
||||
.map(|tor_key| format!("https://{}", tor_key.onion_address()))
|
||||
.collect(),
|
||||
hostname: value.hostname.clone(),
|
||||
lan_address: value.hostname.lan_address(),
|
||||
root_ca: String::from_utf8(value.root_ca_cert.to_pem()?)?,
|
||||
})
|
||||
}
|
||||
pub root_ca: Pem<X509>,
|
||||
pub needs_restart: bool,
|
||||
}
|
||||
|
||||
pub struct SetupContextSeed {
|
||||
pub webserver: WebServerAcceptorSetter<UpgradableListener>,
|
||||
pub config: ServerConfig,
|
||||
pub os_partitions: OsPartitionInfo,
|
||||
pub config: SyncMutex<ServerConfig>,
|
||||
pub disable_encryption: bool,
|
||||
pub progress: FullProgressTracker,
|
||||
pub task: OnceCell<NonDetachingJoinHandle<()>>,
|
||||
pub result: OnceCell<Result<(SetupResult, RpcContext), Error>>,
|
||||
pub disk_guid: OnceCell<Arc<String>>,
|
||||
pub disk_guid: OnceCell<InternedString>,
|
||||
pub shutdown: Sender<Option<Shutdown>>,
|
||||
pub rpc_continuations: RpcContinuations,
|
||||
pub install_rootfs: SyncMutex<Option<(TmpMountGuard, MountGuard)>>,
|
||||
pub keyboard: SyncMutex<Option<KeyboardOptions>>,
|
||||
pub language: SyncMutex<Option<InternedString>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -83,27 +71,24 @@ impl SetupContext {
|
||||
#[instrument(skip_all)]
|
||||
pub fn init(
|
||||
webserver: &WebServer<UpgradableListener>,
|
||||
config: &ServerConfig,
|
||||
config: ServerConfig,
|
||||
) -> Result<Self, Error> {
|
||||
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
||||
let mut progress = FullProgressTracker::new();
|
||||
progress.enable_logging(true);
|
||||
Ok(Self(Arc::new(SetupContextSeed {
|
||||
webserver: webserver.acceptor_setter(),
|
||||
config: config.clone(),
|
||||
os_partitions: config.os_partitions.clone().ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("missing required configuration: `os-partitions`"),
|
||||
ErrorKind::NotFound,
|
||||
)
|
||||
})?,
|
||||
disable_encryption: config.disable_encryption.unwrap_or(false),
|
||||
config: SyncMutex::new(config),
|
||||
progress,
|
||||
task: OnceCell::new(),
|
||||
result: OnceCell::new(),
|
||||
disk_guid: OnceCell::new(),
|
||||
shutdown,
|
||||
rpc_continuations: RpcContinuations::new(),
|
||||
install_rootfs: SyncMutex::new(None),
|
||||
language: SyncMutex::new(None),
|
||||
keyboard: SyncMutex::new(None),
|
||||
})))
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
@@ -129,11 +114,14 @@ impl SetupContext {
|
||||
.get_or_init(|| async {
|
||||
match f().await {
|
||||
Ok(res) => {
|
||||
tracing::info!("Setup complete!");
|
||||
tracing::info!("{}", t!("context.setup.setup-complete"));
|
||||
Ok(res)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Setup failed: {e}");
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("context.setup.setup-failed", error = e)
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
Err(e)
|
||||
}
|
||||
@@ -146,10 +134,13 @@ impl SetupContext {
|
||||
)
|
||||
.map_err(|_| {
|
||||
if self.result.initialized() {
|
||||
Error::new(eyre!("Setup already complete"), ErrorKind::InvalidRequest)
|
||||
Error::new(
|
||||
eyre!("{}", t!("context.setup.setup-already-complete")),
|
||||
ErrorKind::InvalidRequest,
|
||||
)
|
||||
} else {
|
||||
Error::new(
|
||||
eyre!("Setup already in progress"),
|
||||
eyre!("{}", t!("context.setup.setup-already-in-progress")),
|
||||
ErrorKind::InvalidRequest,
|
||||
)
|
||||
}
|
||||
@@ -199,7 +190,7 @@ impl SetupContext {
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("Error in setup progress websocket: {e}");
|
||||
tracing::error!("{}", t!("context.setup.error-in-setup-progress-websocket", error = e));
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::{Error, PackageId};
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ControlParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub id: PackageId,
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ pub fn db<C: Context>() -> ParentHandler<C> {
|
||||
"dump",
|
||||
from_fn_async(cli_dump)
|
||||
.with_display_serializable()
|
||||
.with_about("Filter/query db to display tables and records"),
|
||||
.with_about("about.filter-query-db"),
|
||||
)
|
||||
.subcommand("dump", from_fn_async(dump).no_cli())
|
||||
.subcommand(
|
||||
@@ -65,13 +65,13 @@ pub fn db<C: Context>() -> ParentHandler<C> {
|
||||
)
|
||||
.subcommand(
|
||||
"put",
|
||||
put::<C>().with_about("Command for adding UI record to db"),
|
||||
put::<C>().with_about("about.command-add-ui-record-db"),
|
||||
)
|
||||
.subcommand(
|
||||
"apply",
|
||||
from_fn_async(cli_apply)
|
||||
.no_display()
|
||||
.with_about("Update a db record"),
|
||||
.with_about("about.update-db-record"),
|
||||
)
|
||||
.subcommand("apply", from_fn_async(apply).no_cli())
|
||||
}
|
||||
@@ -87,9 +87,10 @@ pub enum RevisionsRes {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct CliDumpParams {
|
||||
#[arg(long = "include-private", short = 'p')]
|
||||
#[arg(long = "include-private", short = 'p', help = "help.arg.include-private-data")]
|
||||
#[serde(default)]
|
||||
include_private: bool,
|
||||
#[arg(help = "help.arg.db-path")]
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
@@ -258,9 +259,11 @@ pub async fn subscribe(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct CliApplyParams {
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.allow-model-mismatch")]
|
||||
allow_model_mismatch: bool,
|
||||
#[arg(help = "help.arg.db-apply-expr")]
|
||||
expr: String,
|
||||
#[arg(help = "help.arg.db-path")]
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
@@ -327,6 +330,7 @@ async fn cli_apply(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ApplyParams {
|
||||
#[arg(help = "help.arg.db-apply-expr")]
|
||||
expr: String,
|
||||
}
|
||||
|
||||
@@ -358,7 +362,7 @@ pub fn put<C: Context>() -> ParentHandler<C> {
|
||||
"ui",
|
||||
from_fn_async(ui)
|
||||
.with_display_serializable()
|
||||
.with_about("Add path and value to db")
|
||||
.with_about("about.add-path-value-db")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -366,8 +370,10 @@ pub fn put<C: Context>() -> ParentHandler<C> {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct UiParams {
|
||||
#[arg(help = "help.arg.json-pointer")]
|
||||
#[ts(type = "string")]
|
||||
pointer: JsonPointer,
|
||||
#[arg(help = "help.arg.json-value")]
|
||||
#[ts(type = "any")]
|
||||
value: Value,
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::notifications::Notifications;
|
||||
use crate::prelude::*;
|
||||
use crate::sign::AnyVerifyingKey;
|
||||
use crate::ssh::SshKeys;
|
||||
use crate::system::KeyboardOptions;
|
||||
use crate::util::serde::Pem;
|
||||
|
||||
pub mod package;
|
||||
@@ -28,9 +29,14 @@ pub struct Database {
|
||||
pub private: Private,
|
||||
}
|
||||
impl Database {
|
||||
pub fn init(account: &AccountInfo, kiosk: Option<bool>) -> Result<Self, Error> {
|
||||
pub fn init(
|
||||
account: &AccountInfo,
|
||||
kiosk: Option<bool>,
|
||||
language: Option<InternedString>,
|
||||
keyboard: Option<KeyboardOptions>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
public: Public::init(account, kiosk)?,
|
||||
public: Public::init(account, kiosk, language, keyboard)?,
|
||||
private: Private {
|
||||
key_store: KeyStore::new(account)?,
|
||||
password: account.password.clone(),
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::net::host::Hosts;
|
||||
use crate::net::service_interface::ServiceInterface;
|
||||
use crate::prelude::*;
|
||||
use crate::progress::FullProgress;
|
||||
use crate::s9pk::manifest::Manifest;
|
||||
use crate::s9pk::manifest::{LocaleString, Manifest};
|
||||
use crate::status::StatusInfo;
|
||||
use crate::util::DataUrl;
|
||||
use crate::util::serde::{Pem, is_partial_of};
|
||||
@@ -417,8 +417,7 @@ impl Map for CurrentDependencies {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct CurrentDependencyInfo {
|
||||
#[ts(type = "string | null")]
|
||||
pub title: Option<InternedString>,
|
||||
pub title: Option<LocaleString>,
|
||||
pub icon: Option<DataUrl<'static>>,
|
||||
#[serde(flatten)]
|
||||
pub kind: CurrentDependencyKind,
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::net::utils::ipv6_is_local;
|
||||
use crate::net::vhost::AlpnInfo;
|
||||
use crate::prelude::*;
|
||||
use crate::progress::FullProgress;
|
||||
use crate::system::SmtpValue;
|
||||
use crate::system::{KeyboardOptions, SmtpValue};
|
||||
use crate::util::cpupower::Governor;
|
||||
use crate::util::lshw::LshwDevice;
|
||||
use crate::util::serde::MaybeUtf8String;
|
||||
@@ -45,7 +45,12 @@ pub struct Public {
|
||||
pub ui: Value,
|
||||
}
|
||||
impl Public {
|
||||
pub fn init(account: &AccountInfo, kiosk: Option<bool>) -> Result<Self, Error> {
|
||||
pub fn init(
|
||||
account: &AccountInfo,
|
||||
kiosk: Option<bool>,
|
||||
language: Option<InternedString>,
|
||||
keyboard: Option<KeyboardOptions>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
server_info: ServerInfo {
|
||||
arch: get_arch(),
|
||||
@@ -139,6 +144,8 @@ impl Public {
|
||||
ram: 0,
|
||||
devices: Vec::new(),
|
||||
kiosk,
|
||||
language,
|
||||
keyboard,
|
||||
},
|
||||
package_data: AllPackageData::default(),
|
||||
ui: serde_json::from_str(*DB_UI_SEED_CELL.get().unwrap_or(&"null"))
|
||||
@@ -195,6 +202,8 @@ pub struct ServerInfo {
|
||||
pub ram: u64,
|
||||
pub devices: Vec<LshwDevice>,
|
||||
pub kiosk: Option<bool>,
|
||||
pub language: Option<InternedString>,
|
||||
pub keyboard: Option<KeyboardOptions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
use imbl_value::InternedString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::LocaleString;
|
||||
use crate::util::PathOrUrl;
|
||||
use crate::{Error, PackageId};
|
||||
|
||||
@@ -28,7 +28,7 @@ impl Map for Dependencies {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct DepInfo {
|
||||
pub description: Option<String>,
|
||||
pub description: Option<LocaleString>,
|
||||
pub optional: bool,
|
||||
#[serde(flatten)]
|
||||
pub metadata: Option<MetadataSrc>,
|
||||
@@ -73,7 +73,7 @@ pub enum MetadataSrc {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct Metadata {
|
||||
pub title: InternedString,
|
||||
pub title: LocaleString,
|
||||
pub icon: PathOrUrl,
|
||||
}
|
||||
|
||||
@@ -82,5 +82,5 @@ pub struct Metadata {
|
||||
#[model = "Model<Self>"]
|
||||
pub struct DependencyMetadata {
|
||||
#[ts(type = "string")]
|
||||
pub title: InternedString,
|
||||
pub title: LocaleString,
|
||||
}
|
||||
|
||||
@@ -17,45 +17,46 @@ pub fn diagnostic<C: Context>() -> ParentHandler<C> {
|
||||
.subcommand(
|
||||
"error",
|
||||
from_fn(error)
|
||||
.with_about("Display diagnostic error")
|
||||
.with_about("about.display-diagnostic-error")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"logs",
|
||||
crate::system::logs::<DiagnosticContext>().with_about("Display OS logs"),
|
||||
crate::system::logs::<DiagnosticContext>().with_about("about.display-os-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"logs",
|
||||
from_fn_async(crate::logs::cli_logs::<DiagnosticContext, Empty>)
|
||||
.no_display()
|
||||
.with_about("Display OS logs"),
|
||||
.with_about("about.display-os-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"kernel-logs",
|
||||
crate::system::kernel_logs::<DiagnosticContext>().with_about("Display kernel logs"),
|
||||
crate::system::kernel_logs::<DiagnosticContext>()
|
||||
.with_about("about.display-kernel-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"kernel-logs",
|
||||
from_fn_async(crate::logs::cli_logs::<DiagnosticContext, Empty>)
|
||||
.no_display()
|
||||
.with_about("Display kernal logs"),
|
||||
.with_about("about.display-kernel-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"restart",
|
||||
from_fn(restart)
|
||||
.no_display()
|
||||
.with_about("Restart the server")
|
||||
.with_about("about.restart-server")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"disk",
|
||||
disk::<C>().with_about("Command to remove disk from filesystem"),
|
||||
disk::<C>().with_about("about.command-remove-disk-filesystem"),
|
||||
)
|
||||
.subcommand(
|
||||
"rebuild",
|
||||
from_fn_async(rebuild)
|
||||
.no_display()
|
||||
.with_about("Teardown and rebuild service containers")
|
||||
.with_about("about.teardown-rebuild-containers")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -89,16 +90,16 @@ pub fn disk<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(forget_disk::<RpcContext>).no_display(),
|
||||
)
|
||||
.no_display()
|
||||
.with_about("Remove disk from filesystem"),
|
||||
.with_about("about.remove-disk-filesystem"),
|
||||
)
|
||||
.subcommand("repair", from_fn_async(|_: C| repair()).no_cli())
|
||||
.subcommand(
|
||||
"repair",
|
||||
CallRemoteHandler::<CliContext, _, _>::new(
|
||||
from_fn_async(|_: RpcContext| repair())
|
||||
.no_display()
|
||||
.with_about("Repair disk in the event of corruption"),
|
||||
),
|
||||
from_fn_async(|_: RpcContext| repair()).no_display(),
|
||||
)
|
||||
.no_display()
|
||||
.with_about("about.repair-disk-corruption"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::path::Path;
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::FutureExt;
|
||||
use futures::future::BoxFuture;
|
||||
use rust_i18n::t;
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -62,33 +63,31 @@ async fn e2fsck_runner(
|
||||
let e2fsck_stderr = String::from_utf8(e2fsck_out.stderr)?;
|
||||
let code = e2fsck_out.status.code().ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("e2fsck: process terminated by signal"),
|
||||
eyre!("{}", t!("disk.fsck.process-terminated-by-signal")),
|
||||
crate::ErrorKind::DiskManagement,
|
||||
)
|
||||
})?;
|
||||
if code & 4 != 0 {
|
||||
tracing::error!(
|
||||
"some filesystem errors NOT corrected on {}:\n{}",
|
||||
logicalname.as_ref().display(),
|
||||
e2fsck_stderr,
|
||||
"{}",
|
||||
t!("disk.fsck.errors-not-corrected", device = logicalname.as_ref().display(), stderr = e2fsck_stderr),
|
||||
);
|
||||
} else if code & 1 != 0 {
|
||||
tracing::warn!(
|
||||
"filesystem errors corrected on {}:\n{}",
|
||||
logicalname.as_ref().display(),
|
||||
e2fsck_stderr,
|
||||
"{}",
|
||||
t!("disk.fsck.errors-corrected", device = logicalname.as_ref().display(), stderr = e2fsck_stderr),
|
||||
);
|
||||
}
|
||||
if code < 8 {
|
||||
if code & 2 != 0 {
|
||||
tracing::warn!("reboot required");
|
||||
tracing::warn!("{}", t!("disk.fsck.reboot-required"));
|
||||
Ok(RequiresReboot(true))
|
||||
} else {
|
||||
Ok(RequiresReboot(false))
|
||||
}
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("e2fsck: {}", e2fsck_stderr),
|
||||
eyre!("{}", t!("disk.fsck.e2fsck-error", stderr = e2fsck_stderr)),
|
||||
crate::ErrorKind::DiskManagement,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use imbl_value::InternedString;
|
||||
use rust_i18n::t;
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -20,10 +22,10 @@ pub const MAIN_FS_SIZE: FsSize = FsSize::Gigabytes(8);
|
||||
#[instrument(skip_all)]
|
||||
pub async fn create<I, P>(
|
||||
disks: &I,
|
||||
pvscan: &BTreeMap<PathBuf, Option<String>>,
|
||||
pvscan: &BTreeMap<PathBuf, Option<InternedString>>,
|
||||
datadir: impl AsRef<Path>,
|
||||
password: Option<&str>,
|
||||
) -> Result<String, Error>
|
||||
) -> Result<InternedString, Error>
|
||||
where
|
||||
for<'a> &'a I: IntoIterator<Item = &'a P>,
|
||||
P: AsRef<Path>,
|
||||
@@ -37,9 +39,9 @@ where
|
||||
#[instrument(skip_all)]
|
||||
pub async fn create_pool<I, P>(
|
||||
disks: &I,
|
||||
pvscan: &BTreeMap<PathBuf, Option<String>>,
|
||||
pvscan: &BTreeMap<PathBuf, Option<InternedString>>,
|
||||
encrypted: bool,
|
||||
) -> Result<String, Error>
|
||||
) -> Result<InternedString, Error>
|
||||
where
|
||||
for<'a> &'a I: IntoIterator<Item = &'a P>,
|
||||
P: AsRef<Path>,
|
||||
@@ -79,7 +81,7 @@ where
|
||||
cmd.arg(disk.as_ref());
|
||||
}
|
||||
cmd.invoke(crate::ErrorKind::DiskManagement).await?;
|
||||
Ok(guid)
|
||||
Ok(guid.into())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -224,7 +226,7 @@ pub async fn import<P: AsRef<Path>>(
|
||||
.is_none()
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("StartOS disk not found."),
|
||||
eyre!("{}", t!("disk.main.disk-not-found")),
|
||||
crate::ErrorKind::DiskNotAvailable,
|
||||
));
|
||||
}
|
||||
@@ -234,7 +236,7 @@ pub async fn import<P: AsRef<Path>>(
|
||||
.any(|id| id == guid)
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("A StartOS disk was found, but it is not the correct disk for this device."),
|
||||
eyre!("{}", t!("disk.main.incorrect-disk")),
|
||||
crate::ErrorKind::IncorrectDisk,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ pub struct OsPartitionInfo {
|
||||
pub bios: Option<PathBuf>,
|
||||
pub boot: PathBuf,
|
||||
pub root: PathBuf,
|
||||
#[serde(skip)] // internal use only
|
||||
pub data: Option<PathBuf>,
|
||||
}
|
||||
impl OsPartitionInfo {
|
||||
pub fn contains(&self, logicalname: impl AsRef<Path>) -> bool {
|
||||
@@ -49,7 +51,7 @@ pub fn disk<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(list)
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|handle, result| display_disk_info(handle.params, result))
|
||||
.with_about("List disk info")
|
||||
.with_about("about.list-disk-info")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand("repair", from_fn_async(|_: C| repair()).no_cli())
|
||||
@@ -58,7 +60,7 @@ pub fn disk<C: Context>() -> ParentHandler<C> {
|
||||
CallRemoteHandler::<CliContext, _, _>::new(
|
||||
from_fn_async(|_: RpcContext| repair())
|
||||
.no_display()
|
||||
.with_about("Repair disk in the event of corruption"),
|
||||
.with_about("about.repair-disk-corruption"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,9 +23,8 @@ pub async fn bind<P0: AsRef<Path>, P1: AsRef<Path>>(
|
||||
read_only: bool,
|
||||
) -> Result<(), Error> {
|
||||
tracing::info!(
|
||||
"Binding {} to {}",
|
||||
src.as_ref().display(),
|
||||
dst.as_ref().display()
|
||||
"{}",
|
||||
t!("disk.mount.binding", src = src.as_ref().display(), dst = dst.as_ref().display())
|
||||
);
|
||||
if is_mountpoint(&dst).await? {
|
||||
unmount(dst.as_ref(), true).await?;
|
||||
|
||||
@@ -20,9 +20,9 @@ use super::mount::guard::TmpMountGuard;
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::disk::mount::guard::GenericMountGuard;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::{Error, ResultExt as _};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -40,7 +40,7 @@ pub struct DiskInfo {
|
||||
pub model: Option<String>,
|
||||
pub partitions: Vec<PartitionInfo>,
|
||||
pub capacity: u64,
|
||||
pub guid: Option<String>,
|
||||
pub guid: Option<InternedString>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
@@ -51,7 +51,7 @@ pub struct PartitionInfo {
|
||||
pub capacity: u64,
|
||||
pub used: Option<u64>,
|
||||
pub start_os: BTreeMap<String, StartOsRecoveryInfo>,
|
||||
pub guid: Option<String>,
|
||||
pub guid: Option<InternedString>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
@@ -95,7 +95,7 @@ pub async fn get_vendor<P: AsRef<Path>>(path: P) -> Result<Option<String>, Error
|
||||
Path::new(SYS_BLOCK_PATH)
|
||||
.join(path.as_ref().strip_prefix("/dev").map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("not a canonical block device"),
|
||||
eyre!("{}", t!("disk.util.not-canonical-block-device")),
|
||||
crate::ErrorKind::BlockDevice,
|
||||
)
|
||||
})?)
|
||||
@@ -118,7 +118,7 @@ pub async fn get_model<P: AsRef<Path>>(path: P) -> Result<Option<String>, Error>
|
||||
Path::new(SYS_BLOCK_PATH)
|
||||
.join(path.as_ref().strip_prefix("/dev").map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("not a canonical block device"),
|
||||
eyre!("{}", t!("disk.util.not-canonical-block-device")),
|
||||
crate::ErrorKind::BlockDevice,
|
||||
)
|
||||
})?)
|
||||
@@ -215,7 +215,7 @@ pub async fn get_percentage<P: AsRef<Path>>(path: P) -> Result<u64, Error> {
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn pvscan() -> Result<BTreeMap<PathBuf, Option<String>>, Error> {
|
||||
pub async fn pvscan() -> Result<BTreeMap<PathBuf, Option<InternedString>>, Error> {
|
||||
let pvscan_out = Command::new("pvscan")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
@@ -259,6 +259,31 @@ pub async fn recovery_info(
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Returns the canonical path of the source device for a given mount point,
|
||||
/// or None if the mount point doesn't exist or isn't mounted.
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get_mount_source(mountpoint: impl AsRef<Path>) -> Result<Option<PathBuf>, Error> {
|
||||
let mounts_content = tokio::fs::read_to_string("/proc/mounts")
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, "/proc/mounts"))?;
|
||||
|
||||
let mountpoint = mountpoint.as_ref();
|
||||
for line in mounts_content.lines() {
|
||||
let mut parts = line.split_whitespace();
|
||||
let source = parts.next();
|
||||
let mount = parts.next();
|
||||
if let (Some(source), Some(mount)) = (source, mount) {
|
||||
if Path::new(mount) == mountpoint {
|
||||
// Try to canonicalize the source path
|
||||
if let Ok(canonical) = tokio::fs::canonicalize(source).await {
|
||||
return Ok(Some(canonical));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn list(os: &OsPartitionInfo) -> Result<Vec<DiskInfo>, Error> {
|
||||
struct DiskIndex {
|
||||
@@ -374,23 +399,53 @@ async fn disk_info(disk: PathBuf) -> DiskInfo {
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"Could not get partition table of {}: {}",
|
||||
disk.display(),
|
||||
e.source
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-partition-table",
|
||||
disk = disk.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let vendor = get_vendor(&disk)
|
||||
.await
|
||||
.map_err(|e| tracing::warn!("Could not get vendor of {}: {}", disk.display(), e.source))
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-vendor",
|
||||
disk = disk.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let model = get_model(&disk)
|
||||
.await
|
||||
.map_err(|e| tracing::warn!("Could not get model of {}: {}", disk.display(), e.source))
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-model",
|
||||
disk = disk.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let capacity = get_capacity(&disk)
|
||||
.await
|
||||
.map_err(|e| tracing::warn!("Could not get capacity of {}: {}", disk.display(), e.source))
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-capacity",
|
||||
disk = disk.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
DiskInfo {
|
||||
logicalname: disk,
|
||||
@@ -407,21 +462,49 @@ async fn part_info(part: PathBuf) -> PartitionInfo {
|
||||
let mut start_os = BTreeMap::new();
|
||||
let label = get_label(&part)
|
||||
.await
|
||||
.map_err(|e| tracing::warn!("Could not get label of {}: {}", part.display(), e.source))
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-label",
|
||||
part = part.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let capacity = get_capacity(&part)
|
||||
.await
|
||||
.map_err(|e| tracing::warn!("Could not get capacity of {}: {}", part.display(), e.source))
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-capacity-part",
|
||||
part = part.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let mut used = None;
|
||||
|
||||
match TmpMountGuard::mount(&BlockDev::new(&part), ReadOnly).await {
|
||||
Err(e) => tracing::warn!("Could not collect usage information: {}", e.source),
|
||||
Err(e) => tracing::warn!(
|
||||
"{}",
|
||||
t!("disk.util.could-not-collect-usage-info", error = e.source)
|
||||
),
|
||||
Ok(mount_guard) => {
|
||||
used = get_used(mount_guard.path())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::warn!("Could not get usage of {}: {}", part.display(), e.source)
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.could-not-get-usage",
|
||||
part = part.display(),
|
||||
error = e.source
|
||||
)
|
||||
)
|
||||
})
|
||||
.ok();
|
||||
match recovery_info(mount_guard.path()).await {
|
||||
@@ -429,11 +512,21 @@ async fn part_info(part: PathBuf) -> PartitionInfo {
|
||||
start_os = a;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error fetching unencrypted backup metadata: {}", e);
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("disk.util.error-fetching-backup-metadata", error = e)
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Err(e) = mount_guard.unmount().await {
|
||||
tracing::error!("Error unmounting partition {}: {}", part.display(), e);
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.util.error-unmounting-partition",
|
||||
part = part.display(),
|
||||
error = e
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,7 +541,7 @@ async fn part_info(part: PathBuf) -> PartitionInfo {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_pvscan_output(pvscan_output: &str) -> BTreeMap<PathBuf, Option<String>> {
|
||||
fn parse_pvscan_output(pvscan_output: &str) -> BTreeMap<PathBuf, Option<InternedString>> {
|
||||
fn parse_line(line: &str) -> IResult<&str, (&str, Option<&str>)> {
|
||||
let pv_parse = preceded(
|
||||
tag(" PV "),
|
||||
@@ -471,10 +564,10 @@ fn parse_pvscan_output(pvscan_output: &str) -> BTreeMap<PathBuf, Option<String>>
|
||||
for entry in entries {
|
||||
match parse_line(entry) {
|
||||
Ok((_, (pv, vg))) => {
|
||||
ret.insert(PathBuf::from(pv), vg.map(|s| s.to_owned()));
|
||||
ret.insert(PathBuf::from(pv), vg.map(InternedString::intern));
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::warn!("Failed to parse pvscan output line: {}", entry);
|
||||
tracing::warn!("{}", t!("disk.util.failed-to-parse-pvscan", line = entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use rpc_toolkit::reqwest;
|
||||
use rpc_toolkit::yajrc::{
|
||||
INVALID_PARAMS_ERROR, INVALID_REQUEST_ERROR, METHOD_NOT_FOUND_ERROR, PARSE_ERROR, RpcError,
|
||||
};
|
||||
use rust_i18n::t;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_rustls::rustls;
|
||||
@@ -97,95 +98,97 @@ pub enum ErrorKind {
|
||||
InstallFailed = 76,
|
||||
UpdateFailed = 77,
|
||||
Smtp = 78,
|
||||
SetSysInfo = 79,
|
||||
}
|
||||
impl ErrorKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
pub fn as_str(&self) -> String {
|
||||
use ErrorKind::*;
|
||||
match self {
|
||||
Unknown => "Unknown Error",
|
||||
Filesystem => "Filesystem I/O Error",
|
||||
Docker => "Docker Error",
|
||||
ConfigSpecViolation => "Config Spec Violation",
|
||||
ConfigRulesViolation => "Config Rules Violation",
|
||||
NotFound => "Not Found",
|
||||
IncorrectPassword => "Incorrect Password",
|
||||
VersionIncompatible => "Version Incompatible",
|
||||
Network => "Network Error",
|
||||
Registry => "Registry Error",
|
||||
Serialization => "Serialization Error",
|
||||
Deserialization => "Deserialization Error",
|
||||
Utf8 => "UTF-8 Parse Error",
|
||||
ParseVersion => "Version Parsing Error",
|
||||
IncorrectDisk => "Incorrect Disk",
|
||||
// Nginx => "Nginx Error",
|
||||
Dependency => "Dependency Error",
|
||||
ParseS9pk => "S9PK Parsing Error",
|
||||
ParseUrl => "URL Parsing Error",
|
||||
DiskNotAvailable => "Disk Not Available",
|
||||
BlockDevice => "Block Device Error",
|
||||
InvalidOnionAddress => "Invalid Onion Address",
|
||||
Pack => "Pack Error",
|
||||
ValidateS9pk => "S9PK Validation Error",
|
||||
DiskCorrupted => "Disk Corrupted", // Remove
|
||||
Tor => "Tor Daemon Error",
|
||||
ConfigGen => "Config Generation Error",
|
||||
ParseNumber => "Number Parsing Error",
|
||||
Database => "Database Error",
|
||||
InvalidId => "Invalid ID",
|
||||
InvalidSignature => "Invalid Signature",
|
||||
Backup => "Backup Error",
|
||||
Restore => "Restore Error",
|
||||
Authorization => "Unauthorized",
|
||||
AutoConfigure => "Auto-Configure Error",
|
||||
Action => "Action Failed",
|
||||
RateLimited => "Rate Limited",
|
||||
InvalidRequest => "Invalid Request",
|
||||
MigrationFailed => "Migration Failed",
|
||||
Uninitialized => "Uninitialized",
|
||||
ParseNetAddress => "Net Address Parsing Error",
|
||||
ParseSshKey => "SSH Key Parsing Error",
|
||||
SoundError => "Sound Interface Error",
|
||||
ParseTimestamp => "Timestamp Parsing Error",
|
||||
ParseSysInfo => "System Info Parsing Error",
|
||||
Wifi => "WiFi Internal Error",
|
||||
Journald => "Journald Error",
|
||||
DiskManagement => "Disk Management Error",
|
||||
OpenSsl => "OpenSSL Internal Error",
|
||||
PasswordHashGeneration => "Password Hash Generation Error",
|
||||
DiagnosticMode => "Server is in Diagnostic Mode",
|
||||
ParseDbField => "Database Field Parse Error",
|
||||
Duplicate => "Duplication Error",
|
||||
MultipleErrors => "Multiple Errors",
|
||||
Incoherent => "Incoherent",
|
||||
InvalidBackupTargetId => "Invalid Backup Target ID",
|
||||
ProductKeyMismatch => "Incompatible Product Keys",
|
||||
LanPortConflict => "Incompatible LAN Port Configuration",
|
||||
Javascript => "Javascript Engine Error",
|
||||
Pem => "PEM Encoding Error",
|
||||
TLSInit => "TLS Backend Initialization Error",
|
||||
Ascii => "ASCII Parse Error",
|
||||
MissingHeader => "Missing Header",
|
||||
Grub => "Grub Error",
|
||||
Systemd => "Systemd Error",
|
||||
OpenSsh => "OpenSSH Error",
|
||||
Zram => "Zram Error",
|
||||
Lshw => "LSHW Error",
|
||||
CpuSettings => "CPU Settings Error",
|
||||
Firmware => "Firmware Error",
|
||||
Timeout => "Timeout Error",
|
||||
Lxc => "LXC Error",
|
||||
Cancelled => "Cancelled",
|
||||
Git => "Git Error",
|
||||
DBus => "DBus Error",
|
||||
InstallFailed => "Install Failed",
|
||||
UpdateFailed => "Update Failed",
|
||||
Smtp => "SMTP Error",
|
||||
}
|
||||
Unknown => t!("error.unknown"),
|
||||
Filesystem => t!("error.filesystem"),
|
||||
Docker => t!("error.docker"),
|
||||
ConfigSpecViolation => t!("error.config-spec-violation"),
|
||||
ConfigRulesViolation => t!("error.config-rules-violation"),
|
||||
NotFound => t!("error.not-found"),
|
||||
IncorrectPassword => t!("error.incorrect-password"),
|
||||
VersionIncompatible => t!("error.version-incompatible"),
|
||||
Network => t!("error.network"),
|
||||
Registry => t!("error.registry"),
|
||||
Serialization => t!("error.serialization"),
|
||||
Deserialization => t!("error.deserialization"),
|
||||
Utf8 => t!("error.utf8"),
|
||||
ParseVersion => t!("error.parse-version"),
|
||||
IncorrectDisk => t!("error.incorrect-disk"),
|
||||
// Nginx => t!("error.nginx"),
|
||||
Dependency => t!("error.dependency"),
|
||||
ParseS9pk => t!("error.parse-s9pk"),
|
||||
ParseUrl => t!("error.parse-url"),
|
||||
DiskNotAvailable => t!("error.disk-not-available"),
|
||||
BlockDevice => t!("error.block-device"),
|
||||
InvalidOnionAddress => t!("error.invalid-onion-address"),
|
||||
Pack => t!("error.pack"),
|
||||
ValidateS9pk => t!("error.validate-s9pk"),
|
||||
DiskCorrupted => t!("error.disk-corrupted"), // Remove
|
||||
Tor => t!("error.tor"),
|
||||
ConfigGen => t!("error.config-gen"),
|
||||
ParseNumber => t!("error.parse-number"),
|
||||
Database => t!("error.database"),
|
||||
InvalidId => t!("error.invalid-id"),
|
||||
InvalidSignature => t!("error.invalid-signature"),
|
||||
Backup => t!("error.backup"),
|
||||
Restore => t!("error.restore"),
|
||||
Authorization => t!("error.authorization"),
|
||||
AutoConfigure => t!("error.auto-configure"),
|
||||
Action => t!("error.action"),
|
||||
RateLimited => t!("error.rate-limited"),
|
||||
InvalidRequest => t!("error.invalid-request"),
|
||||
MigrationFailed => t!("error.migration-failed"),
|
||||
Uninitialized => t!("error.uninitialized"),
|
||||
ParseNetAddress => t!("error.parse-net-address"),
|
||||
ParseSshKey => t!("error.parse-ssh-key"),
|
||||
SoundError => t!("error.sound-error"),
|
||||
ParseTimestamp => t!("error.parse-timestamp"),
|
||||
ParseSysInfo => t!("error.parse-sys-info"),
|
||||
Wifi => t!("error.wifi"),
|
||||
Journald => t!("error.journald"),
|
||||
DiskManagement => t!("error.disk-management"),
|
||||
OpenSsl => t!("error.openssl"),
|
||||
PasswordHashGeneration => t!("error.password-hash-generation"),
|
||||
DiagnosticMode => t!("error.diagnostic-mode"),
|
||||
ParseDbField => t!("error.parse-db-field"),
|
||||
Duplicate => t!("error.duplicate"),
|
||||
MultipleErrors => t!("error.multiple-errors"),
|
||||
Incoherent => t!("error.incoherent"),
|
||||
InvalidBackupTargetId => t!("error.invalid-backup-target-id"),
|
||||
ProductKeyMismatch => t!("error.product-key-mismatch"),
|
||||
LanPortConflict => t!("error.lan-port-conflict"),
|
||||
Javascript => t!("error.javascript"),
|
||||
Pem => t!("error.pem"),
|
||||
TLSInit => t!("error.tls-init"),
|
||||
Ascii => t!("error.ascii"),
|
||||
MissingHeader => t!("error.missing-header"),
|
||||
Grub => t!("error.grub"),
|
||||
Systemd => t!("error.systemd"),
|
||||
OpenSsh => t!("error.openssh"),
|
||||
Zram => t!("error.zram"),
|
||||
Lshw => t!("error.lshw"),
|
||||
CpuSettings => t!("error.cpu-settings"),
|
||||
Firmware => t!("error.firmware"),
|
||||
Timeout => t!("error.timeout"),
|
||||
Lxc => t!("error.lxc"),
|
||||
Cancelled => t!("error.cancelled"),
|
||||
Git => t!("error.git"),
|
||||
DBus => t!("error.dbus"),
|
||||
InstallFailed => t!("error.install-failed"),
|
||||
UpdateFailed => t!("error.update-failed"),
|
||||
Smtp => t!("error.smtp"),
|
||||
SetSysInfo => t!("error.set-sys-info"),
|
||||
}.to_string()
|
||||
}
|
||||
}
|
||||
impl Display for ErrorKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
write!(f, "{}", &self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +202,7 @@ pub struct Error {
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}: {:#}", self.kind.as_str(), self.source)
|
||||
write!(f, "{}: {:#}", &self.kind.as_str(), self.source)
|
||||
}
|
||||
}
|
||||
impl Debug for Error {
|
||||
@@ -207,7 +210,7 @@ impl Debug for Error {
|
||||
write!(
|
||||
f,
|
||||
"{}: {:?}",
|
||||
self.kind.as_str(),
|
||||
&self.kind.as_str(),
|
||||
self.debug.as_ref().unwrap_or(&self.source)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -81,26 +81,28 @@ impl InitPhases {
|
||||
pub fn new(handle: &FullProgressTracker) -> Self {
|
||||
Self {
|
||||
preinit: if Path::new("/media/startos/config/preinit.sh").exists() {
|
||||
Some(handle.add_phase("Running preinit.sh".into(), Some(5)))
|
||||
Some(handle.add_phase(t!("init.running-preinit").into(), Some(5)))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
local_auth: handle.add_phase("Enabling local authentication".into(), Some(1)),
|
||||
load_database: handle.add_phase("Loading database".into(), Some(5)),
|
||||
load_ssh_keys: handle.add_phase("Loading SSH Keys".into(), Some(1)),
|
||||
start_net: handle.add_phase("Starting network controller".into(), Some(1)),
|
||||
mount_logs: handle.add_phase("Switching logs to write to data drive".into(), Some(1)),
|
||||
load_ca_cert: handle.add_phase("Loading CA certificate".into(), Some(1)),
|
||||
load_wifi: handle.add_phase("Loading WiFi configuration".into(), Some(1)),
|
||||
init_tmp: handle.add_phase("Initializing temporary files".into(), Some(1)),
|
||||
set_governor: handle.add_phase("Setting CPU performance profile".into(), Some(1)),
|
||||
sync_clock: handle.add_phase("Synchronizing system clock".into(), Some(10)),
|
||||
enable_zram: handle.add_phase("Enabling ZRAM".into(), Some(1)),
|
||||
update_server_info: handle.add_phase("Updating server info".into(), Some(1)),
|
||||
launch_service_network: handle.add_phase("Launching service intranet".into(), Some(1)),
|
||||
validate_db: handle.add_phase("Validating database".into(), Some(1)),
|
||||
local_auth: handle.add_phase(t!("init.enabling-local-auth").into(), Some(1)),
|
||||
load_database: handle.add_phase(t!("init.loading-database").into(), Some(5)),
|
||||
load_ssh_keys: handle.add_phase(t!("init.loading-ssh-keys").into(), Some(1)),
|
||||
start_net: handle.add_phase(t!("init.starting-network-controller").into(), Some(1)),
|
||||
mount_logs: handle.add_phase(t!("init.switching-logs-to-data-drive").into(), Some(1)),
|
||||
load_ca_cert: handle.add_phase(t!("init.loading-ca-certificate").into(), Some(1)),
|
||||
load_wifi: handle.add_phase(t!("init.loading-wifi-configuration").into(), Some(1)),
|
||||
init_tmp: handle.add_phase(t!("init.initializing-temporary-files").into(), Some(1)),
|
||||
set_governor: handle
|
||||
.add_phase(t!("init.setting-cpu-performance-profile").into(), Some(1)),
|
||||
sync_clock: handle.add_phase(t!("init.synchronizing-system-clock").into(), Some(10)),
|
||||
enable_zram: handle.add_phase(t!("init.enabling-zram").into(), Some(1)),
|
||||
update_server_info: handle.add_phase(t!("init.updating-server-info").into(), Some(1)),
|
||||
launch_service_network: handle
|
||||
.add_phase(t!("init.launching-service-intranet").into(), Some(1)),
|
||||
validate_db: handle.add_phase(t!("init.validating-database").into(), Some(1)),
|
||||
postinit: if Path::new("/media/startos/config/postinit.sh").exists() {
|
||||
Some(handle.add_phase("Running postinit.sh".into(), Some(5)))
|
||||
Some(handle.add_phase(t!("init.running-postinit").into(), Some(5)))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
@@ -127,7 +129,14 @@ pub async fn run_script<P: AsRef<Path>>(path: P, mut progress: PhaseProgressTrac
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("Error Running {}: {}", script.display(), e);
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!(
|
||||
"init.error-running-script",
|
||||
script = script.display(),
|
||||
error = e
|
||||
)
|
||||
);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
progress.complete();
|
||||
@@ -230,6 +239,7 @@ pub async fn init(
|
||||
.arg("-R")
|
||||
.arg("+C")
|
||||
.arg("/var/log/journal")
|
||||
.env("LANG", "C.UTF-8")
|
||||
.invoke(ErrorKind::Filesystem)
|
||||
.await
|
||||
{
|
||||
@@ -314,14 +324,17 @@ pub async fn init(
|
||||
{
|
||||
Some(governor)
|
||||
} else {
|
||||
tracing::warn!("CPU Governor \"{governor}\" Not Available");
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!("init.cpu-governor-not-available", governor = governor)
|
||||
);
|
||||
None
|
||||
}
|
||||
} else {
|
||||
cpupower::get_preferred_governor().await?
|
||||
};
|
||||
if let Some(governor) = governor {
|
||||
tracing::info!("Setting CPU Governor to \"{governor}\"");
|
||||
tracing::info!("{}", t!("init.setting-cpu-governor", governor = governor));
|
||||
cpupower::set_governor(governor).await?;
|
||||
}
|
||||
set_governor.complete();
|
||||
@@ -349,14 +362,14 @@ pub async fn init(
|
||||
}
|
||||
}
|
||||
if !ntp_synced {
|
||||
tracing::warn!("Timed out waiting for system time to synchronize");
|
||||
tracing::warn!("{}", t!("init.clock-sync-timeout"));
|
||||
}
|
||||
sync_clock.complete();
|
||||
|
||||
enable_zram.start();
|
||||
if server_info.as_zram().de()? {
|
||||
crate::system::enable_zram().await?;
|
||||
tracing::info!("Enabled ZRAM");
|
||||
tracing::info!("{}", t!("init.enabled-zram"));
|
||||
}
|
||||
enable_zram.complete();
|
||||
|
||||
@@ -404,7 +417,7 @@ pub async fn init(
|
||||
run_script("/media/startos/config/postinit.sh", progress).await;
|
||||
}
|
||||
|
||||
tracing::info!("System initialized.");
|
||||
tracing::info!("{}", t!("init.system-initialized"));
|
||||
|
||||
Ok(InitResult {
|
||||
net_ctrl,
|
||||
@@ -416,30 +429,30 @@ pub fn init_api<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"logs",
|
||||
crate::system::logs::<InitContext>().with_about("Disply OS logs"),
|
||||
crate::system::logs::<InitContext>().with_about("about.display-os-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"logs",
|
||||
from_fn_async(crate::logs::cli_logs::<InitContext, Empty>)
|
||||
.no_display()
|
||||
.with_about("Display OS logs"),
|
||||
.with_about("about.display-os-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"kernel-logs",
|
||||
crate::system::kernel_logs::<InitContext>().with_about("Display kernel logs"),
|
||||
crate::system::kernel_logs::<InitContext>().with_about("about.display-kernel-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"kernel-logs",
|
||||
from_fn_async(crate::logs::cli_logs::<InitContext, Empty>)
|
||||
.no_display()
|
||||
.with_about("Display kernel logs"),
|
||||
.with_about("about.display-kernel-logs"),
|
||||
)
|
||||
.subcommand("subscribe", from_fn_async(init_progress).no_cli())
|
||||
.subcommand(
|
||||
"subscribe",
|
||||
from_fn_async(cli_init_progress)
|
||||
.no_display()
|
||||
.with_about("Get initialization progress"),
|
||||
.with_about("about.get-initialization-progress"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -495,7 +508,7 @@ pub async fn init_progress(ctx: InitContext) -> Result<InitProgressRes, Error> {
|
||||
);
|
||||
|
||||
if let Err(e) = ws.close_result(res.map(|_| "complete")).await {
|
||||
tracing::error!("error closing init progress websocket: {e}");
|
||||
tracing::error!("{}", t!("init.error-closing-websocket", error = e));
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
},
|
||||
@@ -526,7 +539,7 @@ pub async fn cli_init_progress(
|
||||
.await?,
|
||||
)?;
|
||||
let mut ws = ctx.ws_continuation(res.guid).await?;
|
||||
let mut bar = PhasedProgressBar::new("Initializing...");
|
||||
let mut bar = PhasedProgressBar::new(&t!("init.initializing"));
|
||||
while let Some(msg) = ws.try_next().await.with_kind(ErrorKind::Network)? {
|
||||
if let tokio_tungstenite::tungstenite::Message::Text(msg) = msg {
|
||||
bar.update(&serde_json::from_str(&msg).with_kind(ErrorKind::Deserialization)?);
|
||||
|
||||
@@ -283,6 +283,7 @@ pub async fn sideload(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct CancelInstallParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub id: PackageId,
|
||||
}
|
||||
|
||||
@@ -299,7 +300,9 @@ pub fn cancel_install(
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct QueryPackageParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
id: PackageId,
|
||||
#[arg(help = "help.arg.version-range")]
|
||||
version: Option<VersionRange>,
|
||||
}
|
||||
|
||||
@@ -357,6 +360,7 @@ impl FromArgMatches for CliInstallParams {
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[ts(export)]
|
||||
pub struct InstalledVersionParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
id: PackageId,
|
||||
}
|
||||
|
||||
@@ -516,11 +520,12 @@ pub async fn cli_install(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct UninstallParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
id: PackageId,
|
||||
#[arg(long, help = "Do not delete the service data")]
|
||||
#[arg(long, help = "help.arg.soft-uninstall")]
|
||||
#[serde(default)]
|
||||
soft: bool,
|
||||
#[arg(long, help = "Ignore errors in service uninit script")]
|
||||
#[arg(long, help = "help.arg.force-uninstall")]
|
||||
#[serde(default)]
|
||||
force: bool,
|
||||
}
|
||||
|
||||
220
core/src/lib.rs
220
core/src/lib.rs
@@ -1,5 +1,7 @@
|
||||
use const_format::formatcp;
|
||||
|
||||
rust_i18n::i18n!("locales", fallback = ["en_US"]);
|
||||
|
||||
pub const DATA_DIR: &str = "/media/startos/data";
|
||||
pub const MAIN_DATA: &str = formatcp!("{DATA_DIR}/main");
|
||||
pub const PACKAGE_DATA: &str = formatcp!("{DATA_DIR}/package-data");
|
||||
@@ -8,7 +10,7 @@ pub use std::env::consts::ARCH;
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref PLATFORM: String = {
|
||||
if let Ok(platform) = std::fs::read_to_string("/usr/lib/startos/PLATFORM.txt") {
|
||||
platform
|
||||
platform.trim().to_string()
|
||||
} else {
|
||||
ARCH.to_string()
|
||||
}
|
||||
@@ -18,6 +20,17 @@ lazy_static::lazy_static! {
|
||||
};
|
||||
}
|
||||
|
||||
/// Map a platform string to its architecture
|
||||
pub fn platform_to_arch(platform: &str) -> &str {
|
||||
if let Some(arch) = platform.strip_suffix("-nonfree") {
|
||||
return arch;
|
||||
}
|
||||
match platform {
|
||||
"raspberrypi" | "rockchip64" => "aarch64",
|
||||
_ => platform,
|
||||
}
|
||||
}
|
||||
|
||||
mod cap {
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
@@ -97,6 +110,7 @@ use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable};
|
||||
#[command(rename_all = "kebab-case")]
|
||||
#[ts(export)]
|
||||
pub struct EchoParams {
|
||||
#[arg(help = "help.arg.echo-message")]
|
||||
message: String,
|
||||
}
|
||||
|
||||
@@ -122,80 +136,63 @@ pub fn main_api<C: Context>() -> ParentHandler<C> {
|
||||
let mut api = ParentHandler::new()
|
||||
.subcommand(
|
||||
"git-info",
|
||||
from_fn(|_: C| version::git_info()).with_about("Display the githash of StartOS CLI"),
|
||||
from_fn(|_: C| version::git_info()).with_about("about.display-githash"),
|
||||
)
|
||||
.subcommand(
|
||||
"echo",
|
||||
from_fn(echo::<RpcContext>)
|
||||
.with_metadata("authenticated", Value::Bool(false))
|
||||
.with_about("Echo a message")
|
||||
.with_about("about.echo-message")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"state",
|
||||
from_fn(|_: RpcContext| Ok::<_, Error>(ApiState::Running))
|
||||
.with_metadata("authenticated", Value::Bool(false))
|
||||
.with_about("Display the API that is currently serving")
|
||||
.with_about("about.display-current-api")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"state",
|
||||
from_fn(|_: InitContext| Ok::<_, Error>(ApiState::Initializing))
|
||||
.with_metadata("authenticated", Value::Bool(false))
|
||||
.with_about("Display the API that is currently serving")
|
||||
.with_about("about.display-current-api")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"state",
|
||||
from_fn(|_: DiagnosticContext| Ok::<_, Error>(ApiState::Error))
|
||||
.with_metadata("authenticated", Value::Bool(false))
|
||||
.with_about("Display the API that is currently serving")
|
||||
.with_about("about.display-current-api")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"server",
|
||||
server::<C>()
|
||||
.with_about("Commands related to the server i.e. restart, update, and shutdown"),
|
||||
)
|
||||
.subcommand("server", server::<C>().with_about("about.commands-server"))
|
||||
.subcommand(
|
||||
"package",
|
||||
package::<C>().with_about("Commands related to packages"),
|
||||
package::<C>().with_about("about.commands-packages"),
|
||||
)
|
||||
.subcommand(
|
||||
"net",
|
||||
net::net_api::<C>().with_about("Network commands related to tor and dhcp"),
|
||||
net::net_api::<C>().with_about("about.network-commands"),
|
||||
)
|
||||
.subcommand(
|
||||
"auth",
|
||||
auth::auth::<C, RpcContext>()
|
||||
.with_about("Commands related to Authentication i.e. login, logout"),
|
||||
)
|
||||
.subcommand(
|
||||
"db",
|
||||
db::db::<C>().with_about("Commands to interact with the db i.e. dump, put, apply"),
|
||||
)
|
||||
.subcommand(
|
||||
"ssh",
|
||||
ssh::ssh::<C>()
|
||||
.with_about("Commands for interacting with ssh keys i.e. add, delete, list"),
|
||||
auth::auth::<C, RpcContext>().with_about("about.commands-authentication"),
|
||||
)
|
||||
.subcommand("db", db::db::<C>().with_about("about.commands-db"))
|
||||
.subcommand("ssh", ssh::ssh::<C>().with_about("about.commands-ssh-keys"))
|
||||
.subcommand(
|
||||
"wifi",
|
||||
net::wifi::wifi::<C>()
|
||||
.with_about("Commands related to wifi networks i.e. add, connect, delete"),
|
||||
)
|
||||
.subcommand(
|
||||
"disk",
|
||||
disk::disk::<C>().with_about("Commands for listing disk info and repairing"),
|
||||
net::wifi::wifi::<C>().with_about("about.commands-wifi"),
|
||||
)
|
||||
.subcommand("disk", disk::disk::<C>().with_about("about.commands-disk"))
|
||||
.subcommand(
|
||||
"notification",
|
||||
notifications::notification::<C>().with_about("Create, delete, or list notifications"),
|
||||
notifications::notification::<C>().with_about("about.commands-notifications"),
|
||||
)
|
||||
.subcommand(
|
||||
"backup",
|
||||
backup::backup::<C>()
|
||||
.with_about("Commands related to backup creation and backup targets"),
|
||||
backup::backup::<C>().with_about("about.commands-backup"),
|
||||
)
|
||||
.subcommand(
|
||||
"registry",
|
||||
@@ -206,7 +203,7 @@ pub fn main_api<C: Context>() -> ParentHandler<C> {
|
||||
)
|
||||
.subcommand(
|
||||
"registry",
|
||||
registry::registry_api::<CliContext>().with_about("Commands related to the registry"),
|
||||
registry::registry_api::<CliContext>().with_about("about.commands-registry"),
|
||||
)
|
||||
.subcommand(
|
||||
"tunnel",
|
||||
@@ -215,41 +212,46 @@ pub fn main_api<C: Context>() -> ParentHandler<C> {
|
||||
)
|
||||
.subcommand(
|
||||
"tunnel",
|
||||
tunnel::api::tunnel_api::<CliContext>().with_about("Commands related to StartTunnel"),
|
||||
)
|
||||
.subcommand(
|
||||
"s9pk",
|
||||
s9pk::rpc::s9pk().with_about("Commands for interacting with s9pk files"),
|
||||
tunnel::api::tunnel_api::<CliContext>().with_about("about.commands-tunnel"),
|
||||
)
|
||||
.subcommand("s9pk", s9pk::rpc::s9pk().with_about("about.commands-s9pk"))
|
||||
.subcommand(
|
||||
"util",
|
||||
util::rpc::util::<C>().with_about("Command for calculating the blake3 hash of a file"),
|
||||
util::rpc::util::<C>().with_about("about.command-blake3-hash"),
|
||||
)
|
||||
.subcommand(
|
||||
"init-key",
|
||||
from_fn_async(developer::init)
|
||||
.no_display()
|
||||
.with_about("Create developer key if it doesn't exist"),
|
||||
.with_about("about.create-developer-key"),
|
||||
)
|
||||
.subcommand(
|
||||
"pubkey",
|
||||
from_fn_blocking(developer::pubkey)
|
||||
.with_about("Get public key for developer private key"),
|
||||
from_fn_blocking(developer::pubkey).with_about("about.get-developer-pubkey"),
|
||||
)
|
||||
.subcommand(
|
||||
"diagnostic",
|
||||
diagnostic::diagnostic::<C>()
|
||||
.with_about("Commands to display logs, restart the server, etc"),
|
||||
diagnostic::diagnostic::<C>().with_about("about.commands-diagnostic"),
|
||||
)
|
||||
.subcommand("init", init::init_api::<C>())
|
||||
.subcommand("setup", setup::setup::<C>())
|
||||
.subcommand(
|
||||
"install",
|
||||
os_install::install::<C>()
|
||||
.with_about("Commands to list disk info, install StartOS, and reboot"),
|
||||
"init",
|
||||
init::init_api::<C>().with_about("about.commands-init"),
|
||||
)
|
||||
.subcommand(
|
||||
"setup",
|
||||
setup::setup::<C>().with_about("about.commands-setup"),
|
||||
);
|
||||
if &*PLATFORM != "raspberrypi" {
|
||||
api = api.subcommand("kiosk", kiosk::<C>());
|
||||
api = api.subcommand("kiosk", kiosk::<C>().with_about("about.commands-kiosk"));
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
api = api.subcommand(
|
||||
"flash-os",
|
||||
from_fn_async(os_install::cli_install_os)
|
||||
.no_display()
|
||||
.with_about("about.flash-startos"),
|
||||
);
|
||||
}
|
||||
api
|
||||
}
|
||||
@@ -263,29 +265,32 @@ pub fn server<C: Context>() -> ParentHandler<C> {
|
||||
.with_custom_display_fn(|handle, result| {
|
||||
system::display_time(handle.params, result)
|
||||
})
|
||||
.with_about("Display current time and server uptime")
|
||||
.with_call_remote::<CliContext>()
|
||||
.with_about("about.display-time-uptime")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"experimental",
|
||||
system::experimental::<C>()
|
||||
.with_about("Commands related to configuring experimental options such as zram and cpu governor"),
|
||||
system::experimental::<C>().with_about("about.commands-experimental"),
|
||||
)
|
||||
.subcommand(
|
||||
"logs",
|
||||
system::logs::<RpcContext>().with_about("Display OS logs"),
|
||||
system::logs::<RpcContext>().with_about("about.display-os-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"logs",
|
||||
from_fn_async(logs::cli_logs::<RpcContext, Empty>).no_display().with_about("Display OS logs"),
|
||||
from_fn_async(logs::cli_logs::<RpcContext, Empty>)
|
||||
.no_display()
|
||||
.with_about("about.display-os-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"kernel-logs",
|
||||
system::kernel_logs::<RpcContext>().with_about("Display Kernel logs"),
|
||||
system::kernel_logs::<RpcContext>().with_about("about.display-kernel-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"kernel-logs",
|
||||
from_fn_async(logs::cli_logs::<RpcContext, Empty>).no_display().with_about("Display Kernel logs"),
|
||||
from_fn_async(logs::cli_logs::<RpcContext, Empty>)
|
||||
.no_display()
|
||||
.with_about("about.display-kernel-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"metrics",
|
||||
@@ -293,35 +298,31 @@ pub fn server<C: Context>() -> ParentHandler<C> {
|
||||
.root_handler(
|
||||
from_fn_async(system::metrics)
|
||||
.with_display_serializable()
|
||||
.with_about("Display information about the server i.e. temperature, RAM, CPU, and disk usage")
|
||||
.with_call_remote::<CliContext>()
|
||||
)
|
||||
.subcommand(
|
||||
"follow",
|
||||
from_fn_async(system::metrics_follow)
|
||||
.no_cli()
|
||||
.with_about("about.display-server-metrics")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand("follow", from_fn_async(system::metrics_follow).no_cli()),
|
||||
)
|
||||
.subcommand(
|
||||
"shutdown",
|
||||
from_fn_async(shutdown::shutdown)
|
||||
.no_display()
|
||||
.with_about("Shutdown the server")
|
||||
.with_call_remote::<CliContext>()
|
||||
.with_about("about.shutdown-server")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"restart",
|
||||
from_fn_async(shutdown::restart)
|
||||
.no_display()
|
||||
.with_about("Restart the server")
|
||||
.with_call_remote::<CliContext>()
|
||||
.with_about("about.restart-server")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"rebuild",
|
||||
from_fn_async(shutdown::rebuild)
|
||||
.no_display()
|
||||
.with_about("Teardown and rebuild service containers")
|
||||
.with_call_remote::<CliContext>()
|
||||
.with_about("about.teardown-rebuild-containers")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"update",
|
||||
@@ -331,7 +332,9 @@ pub fn server<C: Context>() -> ParentHandler<C> {
|
||||
)
|
||||
.subcommand(
|
||||
"update",
|
||||
from_fn_async(update::cli_update_system).no_display().with_about("Check a given registry for StartOS updates and update if available"),
|
||||
from_fn_async(update::cli_update_system)
|
||||
.no_display()
|
||||
.with_about("about.check-update-startos"),
|
||||
)
|
||||
.subcommand(
|
||||
"update-firmware",
|
||||
@@ -346,37 +349,55 @@ pub fn server<C: Context>() -> ParentHandler<C> {
|
||||
.with_custom_display_fn(|_handle, result| {
|
||||
Ok(firmware::display_firmware_update_result(result))
|
||||
})
|
||||
.with_about("Update the mainboard's firmware to the latest firmware available in this version of StartOS if available. Note: This command does not reach out to the Internet")
|
||||
.with_call_remote::<CliContext>()
|
||||
.with_about("about.update-firmware")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"set-smtp",
|
||||
from_fn_async(system::set_system_smtp)
|
||||
.no_display()
|
||||
.with_about("Set system smtp server and credentials")
|
||||
.with_call_remote::<CliContext>()
|
||||
.with_about("about.set-smtp")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"test-smtp",
|
||||
"test-smtp",
|
||||
from_fn_async(system::test_smtp)
|
||||
.no_display()
|
||||
.with_about("Send test email using provided smtp server and credentials")
|
||||
.with_call_remote::<CliContext>()
|
||||
.with_about("about.test-smtp")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"clear-smtp",
|
||||
from_fn_async(system::clear_system_smtp)
|
||||
.no_display()
|
||||
.with_about("Remove system smtp server and credentials")
|
||||
.with_call_remote::<CliContext>()
|
||||
).subcommand("host", net::host::server_host_api::<C>().with_about("Commands for modifying the host for the system ui"))
|
||||
.with_about("about.clear-smtp")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"host",
|
||||
net::host::server_host_api::<C>().with_about("about.commands-host-system-ui"),
|
||||
)
|
||||
.subcommand(
|
||||
"set-keyboard",
|
||||
from_fn_async(system::set_keyboard)
|
||||
.no_display()
|
||||
.with_about("about.set-keyboard")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"set-language",
|
||||
from_fn_async(system::set_language)
|
||||
.no_display()
|
||||
.with_about("about.set-language")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn package<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"action",
|
||||
action::action_api::<C>().with_about("Commands to get action input or run an action"),
|
||||
action::action_api::<C>().with_about("about.commands-action"),
|
||||
)
|
||||
.subcommand(
|
||||
"install",
|
||||
@@ -394,13 +415,13 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
||||
"install",
|
||||
from_fn_async_local(install::cli_install)
|
||||
.no_display()
|
||||
.with_about("Install a package from a marketplace or via sideloading"),
|
||||
.with_about("about.install-package"),
|
||||
)
|
||||
.subcommand(
|
||||
"cancel-install",
|
||||
from_fn(install::cancel_install)
|
||||
.no_display()
|
||||
.with_about("Cancel an install of a package")
|
||||
.with_about("about.cancel-install-package")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -408,21 +429,21 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(install::uninstall)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Remove a package")
|
||||
.with_about("about.remove-package")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"list",
|
||||
from_fn_async(install::list)
|
||||
.with_display_serializable()
|
||||
.with_about("List installed packages")
|
||||
.with_about("about.list-installed-packages")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"installed-version",
|
||||
from_fn_async(install::installed_version)
|
||||
.with_display_serializable()
|
||||
.with_about("Display installed version for a PackageId")
|
||||
.with_about("about.display-installed-version")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -430,7 +451,7 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(control::start)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Start a service")
|
||||
.with_about("about.start-service")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -438,7 +459,7 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(control::stop)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Stop a service")
|
||||
.with_about("about.stop-service")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -446,7 +467,7 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(control::restart)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Restart a service")
|
||||
.with_about("about.restart-service")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -454,7 +475,7 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(service::rebuild)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Rebuild service container")
|
||||
.with_about("about.rebuild-service-container")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -494,35 +515,34 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
||||
table.print_tty(false)?;
|
||||
Ok(())
|
||||
})
|
||||
.with_about("List information related to the lxc containers i.e. CPU, Memory, Disk")
|
||||
.with_about("about.list-lxc-container-info")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand("logs", logs::package_logs())
|
||||
.subcommand(
|
||||
"logs",
|
||||
logs::package_logs().with_about("Display package logs"),
|
||||
logs::package_logs().with_about("about.display-package-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"logs",
|
||||
from_fn_async(logs::cli_logs::<RpcContext, logs::PackageIdParams>)
|
||||
.no_display()
|
||||
.with_about("Display package logs"),
|
||||
.with_about("about.display-package-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"backup",
|
||||
backup::package_backup::<C>()
|
||||
.with_about("Commands for restoring package(s) from backup"),
|
||||
backup::package_backup::<C>().with_about("about.commands-restore-backup"),
|
||||
)
|
||||
.subcommand(
|
||||
"attach",
|
||||
from_fn_async(service::attach)
|
||||
.with_metadata("get_session", Value::Bool(true))
|
||||
.with_about("Execute commands within a service container")
|
||||
.with_about("about.execute-commands-container")
|
||||
.no_cli(),
|
||||
)
|
||||
.subcommand("attach", from_fn_async(service::cli_attach).no_display())
|
||||
.subcommand(
|
||||
"host",
|
||||
net::host::host_api::<C>().with_about("Manage network hosts for a package"),
|
||||
net::host::host_api::<C>().with_about("about.manage-network-hosts-package"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -232,6 +232,7 @@ pub const SYSTEM_UNIT: &str = "startd";
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct PackageIdParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
id: PackageId,
|
||||
}
|
||||
|
||||
@@ -327,14 +328,14 @@ pub struct LogsParams<Extra: FromArgMatches + Args = Empty> {
|
||||
#[command(flatten)]
|
||||
#[serde(flatten)]
|
||||
extra: Extra,
|
||||
#[arg(short = 'l', long = "limit")]
|
||||
#[arg(short = 'l', long = "limit", help = "help.arg.log-limit")]
|
||||
limit: Option<usize>,
|
||||
#[arg(short = 'c', long = "cursor", conflicts_with = "follow")]
|
||||
#[arg(short = 'c', long = "cursor", conflicts_with = "follow", help = "help.arg.log-cursor")]
|
||||
cursor: Option<String>,
|
||||
#[arg(short = 'b', long = "boot")]
|
||||
#[arg(short = 'b', long = "boot", help = "help.arg.log-boot")]
|
||||
#[serde(default)]
|
||||
boot: Option<BootIdentifier>,
|
||||
#[arg(short = 'B', long = "before", conflicts_with = "follow")]
|
||||
#[arg(short = 'B', long = "before", conflicts_with = "follow", help = "help.arg.log-before")]
|
||||
#[serde(default)]
|
||||
before: bool,
|
||||
}
|
||||
@@ -346,7 +347,7 @@ pub struct CliLogsParams<Extra: FromArgMatches + Args = Empty> {
|
||||
#[command(flatten)]
|
||||
#[serde(flatten)]
|
||||
rpc_params: LogsParams<Extra>,
|
||||
#[arg(short = 'f', long = "follow")]
|
||||
#[arg(short = 'f', long = "follow", help = "help.arg.log-follow")]
|
||||
#[serde(default)]
|
||||
follow: bool,
|
||||
}
|
||||
@@ -554,7 +555,7 @@ pub async fn journalctl(
|
||||
let mut child = follow_cmd.stdout(Stdio::piped()).spawn()?;
|
||||
let out =
|
||||
BufReader::new(child.stdout.take().ok_or_else(|| {
|
||||
Error::new(eyre!("No stdout available"), crate::ErrorKind::Journald)
|
||||
Error::new(eyre!("{}", t!("logs.no-stdout-available")), crate::ErrorKind::Journald)
|
||||
})?);
|
||||
|
||||
let journalctl_entries = LinesStream::new(out.lines());
|
||||
@@ -700,7 +701,7 @@ pub async fn follow_logs<Context: AsRef<RpcContinuations>>(
|
||||
RpcContinuation::ws(
|
||||
move |socket| async move {
|
||||
if let Err(e) = ws_handler(first_entry, stream, socket).await {
|
||||
tracing::error!("Error in log stream: {}", e);
|
||||
tracing::error!("{}", t!("logs.error-in-log-stream", error = e.to_string()));
|
||||
}
|
||||
},
|
||||
Duration::from_secs(30),
|
||||
|
||||
@@ -141,7 +141,7 @@ impl LxcManager {
|
||||
> 0
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("rootfs is not empty, refusing to delete"),
|
||||
eyre!("{}", t!("lxc.mod.rootfs-not-empty")),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
@@ -249,6 +249,7 @@ impl LxcContainer {
|
||||
.arg("-R")
|
||||
.arg("+C")
|
||||
.arg(&log_mount_point)
|
||||
.env("LANG", "C.UTF-8")
|
||||
.invoke(ErrorKind::Filesystem)
|
||||
.await
|
||||
{
|
||||
@@ -381,7 +382,7 @@ impl LxcContainer {
|
||||
}
|
||||
if start.elapsed() > CONTAINER_DHCP_TIMEOUT {
|
||||
return Err(Error::new(
|
||||
eyre!("Timed out waiting for container to acquire DHCP lease"),
|
||||
eyre!("{}", t!("lxc.mod.dhcp-timeout")),
|
||||
ErrorKind::Timeout,
|
||||
));
|
||||
}
|
||||
@@ -407,9 +408,12 @@ impl LxcContainer {
|
||||
if !output.status.success() {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Command failed with exit code: {:?} \n Message: {:?}",
|
||||
output.status.code(),
|
||||
String::from_utf8(output.stderr)
|
||||
"{}",
|
||||
t!(
|
||||
"lxc.mod.command-failed",
|
||||
code = format!("{:?}", output.status.code()),
|
||||
message = format!("{:?}", String::from_utf8(output.stderr))
|
||||
)
|
||||
),
|
||||
ErrorKind::Docker,
|
||||
));
|
||||
@@ -437,7 +441,7 @@ impl LxcContainer {
|
||||
> 0
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("rootfs is not empty, refusing to delete"),
|
||||
eyre!("{}", t!("lxc.mod.rootfs-not-empty")),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
@@ -473,13 +477,19 @@ impl LxcContainer {
|
||||
.await
|
||||
);
|
||||
return Err(Error::new(
|
||||
eyre!("timed out waiting for socket"),
|
||||
eyre!("{}", t!("lxc.mod.socket-timeout")),
|
||||
ErrorKind::Timeout,
|
||||
));
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
tracing::info!("Connected to socket in {:?}", started.elapsed());
|
||||
tracing::info!(
|
||||
"{}",
|
||||
t!(
|
||||
"lxc.mod.connected-to-socket",
|
||||
elapsed = format!("{:?}", started.elapsed())
|
||||
)
|
||||
);
|
||||
Ok(UnixRpcClient::new(sock_path))
|
||||
}
|
||||
|
||||
@@ -569,8 +579,11 @@ impl Drop for LxcContainer {
|
||||
fn drop(&mut self) {
|
||||
if !self.exited {
|
||||
tracing::warn!(
|
||||
"Container {} was ungracefully dropped. Cleaning up dangling containers...",
|
||||
&**self.guid
|
||||
"{}",
|
||||
t!(
|
||||
"lxc.mod.container-ungracefully-dropped",
|
||||
container = &**self.guid
|
||||
)
|
||||
);
|
||||
let rootfs = self.rootfs.take();
|
||||
let guid = std::mem::take(&mut self.guid);
|
||||
@@ -589,16 +602,25 @@ impl Drop for LxcContainer {
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("Error reading logs from crashed container: {e}");
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("lxc.mod.error-reading-crashed-logs", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}")
|
||||
}
|
||||
rootfs.unmount(true).await.log_err();
|
||||
drop(guid);
|
||||
if let Err(e) = manager.gc().await {
|
||||
tracing::error!("Error cleaning up dangling LXC containers: {e}");
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!(
|
||||
"lxc.mod.error-cleaning-up-containers",
|
||||
error = e.to_string()
|
||||
)
|
||||
);
|
||||
tracing::debug!("{e:?}")
|
||||
} else {
|
||||
tracing::info!("Successfully cleaned up dangling LXC containers");
|
||||
tracing::info!("{}", t!("lxc.mod.cleaned-up-containers"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,11 +11,6 @@ fn main() {
|
||||
"$CARGO_MANIFEST_DIR/../web/dist/static/setup-wizard"
|
||||
))
|
||||
.ok();
|
||||
startos::net::static_server::INSTALL_WIZARD_CELL
|
||||
.set(include_dir::include_dir!(
|
||||
"$CARGO_MANIFEST_DIR/../web/dist/static/install-wizard"
|
||||
))
|
||||
.ok();
|
||||
#[cfg(not(feature = "beta"))]
|
||||
startos::db::model::public::DB_UI_SEED_CELL
|
||||
.set(include_str!(concat!(
|
||||
|
||||
@@ -40,7 +40,7 @@ impl LocalAuthContext for RpcContext {
|
||||
}
|
||||
|
||||
fn unauthorized() -> Error {
|
||||
Error::new(eyre!("UNAUTHORIZED"), crate::ErrorKind::Authorization)
|
||||
Error::new(eyre!("{}", t!("middleware.auth.unauthorized")), crate::ErrorKind::Authorization)
|
||||
}
|
||||
|
||||
async fn check_from_header<C: LocalAuthContext>(header: Option<&HeaderValue>) -> Result<(), Error> {
|
||||
|
||||
@@ -146,7 +146,7 @@ impl HashSessionToken {
|
||||
}
|
||||
}
|
||||
Err(Error::new(
|
||||
eyre!("UNAUTHORIZED"),
|
||||
eyre!("{}", t!("middleware.auth.unauthorized")),
|
||||
crate::ErrorKind::Authorization,
|
||||
))
|
||||
}
|
||||
@@ -221,7 +221,7 @@ impl ValidSessionToken {
|
||||
}
|
||||
}
|
||||
Err(Error::new(
|
||||
eyre!("UNAUTHORIZED"),
|
||||
eyre!("{}", t!("middleware.auth.unauthorized")),
|
||||
crate::ErrorKind::Authorization,
|
||||
))
|
||||
}
|
||||
@@ -244,7 +244,7 @@ impl ValidSessionToken {
|
||||
C::access_sessions(db)
|
||||
.as_idx_mut(session_hash)
|
||||
.ok_or_else(|| {
|
||||
Error::new(eyre!("UNAUTHORIZED"), crate::ErrorKind::Authorization)
|
||||
Error::new(eyre!("{}", t!("middleware.auth.unauthorized")), crate::ErrorKind::Authorization)
|
||||
})?
|
||||
.mutate(|s| {
|
||||
s.last_active = Utc::now();
|
||||
@@ -305,7 +305,7 @@ impl<C: SessionAuthContext> Middleware<C> for SessionAuth {
|
||||
self.rate_limiter.mutate(|(count, time)| {
|
||||
if time.elapsed() < Duration::from_secs(20) && *count >= 3 {
|
||||
Err(Error::new(
|
||||
eyre!("Please limit login attempts to 3 per 20 seconds."),
|
||||
eyre!("{}", t!("middleware.auth.rate-limited-login")),
|
||||
crate::ErrorKind::RateLimited,
|
||||
))
|
||||
} else {
|
||||
|
||||
@@ -90,7 +90,7 @@ impl SignatureAuthContext for RpcContext {
|
||||
}
|
||||
|
||||
Err(Error::new(
|
||||
eyre!("Key is not authorized"),
|
||||
eyre!("{}", t!("middleware.auth.key-not-authorized")),
|
||||
ErrorKind::IncorrectPassword,
|
||||
))
|
||||
}
|
||||
@@ -141,7 +141,7 @@ impl SignatureAuth {
|
||||
let mut cache = self.nonce_cache.lock().await;
|
||||
if cache.values().any(|n| *n == nonce) {
|
||||
return Err(Error::new(
|
||||
eyre!("replay attack detected"),
|
||||
eyre!("{}", t!("middleware.auth.replay-attack-detected")),
|
||||
ErrorKind::Authorization,
|
||||
));
|
||||
}
|
||||
@@ -226,7 +226,7 @@ impl<C: SignatureAuthContext> Middleware<C> for SignatureAuth {
|
||||
|
||||
context.sig_context().await.into_iter().fold(
|
||||
Err(Error::new(
|
||||
eyre!("no valid signature context available to verify"),
|
||||
eyre!("{}", t!("middleware.auth.no-valid-sig-context")),
|
||||
ErrorKind::Authorization,
|
||||
)),
|
||||
|acc, x| {
|
||||
@@ -249,7 +249,7 @@ impl<C: SignatureAuthContext> Middleware<C> for SignatureAuth {
|
||||
.unwrap_or_else(|e| e.duration().as_secs() as i64 * -1);
|
||||
if (now - commitment.timestamp).abs() > 30 {
|
||||
return Err(Error::new(
|
||||
eyre!("timestamp not within 30s of now"),
|
||||
eyre!("{}", t!("middleware.auth.timestamp-not-within-30s")),
|
||||
ErrorKind::InvalidSignature,
|
||||
));
|
||||
}
|
||||
@@ -347,6 +347,6 @@ pub async fn call_remote<Ctx: SigningContext + AsRef<Client>>(
|
||||
.with_kind(ErrorKind::Deserialization)?
|
||||
.result
|
||||
}
|
||||
_ => Err(Error::new(eyre!("unknown content type"), ErrorKind::Network).into()),
|
||||
_ => Err(Error::new(eyre!("{}", t!("middleware.auth.unknown-content-type")), ErrorKind::Network).into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use axum::response::Response;
|
||||
use http::HeaderValue;
|
||||
use http::header::InvalidHeaderValue;
|
||||
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
||||
use rust_i18n::t;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
@@ -46,7 +47,7 @@ impl Middleware<RpcContext> for SyncDb {
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("error writing X-Patch-Sequence header: {e}");
|
||||
tracing::error!("{}", t!("middleware.db.error-writing-patch-sequence-header", error = e));
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,7 +395,7 @@ pub fn acme_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(init)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Setup ACME certificate acquisition")
|
||||
.with_about("about.setup-acme-certificate-acquisition")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -403,7 +403,7 @@ pub fn acme_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(remove)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Remove ACME certificate acquisition configuration")
|
||||
.with_about("about.remove-acme-certificate-acquisition-configuration")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -463,9 +463,9 @@ impl ValueParserFactory for AcmeProvider {
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct InitAcmeParams {
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.acme-provider")]
|
||||
pub provider: AcmeProvider,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.acme-contact")]
|
||||
pub contact: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -488,7 +488,7 @@ pub async fn init(
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct RemoveAcmeParams {
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.acme-provider")]
|
||||
pub provider: AcmeProvider,
|
||||
}
|
||||
|
||||
|
||||
@@ -54,13 +54,13 @@ pub fn dns_api<C: Context>() -> ParentHandler<C> {
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.with_about("Test the DNS configuration for a domain"),
|
||||
.with_about("about.test-dns-configuration-for-domain"),
|
||||
)
|
||||
.subcommand(
|
||||
"set-static",
|
||||
from_fn_async(set_static_dns)
|
||||
.no_display()
|
||||
.with_about("Set static DNS servers")
|
||||
.with_about("about.set-static-dns-servers")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -88,13 +88,14 @@ pub fn dns_api<C: Context>() -> ParentHandler<C> {
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.with_about("Dump address resolution table")
|
||||
.with_about("about.dump-address-resolution-table")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct QueryDnsParams {
|
||||
#[arg(help = "help.arg.fqdn")]
|
||||
pub fqdn: InternedString,
|
||||
}
|
||||
|
||||
@@ -134,6 +135,7 @@ pub fn query_dns<C: Context>(
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct SetStaticDnsParams {
|
||||
#[arg(help = "help.arg.dns-servers")]
|
||||
pub servers: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
@@ -292,7 +294,7 @@ impl Resolver {
|
||||
.await
|
||||
.map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("timed out waiting to update dns catalog"),
|
||||
eyre!("{}", t!("net.dns.timeout-updating-catalog")),
|
||||
ErrorKind::Timeout,
|
||||
)
|
||||
})?;
|
||||
@@ -348,7 +350,13 @@ impl Resolver {
|
||||
}) {
|
||||
return Some(res);
|
||||
} else {
|
||||
tracing::warn!("Could not determine source interface of {src}");
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"net.dns.could-not-determine-source-interface",
|
||||
src = src.to_string()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
if STARTOS.zone_of(name) || EMBASSY.zone_of(name) {
|
||||
@@ -473,7 +481,10 @@ impl RequestHandler for Resolver {
|
||||
Ok(Some(a)) => return a,
|
||||
Ok(None) => (),
|
||||
Err(e) => {
|
||||
tracing::error!("Error resolving internal DNS: {e}");
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("net.dns.error-resolving-internal", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
let mut header = Header::response_from_request(request.header());
|
||||
header.set_recursion_available(true);
|
||||
@@ -557,7 +568,7 @@ impl DnsController {
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("DNS Server Thread has exited"),
|
||||
eyre!("{}", t!("net.dns.server-thread-exited")),
|
||||
crate::ErrorKind::Network,
|
||||
))
|
||||
}
|
||||
@@ -577,7 +588,7 @@ impl DnsController {
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("DNS Server Thread has exited"),
|
||||
eyre!("{}", t!("net.dns.server-thread-exited")),
|
||||
crate::ErrorKind::Network,
|
||||
))
|
||||
}
|
||||
@@ -598,7 +609,7 @@ impl DnsController {
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("DNS Server Thread has exited"),
|
||||
eyre!("{}", t!("net.dns.server-thread-exited")),
|
||||
crate::ErrorKind::Network,
|
||||
))
|
||||
}
|
||||
@@ -624,7 +635,7 @@ impl DnsController {
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("DNS Server Thread has exited"),
|
||||
eyre!("{}", t!("net.dns.server-thread-exited")),
|
||||
crate::ErrorKind::Network,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ impl AvailablePorts {
|
||||
pub fn alloc(&mut self) -> Result<u16, Error> {
|
||||
self.0.request_id().ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("No more dynamic ports available!"),
|
||||
eyre!("{}", t!("net.forward.no-dynamic-ports-available")),
|
||||
ErrorKind::Network,
|
||||
)
|
||||
})
|
||||
@@ -240,7 +240,7 @@ impl PortForwardController {
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("error initializing PortForwardController: {e:#}");
|
||||
tracing::error!("{}", t!("net.forward.error-initializing-controller", error = format!("{e:#}")));
|
||||
tracing::debug!("{e:?}");
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
}
|
||||
@@ -400,7 +400,7 @@ impl InterfaceForwardEntry {
|
||||
) -> Result<Arc<()>, Error> {
|
||||
if external != self.external {
|
||||
return Err(Error::new(
|
||||
eyre!("Mismatched external port in InterfaceForwardEntry"),
|
||||
eyre!("{}", t!("net.forward.mismatched-external-port")),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
@@ -477,7 +477,7 @@ impl InterfaceForwardState {
|
||||
|
||||
fn err_has_exited<T>(_: T) -> Error {
|
||||
Error::new(
|
||||
eyre!("PortForwardController thread has exited"),
|
||||
eyre!("{}", t!("net.forward.controller-thread-exited")),
|
||||
ErrorKind::Unknown,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ pub fn gateway_api<C: Context>() -> ParentHandler<C> {
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.with_about("Show gateways StartOS can listen on")
|
||||
.with_about("about.show-gateways-startos-can-listen-on")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -103,7 +103,7 @@ pub fn gateway_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(set_public)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Indicate whether this gateway has inbound access from the WAN")
|
||||
.with_about("about.indicate-gateway-inbound-access-from-wan")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -111,10 +111,7 @@ pub fn gateway_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(unset_public)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about(concat!(
|
||||
"Allow this gateway to infer whether it has",
|
||||
" inbound access from the WAN based on its IPv4 address"
|
||||
))
|
||||
.with_about("about.allow-gateway-infer-inbound-access-from-wan")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -122,7 +119,7 @@ pub fn gateway_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(forget_iface)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Forget a disconnected gateway")
|
||||
.with_about("about.forget-disconnected-gateway")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -130,7 +127,7 @@ pub fn gateway_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(set_name)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Rename a gateway")
|
||||
.with_about("about.rename-gateway")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -143,7 +140,9 @@ async fn list_interfaces(
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||
struct NetworkInterfaceSetPublicParams {
|
||||
#[arg(help = "help.arg.gateway-id")]
|
||||
gateway: GatewayId,
|
||||
#[arg(help = "help.arg.is-public")]
|
||||
public: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -159,6 +158,7 @@ async fn set_public(
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||
struct UnsetPublicParams {
|
||||
#[arg(help = "help.arg.gateway-id")]
|
||||
gateway: GatewayId,
|
||||
}
|
||||
|
||||
@@ -174,6 +174,7 @@ async fn unset_public(
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||
struct ForgetGatewayParams {
|
||||
#[arg(help = "help.arg.gateway-id")]
|
||||
gateway: GatewayId,
|
||||
}
|
||||
|
||||
@@ -186,7 +187,9 @@ async fn forget_iface(
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||
struct RenameGatewayParams {
|
||||
#[arg(help = "help.arg.gateway-id")]
|
||||
id: GatewayId,
|
||||
#[arg(help = "help.arg.gateway-name")]
|
||||
name: InternedString,
|
||||
}
|
||||
|
||||
@@ -464,7 +467,8 @@ async fn watcher(
|
||||
ensure_code!(
|
||||
!devices.is_empty(),
|
||||
ErrorKind::Network,
|
||||
"NetworkManager returned no devices. Trying again..."
|
||||
"{}",
|
||||
t!("net.gateway.no-devices-returned")
|
||||
);
|
||||
let mut ifaces = BTreeSet::new();
|
||||
let mut jobs = Vec::new();
|
||||
@@ -731,7 +735,8 @@ async fn watch_ip(
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to determine WAN IP for {iface}: {e}"
|
||||
"{}",
|
||||
t!("net.gateway.failed-to-determine-wan-ip", iface = iface.to_string(), error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
None
|
||||
@@ -1021,7 +1026,13 @@ impl NetworkInterfaceController {
|
||||
info
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error loading network interface info: {e}");
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!(
|
||||
"net.gateway.error-loading-interface-info",
|
||||
error = e.to_string()
|
||||
)
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
OrdMap::new()
|
||||
}
|
||||
@@ -1050,7 +1061,10 @@ impl NetworkInterfaceController {
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("Error syncing ip info to db: {e}");
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("net.gateway.error-syncing-ip-info", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
|
||||
@@ -1060,7 +1074,10 @@ impl NetworkInterfaceController {
|
||||
}
|
||||
.await;
|
||||
if let Err(e) = res {
|
||||
tracing::error!("Error syncing ip info to db: {e}");
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("net.gateway.error-syncing-ip-info", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
})
|
||||
@@ -1121,7 +1138,7 @@ impl NetworkInterfaceController {
|
||||
.map_or(false, |i| i.ip_info.is_some())
|
||||
{
|
||||
err = Some(Error::new(
|
||||
eyre!("Cannot forget currently connected interface"),
|
||||
eyre!("{}", t!("net.gateway.cannot-forget-connected-interface")),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
return false;
|
||||
@@ -1167,7 +1184,7 @@ impl NetworkInterfaceController {
|
||||
|
||||
if &*ac == "/" {
|
||||
return Err(Error::new(
|
||||
eyre!("Cannot delete device without active connection"),
|
||||
eyre!("{}", t!("net.gateway.cannot-delete-without-connection")),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.with_inherited(|_, a| a)
|
||||
.no_display()
|
||||
.with_about("Add a public domain to this host")
|
||||
.with_about("about.add-public-domain-to-host")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -129,7 +129,7 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.with_inherited(|_, a| a)
|
||||
.no_display()
|
||||
.with_about("Remove a public domain from this host")
|
||||
.with_about("about.remove-public-domain-from-host")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.with_inherited(|_, a| a),
|
||||
@@ -143,7 +143,7 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.with_inherited(|_, a| a)
|
||||
.no_display()
|
||||
.with_about("Add a private domain to this host")
|
||||
.with_about("about.add-private-domain-to-host")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -152,7 +152,7 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.with_inherited(|_, a| a)
|
||||
.no_display()
|
||||
.with_about("Remove a private domain from this host")
|
||||
.with_about("about.remove-private-domain-from-host")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.with_inherited(|_, a| a),
|
||||
@@ -168,7 +168,7 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.with_inherited(|_, a| a)
|
||||
.no_display()
|
||||
.with_about("Add an address to this host")
|
||||
.with_about("about.add-address-to-host")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -177,7 +177,7 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.with_inherited(|_, a| a)
|
||||
.no_display()
|
||||
.with_about("Remove an address from this host")
|
||||
.with_about("about.remove-address-from-host")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.with_inherited(Kind::inheritance),
|
||||
@@ -230,16 +230,18 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.with_about("List addresses for this host")
|
||||
.with_about("about.list-addresses-for-host")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct AddPublicDomainParams {
|
||||
#[arg(help = "help.arg.fqdn")]
|
||||
pub fqdn: InternedString,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.acme-provider")]
|
||||
pub acme: Option<AcmeProvider>,
|
||||
#[arg(help = "help.arg.gateway-id")]
|
||||
pub gateway: GatewayId,
|
||||
}
|
||||
|
||||
@@ -284,6 +286,7 @@ pub async fn add_public_domain<Kind: HostApiKind>(
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct RemoveDomainParams {
|
||||
#[arg(help = "help.arg.fqdn")]
|
||||
pub fqdn: InternedString,
|
||||
}
|
||||
|
||||
@@ -307,6 +310,7 @@ pub async fn remove_public_domain<Kind: HostApiKind>(
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct AddPrivateDomainParams {
|
||||
#[arg(help = "help.arg.fqdn")]
|
||||
pub fqdn: InternedString,
|
||||
}
|
||||
|
||||
@@ -349,6 +353,7 @@ pub async fn remove_private_domain<Kind: HostApiKind>(
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct OnionParams {
|
||||
#[arg(help = "help.arg.onion-address")]
|
||||
pub onion: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ pub fn binding<C: Context, Kind: HostApiKind>()
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.with_about("List bindinges for this host")
|
||||
.with_about("about.list-bindings-for-host")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -218,7 +218,7 @@ pub fn binding<C: Context, Kind: HostApiKind>()
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.with_inherited(Kind::inheritance)
|
||||
.no_display()
|
||||
.with_about("Set whether this gateway should be enabled for this binding")
|
||||
.with_about("about.set-gateway-enabled-for-binding")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -237,9 +237,11 @@ pub async fn list_bindings<Kind: HostApiKind>(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct BindingGatewaySetEnabledParams {
|
||||
#[arg(help = "help.arg.internal-port")]
|
||||
internal_port: u16,
|
||||
#[arg(help = "help.arg.gateway-id")]
|
||||
gateway: GatewayId,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.binding-enabled")]
|
||||
enabled: Option<bool>,
|
||||
}
|
||||
|
||||
|
||||
@@ -166,11 +166,13 @@ impl Model<Host> {
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct RequiresPackageId {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
package: PackageId,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct RequiresHostId {
|
||||
#[arg(help = "help.arg.host-id")]
|
||||
host: HostId,
|
||||
}
|
||||
|
||||
@@ -243,7 +245,7 @@ pub fn host_api<C: Context>() -> ParentHandler<C, RequiresPackageId> {
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.with_about("List host IDs available for this service"),
|
||||
.with_about("about.list-host-ids-for-service"),
|
||||
)
|
||||
.subcommand(
|
||||
"address",
|
||||
|
||||
@@ -23,32 +23,29 @@ pub mod wifi;
|
||||
|
||||
pub fn net_api<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"tor",
|
||||
tor::tor_api::<C>().with_about("Tor commands such as list-services, logs, and reset"),
|
||||
)
|
||||
.subcommand("tor", tor::tor_api::<C>().with_about("about.tor-commands"))
|
||||
.subcommand(
|
||||
"acme",
|
||||
acme::acme_api::<C>().with_about("Setup automatic clearnet certificate acquisition"),
|
||||
acme::acme_api::<C>().with_about("about.setup-acme-certificate"),
|
||||
)
|
||||
.subcommand(
|
||||
"dns",
|
||||
dns::dns_api::<C>().with_about("Manage and query DNS"),
|
||||
dns::dns_api::<C>().with_about("about.manage-query-dns"),
|
||||
)
|
||||
.subcommand(
|
||||
"forward",
|
||||
forward::forward_api::<C>().with_about("Manage port forwards"),
|
||||
forward::forward_api::<C>().with_about("about.manage-port-forwards"),
|
||||
)
|
||||
.subcommand(
|
||||
"gateway",
|
||||
gateway::gateway_api::<C>().with_about("View and edit gateway configurations"),
|
||||
gateway::gateway_api::<C>().with_about("about.view-edit-gateway-configs"),
|
||||
)
|
||||
.subcommand(
|
||||
"tunnel",
|
||||
tunnel::tunnel_api::<C>().with_about("Manage tunnels"),
|
||||
tunnel::tunnel_api::<C>().with_about("about.manage-tunnels"),
|
||||
)
|
||||
.subcommand(
|
||||
"vhost",
|
||||
vhost::vhost_api::<C>().with_about("Manage ssl virtual host proxy"),
|
||||
vhost::vhost_api::<C>().with_about("about.manage-ssl-vhost-proxy"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ impl FullchainCertData {
|
||||
]
|
||||
.into_iter()
|
||||
.min()
|
||||
.ok_or_else(|| Error::new(eyre!("unreachable"), ErrorKind::Unknown))
|
||||
.ok_or_else(|| Error::new(eyre!("{}", t!("net.ssl.unreachable")), ErrorKind::Unknown))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt, BufReader};
|
||||
use tokio_util::io::ReaderStream;
|
||||
use url::Url;
|
||||
|
||||
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
||||
use crate::context::{DiagnosticContext, InitContext, RpcContext, SetupContext};
|
||||
use crate::hostname::Hostname;
|
||||
use crate::middleware::auth::Auth;
|
||||
use crate::middleware::auth::session::ValidSessionToken;
|
||||
@@ -178,20 +178,6 @@ impl UiContext for SetupContext {
|
||||
}
|
||||
}
|
||||
|
||||
pub static INSTALL_WIZARD_CELL: OnceLock<Dir<'static>> = OnceLock::new();
|
||||
|
||||
impl UiContext for InstallContext {
|
||||
fn ui_dir() -> &'static Dir<'static> {
|
||||
INSTALL_WIZARD_CELL.get().unwrap_or(&EMPTY_DIR)
|
||||
}
|
||||
fn api() -> ParentHandler<Self> {
|
||||
main_api()
|
||||
}
|
||||
fn middleware(server: Server<Self>) -> HttpServer<Self> {
|
||||
server.middleware(Cors::new())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rpc_router<C: Context + Clone + AsRef<RpcContinuations>>(
|
||||
ctx: C,
|
||||
server: HttpServer<C>,
|
||||
|
||||
@@ -107,7 +107,10 @@ impl TorSecretKey {
|
||||
Ok(Self(
|
||||
tor_llcrypto::pk::ed25519::ExpandedKeypair::from_secret_key_bytes(bytes)
|
||||
.ok_or_else(|| {
|
||||
Error::new(eyre!("invalid ed25519 expanded secret key"), ErrorKind::Tor)
|
||||
Error::new(
|
||||
eyre!("{}", t!("net.tor.invalid-ed25519-key")),
|
||||
ErrorKind::Tor,
|
||||
)
|
||||
})?
|
||||
.into(),
|
||||
))
|
||||
@@ -226,19 +229,19 @@ pub fn tor_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(list_services)
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|handle, result| display_services(handle.params, result))
|
||||
.with_about("Display Tor V3 Onion Addresses")
|
||||
.with_about("about.display-tor-v3-onion-addresses")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"reset",
|
||||
from_fn_async(reset)
|
||||
.no_display()
|
||||
.with_about("Reset Tor daemon")
|
||||
.with_about("about.reset-tor-daemon")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"key",
|
||||
key::<C>().with_about("Manage the onion service key store"),
|
||||
key::<C>().with_about("about.manage-onion-service-key-store"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -247,13 +250,13 @@ pub fn key<C: Context>() -> ParentHandler<C> {
|
||||
.subcommand(
|
||||
"generate",
|
||||
from_fn_async(generate_key)
|
||||
.with_about("Generate an onion service key and add it to the key store")
|
||||
.with_about("about.generate-onion-service-key-add-to-store")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"add",
|
||||
from_fn_async(add_key)
|
||||
.with_about("Add an onion service key to the key store")
|
||||
.with_about("about.add-onion-service-key-to-store")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -265,7 +268,7 @@ pub fn key<C: Context>() -> ParentHandler<C> {
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.with_about("List onion services with keys in the key store")
|
||||
.with_about("about.list-onion-services-with-keys-in-store")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -286,6 +289,7 @@ pub async fn generate_key(ctx: RpcContext) -> Result<OnionAddress, Error> {
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct AddKeyParams {
|
||||
#[arg(help = "help.arg.onion-secret-key")]
|
||||
pub key: Base64<[u8; 64]>,
|
||||
}
|
||||
|
||||
@@ -320,7 +324,7 @@ pub async fn list_keys(ctx: RpcContext) -> Result<BTreeSet<OnionAddress>, Error>
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ResetParams {
|
||||
#[arg(name = "wipe-state", short = 'w', long = "wipe-state")]
|
||||
#[arg(name = "wipe-state", short = 'w', long = "wipe-state", help = "help.arg.wipe-tor-state")]
|
||||
wipe_state: bool,
|
||||
}
|
||||
|
||||
@@ -447,9 +451,13 @@ impl TorController {
|
||||
if prev_inst.elapsed() > BOOTSTRAP_PROGRESS_TIMEOUT {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Bootstrap has not made progress for {}",
|
||||
crate::util::serde::Duration::from(
|
||||
BOOTSTRAP_PROGRESS_TIMEOUT
|
||||
"{}",
|
||||
t!(
|
||||
"net.tor.bootstrap-no-progress",
|
||||
duration = crate::util::serde::Duration::from(
|
||||
BOOTSTRAP_PROGRESS_TIMEOUT
|
||||
)
|
||||
.to_string()
|
||||
)
|
||||
),
|
||||
ErrorKind::Tor,
|
||||
@@ -466,7 +474,10 @@ impl TorController {
|
||||
res = bootstrap_fut => res,
|
||||
res = failure_fut => res,
|
||||
} {
|
||||
tracing::error!("Tor Bootstrap Error: {e}");
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("net.tor.bootstrap-error", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
} else {
|
||||
bootstrapper_client.send_modify(|_| ());
|
||||
@@ -516,7 +527,13 @@ impl TorController {
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("Tor Health Error: {e}");
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!(
|
||||
"net.tor.health-error",
|
||||
error = e.to_string()
|
||||
)
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
});
|
||||
@@ -529,7 +546,10 @@ impl TorController {
|
||||
}
|
||||
}
|
||||
Err(Error::new(
|
||||
eyre!("status event stream ended"),
|
||||
eyre!(
|
||||
"{}",
|
||||
t!("net.tor.status-stream-ended")
|
||||
),
|
||||
ErrorKind::Tor,
|
||||
))
|
||||
})
|
||||
@@ -560,13 +580,19 @@ impl TorController {
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("Tor Client Health Error: {e}");
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("net.tor.client-health-error", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
}
|
||||
tracing::error!(
|
||||
"Client failed health check {} times, recycling",
|
||||
HEALTH_CHECK_FAILURE_ALLOWANCE
|
||||
"{}",
|
||||
t!(
|
||||
"net.tor.health-check-failed-recycling",
|
||||
count = HEALTH_CHECK_FAILURE_ALLOWANCE
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -574,7 +600,10 @@ impl TorController {
|
||||
})
|
||||
.await
|
||||
{
|
||||
tracing::error!("Tor Bootstrapper Error: {e}");
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("net.tor.bootstrapper-error", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
if let Err::<(), Error>(e) = async {
|
||||
@@ -592,7 +621,10 @@ impl TorController {
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("Tor Client Creation Error: {e}");
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("net.tor.client-creation-error", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
}
|
||||
@@ -684,7 +716,10 @@ impl TorController {
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
if let Err(e) = socket2::SockRef::from(&tcp_stream).set_keepalive(true) {
|
||||
tracing::error!("Failed to set tcp keepalive: {e}");
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("net.tor.failed-to-set-tcp-keepalive", error = e.to_string())
|
||||
);
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
Ok(Box::new(tcp_stream))
|
||||
@@ -812,7 +847,7 @@ impl OnionService {
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
if let Err(e) = socket2::SockRef::from(&outgoing).set_keepalive(true) {
|
||||
tracing::error!("Failed to set tcp keepalive: {e}");
|
||||
tracing::error!("{}", t!("net.tor.failed-to-set-tcp-keepalive", error = e.to_string()));
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
let mut incoming = req
|
||||
@@ -852,7 +887,7 @@ impl OnionService {
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("Tor Client Error: {e}");
|
||||
tracing::error!("{}", t!("net.tor.client-error", error = e.to_string()));
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,26 +249,26 @@ pub fn tor_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(list_services)
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|handle, result| display_services(handle.params, result))
|
||||
.with_about("Display Tor V3 Onion Addresses")
|
||||
.with_about("about.display-tor-v3-onion-addresses")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand("logs", logs().with_about("Display Tor logs"))
|
||||
.subcommand("logs", logs().with_about("about.display-tor-logs"))
|
||||
.subcommand(
|
||||
"logs",
|
||||
from_fn_async(crate::logs::cli_logs::<RpcContext, Empty>)
|
||||
.no_display()
|
||||
.with_about("Display Tor logs"),
|
||||
.with_about("about.display-tor-logs"),
|
||||
)
|
||||
.subcommand(
|
||||
"reset",
|
||||
from_fn_async(reset)
|
||||
.no_display()
|
||||
.with_about("Reset Tor daemon")
|
||||
.with_about("about.reset-tor-daemon")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"key",
|
||||
key::<C>().with_about("Manage the onion service key store"),
|
||||
key::<C>().with_about("about.manage-onion-service-key-store"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -277,13 +277,13 @@ pub fn key<C: Context>() -> ParentHandler<C> {
|
||||
.subcommand(
|
||||
"generate",
|
||||
from_fn_async(generate_key)
|
||||
.with_about("Generate an onion service key and add it to the key store")
|
||||
.with_about("about.generate-onion-service-key-add-to-store")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"add",
|
||||
from_fn_async(add_key)
|
||||
.with_about("Add an onion service key to the key store")
|
||||
.with_about("about.add-onion-service-key-to-store")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -295,7 +295,7 @@ pub fn key<C: Context>() -> ParentHandler<C> {
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.with_about("List onion services with keys in the key store")
|
||||
.with_about("about.list-onion-services-with-keys-in-store")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -316,6 +316,7 @@ pub async fn generate_key(ctx: RpcContext) -> Result<OnionAddress, Error> {
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct AddKeyParams {
|
||||
#[arg(help = "help.arg.onion-secret-key")]
|
||||
pub key: Base64<[u8; 64]>,
|
||||
}
|
||||
|
||||
@@ -350,8 +351,9 @@ pub async fn list_keys(ctx: RpcContext) -> Result<BTreeSet<OnionAddress>, Error>
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ResetParams {
|
||||
#[arg(name = "wipe-state", short = 'w', long = "wipe-state")]
|
||||
#[arg(name = "wipe-state", short = 'w', long = "wipe-state", help = "help.arg.wipe-tor-state")]
|
||||
wipe_state: bool,
|
||||
#[arg(help = "help.arg.reset-reason")]
|
||||
reason: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -19,14 +19,14 @@ pub fn tunnel_api<C: Context>() -> ParentHandler<C> {
|
||||
.subcommand(
|
||||
"add",
|
||||
from_fn_async(add_tunnel)
|
||||
.with_about("Add a new tunnel")
|
||||
.with_about("about.add-new-tunnel")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"remove",
|
||||
from_fn_async(remove_tunnel)
|
||||
.no_display()
|
||||
.with_about("Remove a tunnel")
|
||||
.with_about("about.remove-tunnel")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -34,8 +34,11 @@ pub fn tunnel_api<C: Context>() -> ParentHandler<C> {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||
#[ts(export)]
|
||||
pub struct AddTunnelParams {
|
||||
#[arg(help = "help.arg.tunnel-name")]
|
||||
name: InternedString,
|
||||
#[arg(help = "help.arg.wireguard-config")]
|
||||
config: String,
|
||||
#[arg(help = "help.arg.is-public")]
|
||||
public: bool,
|
||||
}
|
||||
|
||||
@@ -123,6 +126,7 @@ pub async fn add_tunnel(
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||
#[ts(export)]
|
||||
pub struct RemoveTunnelParams {
|
||||
#[arg(help = "help.arg.gateway-id")]
|
||||
id: GatewayId,
|
||||
}
|
||||
pub async fn remove_tunnel(
|
||||
|
||||
@@ -30,7 +30,7 @@ type WifiManager = Arc<RwLock<Option<WpaCli>>>;
|
||||
// Ok(wifi_manager)
|
||||
// } else {
|
||||
// Err(Error::new(
|
||||
// color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
// color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")),
|
||||
// ErrorKind::Wifi,
|
||||
// ))
|
||||
// }
|
||||
@@ -42,28 +42,28 @@ pub fn wifi<C: Context>() -> ParentHandler<C> {
|
||||
"set-enabled",
|
||||
from_fn_async(set_enabled)
|
||||
.no_display()
|
||||
.with_about("Enable or disable wifi")
|
||||
.with_about("about.enable-disable-wifi")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"add",
|
||||
from_fn_async(add)
|
||||
.no_display()
|
||||
.with_about("Add wifi ssid and password")
|
||||
.with_about("about.add-wifi-ssid-password")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"connect",
|
||||
from_fn_async(connect)
|
||||
.no_display()
|
||||
.with_about("Connect to wifi network")
|
||||
.with_about("about.connect-wifi-network")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"remove",
|
||||
from_fn_async(remove)
|
||||
.no_display()
|
||||
.with_about("Remove a wifi network")
|
||||
.with_about("about.remove-wifi-network")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -71,16 +71,16 @@ pub fn wifi<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(get)
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|handle, result| display_wifi_info(handle.params, result))
|
||||
.with_about("List wifi info")
|
||||
.with_about("about.list-wifi-info")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"country",
|
||||
country::<C>().with_about("Command to set country"),
|
||||
country::<C>().with_about("about.command-set-country"),
|
||||
)
|
||||
.subcommand(
|
||||
"available",
|
||||
available::<C>().with_about("Command to list available wifi networks"),
|
||||
available::<C>().with_about("about.command-list-available-wifi"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ pub fn wifi<C: Context>() -> ParentHandler<C> {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct SetWifiEnabledParams {
|
||||
#[arg(help = "help.arg.wifi-enabled")]
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
@@ -133,7 +134,7 @@ pub fn available<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(get_available)
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|handle, result| display_wifi_list(handle.params, result))
|
||||
.with_about("List available wifi networks")
|
||||
.with_about("about.list-available-wifi-networks")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -143,7 +144,7 @@ pub fn country<C: Context>() -> ParentHandler<C> {
|
||||
"set",
|
||||
from_fn_async(set_country)
|
||||
.no_display()
|
||||
.with_about("Set Country")
|
||||
.with_about("about.set-country")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -152,7 +153,9 @@ pub fn country<C: Context>() -> ParentHandler<C> {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct AddParams {
|
||||
#[arg(help = "help.arg.wifi-ssid")]
|
||||
ssid: String,
|
||||
#[arg(help = "help.arg.wifi-password")]
|
||||
password: String,
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
@@ -160,13 +163,13 @@ pub async fn add(ctx: RpcContext, AddParams { ssid, password }: AddParams) -> Re
|
||||
let wifi_manager = ctx.wifi_manager.clone();
|
||||
if !ssid.is_ascii() {
|
||||
return Err(Error::new(
|
||||
color_eyre::eyre::eyre!("SSID may not have special characters"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.ssid-no-special-characters")),
|
||||
ErrorKind::Wifi,
|
||||
));
|
||||
}
|
||||
if !password.is_ascii() {
|
||||
return Err(Error::new(
|
||||
color_eyre::eyre::eyre!("WiFi Password may not have special characters"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.password-no-special-characters")),
|
||||
ErrorKind::Wifi,
|
||||
));
|
||||
}
|
||||
@@ -176,11 +179,11 @@ pub async fn add(ctx: RpcContext, AddParams { ssid, password }: AddParams) -> Re
|
||||
ssid: &Ssid,
|
||||
password: &Psk,
|
||||
) -> Result<(), Error> {
|
||||
tracing::info!("Adding new WiFi network: '{}'", ssid.0);
|
||||
tracing::info!("{}", t!("net.wifi.adding-network", ssid = &ssid.0));
|
||||
let mut wpa_supplicant = wifi_manager.write_owned().await;
|
||||
let wpa_supplicant = wpa_supplicant.as_mut().ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?;
|
||||
@@ -195,10 +198,17 @@ pub async fn add(ctx: RpcContext, AddParams { ssid, password }: AddParams) -> Re
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to add new WiFi network '{}': {}", ssid, err);
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!(
|
||||
"net.wifi.failed-to-add-network",
|
||||
ssid = &ssid,
|
||||
error = err.to_string()
|
||||
)
|
||||
);
|
||||
tracing::debug!("{:?}", err);
|
||||
return Err(Error::new(
|
||||
color_eyre::eyre::eyre!("Failed adding {}", ssid),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.failed-adding", ssid = &ssid)),
|
||||
ErrorKind::Wifi,
|
||||
));
|
||||
}
|
||||
@@ -222,6 +232,7 @@ pub async fn add(ctx: RpcContext, AddParams { ssid, password }: AddParams) -> Re
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct SsidParams {
|
||||
#[arg(help = "help.arg.wifi-ssid")]
|
||||
ssid: String,
|
||||
}
|
||||
|
||||
@@ -230,7 +241,7 @@ pub async fn connect(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result
|
||||
let wifi_manager = ctx.wifi_manager.clone();
|
||||
if !ssid.is_ascii() {
|
||||
return Err(Error::new(
|
||||
color_eyre::eyre::eyre!("SSID may not have special characters"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.ssid-no-special-characters")),
|
||||
ErrorKind::Wifi,
|
||||
));
|
||||
}
|
||||
@@ -242,19 +253,19 @@ pub async fn connect(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result
|
||||
let mut wpa_supplicant = wifi_manager.write_owned().await;
|
||||
let wpa_supplicant = wpa_supplicant.as_mut().ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?;
|
||||
let current = wpa_supplicant.get_current_network().await?;
|
||||
let connected = wpa_supplicant.select_network(db.clone(), ssid).await?;
|
||||
if connected {
|
||||
tracing::info!("Successfully connected to WiFi: '{}'", ssid.0);
|
||||
tracing::info!("{}", t!("net.wifi.connected-successfully", ssid = &ssid.0));
|
||||
} else {
|
||||
tracing::info!("Failed to connect to WiFi: '{}'", ssid.0);
|
||||
tracing::info!("{}", t!("net.wifi.connection-failed", ssid = &ssid.0));
|
||||
match current {
|
||||
None => {
|
||||
tracing::info!("No WiFi to revert to!");
|
||||
tracing::info!("{}", t!("net.wifi.no-wifi-to-revert"));
|
||||
}
|
||||
Some(current) => {
|
||||
wpa_supplicant.select_network(db, ¤t).await?;
|
||||
@@ -267,9 +278,16 @@ pub async fn connect(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result
|
||||
if let Err(err) =
|
||||
connect_procedure(ctx.db.clone(), wifi_manager.clone(), &Ssid(ssid.clone())).await
|
||||
{
|
||||
tracing::error!("Failed to connect to WiFi network '{}': {}", &ssid, err);
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!(
|
||||
"net.wifi.failed-to-connect",
|
||||
ssid = &ssid,
|
||||
error = err.to_string()
|
||||
)
|
||||
);
|
||||
return Err(Error::new(
|
||||
color_eyre::eyre::eyre!("Can't connect to {}", ssid),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.cant-connect", ssid = &ssid)),
|
||||
ErrorKind::Wifi,
|
||||
));
|
||||
}
|
||||
@@ -297,7 +315,7 @@ pub async fn remove(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result<
|
||||
let wifi_manager = ctx.wifi_manager.clone();
|
||||
if !ssid.is_ascii() {
|
||||
return Err(Error::new(
|
||||
color_eyre::eyre::eyre!("SSID may not have special characters"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.ssid-no-special-characters")),
|
||||
ErrorKind::Wifi,
|
||||
));
|
||||
}
|
||||
@@ -305,7 +323,7 @@ pub async fn remove(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result<
|
||||
let mut wpa_supplicant = wifi_manager.write_owned().await;
|
||||
let wpa_supplicant = wpa_supplicant.as_mut().ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?;
|
||||
@@ -316,9 +334,7 @@ pub async fn remove(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result<
|
||||
is_current_being_removed && !interface_connected(&ctx.ethernet_interface).await?;
|
||||
if is_current_removed_and_no_hardwire {
|
||||
return Err(Error::new(
|
||||
color_eyre::eyre::eyre!(
|
||||
"Forbidden: Deleting this network would make your server unreachable. Either connect to ethernet or connect to a different WiFi network to remedy this."
|
||||
),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.forbidden-delete-would-disconnect")),
|
||||
ErrorKind::Wifi,
|
||||
));
|
||||
}
|
||||
@@ -463,7 +479,7 @@ pub async fn get(ctx: RpcContext, _: Empty) -> Result<WifiListInfo, Error> {
|
||||
let wpa_supplicant = wifi_manager.read_owned().await;
|
||||
let wpa_supplicant = wpa_supplicant.as_ref().ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?;
|
||||
@@ -517,7 +533,7 @@ pub async fn get_available(ctx: RpcContext, _: Empty) -> Result<Vec<WifiListOut>
|
||||
let wpa_supplicant = wifi_manager.read_owned().await;
|
||||
let wpa_supplicant = wpa_supplicant.as_ref().ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?;
|
||||
@@ -547,7 +563,7 @@ pub async fn get_available(ctx: RpcContext, _: Empty) -> Result<Vec<WifiListOut>
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct SetCountryParams {
|
||||
#[arg(value_parser = CountryCodeParser)]
|
||||
#[arg(value_parser = CountryCodeParser, help = "help.arg.wifi-country-code")]
|
||||
#[ts(type = "string")]
|
||||
country: CountryCode,
|
||||
}
|
||||
@@ -558,14 +574,14 @@ pub async fn set_country(
|
||||
let wifi_manager = ctx.wifi_manager.clone();
|
||||
if !interface_connected(&ctx.ethernet_interface).await? {
|
||||
return Err(Error::new(
|
||||
color_eyre::eyre::eyre!("Won't change country without hardwire connection"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.wont-change-country-without-ethernet")),
|
||||
crate::ErrorKind::Wifi,
|
||||
));
|
||||
}
|
||||
let mut wpa_supplicant = wifi_manager.write_owned().await;
|
||||
let wpa_supplicant = wpa_supplicant.as_mut().ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("No WiFi interface available"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?;
|
||||
@@ -684,7 +700,14 @@ impl WpaCli {
|
||||
.await
|
||||
.map(|_| ())
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::warn!("Failed to set interface {} for {}", self.interface, ssid.0);
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"net.wifi.failed-to-set-interface",
|
||||
interface = &self.interface,
|
||||
ssid = &ssid.0
|
||||
)
|
||||
);
|
||||
tracing::debug!("{:?}", e);
|
||||
});
|
||||
Command::new("nmcli")
|
||||
@@ -719,13 +742,13 @@ impl WpaCli {
|
||||
}
|
||||
let first_country = r.lines().find(|s| s.contains("country")).ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("Could not find a country config lines"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.could-not-find-country-config")),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?;
|
||||
let country = &RE.captures(first_country).ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("Could not find a country config with regex"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.could-not-parse-country-config")),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?[1];
|
||||
@@ -734,7 +757,10 @@ impl WpaCli {
|
||||
} else {
|
||||
Ok(Some(CountryCode::for_alpha2(country).map_err(|_| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("Invalid Country Code: {}", country),
|
||||
color_eyre::eyre::eyre!(
|
||||
"{}",
|
||||
t!("net.wifi.invalid-country-code", country = country)
|
||||
),
|
||||
ErrorKind::Wifi,
|
||||
)
|
||||
})?))
|
||||
@@ -877,7 +903,7 @@ impl WpaCli {
|
||||
let m_id = self.check_active_network(ssid).await?;
|
||||
match m_id {
|
||||
None => Err(Error::new(
|
||||
color_eyre::eyre::eyre!("SSID Not Found"),
|
||||
color_eyre::eyre::eyre!("{}", t!("net.wifi.ssid-not-found")),
|
||||
ErrorKind::Wifi,
|
||||
)),
|
||||
Some(x) => {
|
||||
@@ -1058,7 +1084,7 @@ pub async fn synchronize_network_manager<P: AsRef<Path>>(
|
||||
.invoke(ErrorKind::Wifi)
|
||||
.await?;
|
||||
if let Some(last_country_code) = wifi.last_region {
|
||||
tracing::info!("Setting the region");
|
||||
tracing::info!("{}", t!("net.wifi.setting-region"));
|
||||
let _ = Command::new("iw")
|
||||
.arg("reg")
|
||||
.arg("set")
|
||||
@@ -1066,7 +1092,7 @@ pub async fn synchronize_network_manager<P: AsRef<Path>>(
|
||||
.invoke(ErrorKind::Wifi)
|
||||
.await?;
|
||||
} else {
|
||||
tracing::info!("Setting the region fallback");
|
||||
tracing::info!("{}", t!("net.wifi.setting-region-fallback"));
|
||||
let _ = Command::new("iw")
|
||||
.arg("reg")
|
||||
.arg("set")
|
||||
|
||||
@@ -27,49 +27,49 @@ pub fn notification<C: Context>() -> ParentHandler<C> {
|
||||
"list",
|
||||
from_fn_async(list)
|
||||
.with_display_serializable()
|
||||
.with_about("List notifications")
|
||||
.with_about("about.list-notifications")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"remove",
|
||||
from_fn_async(remove)
|
||||
.no_display()
|
||||
.with_about("Remove notification for given ids")
|
||||
.with_about("about.remove-notification-for-ids")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"remove-before",
|
||||
from_fn_async(remove_before)
|
||||
.no_display()
|
||||
.with_about("Remove notifications preceding a given id")
|
||||
.with_about("about.remove-notifications-before-id")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"mark-seen",
|
||||
from_fn_async(mark_seen)
|
||||
.no_display()
|
||||
.with_about("Mark given notifications as seen")
|
||||
.with_about("about.mark-notifications-seen")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"mark-seen-before",
|
||||
from_fn_async(mark_seen_before)
|
||||
.no_display()
|
||||
.with_about("Mark notifications preceding a given id as seen")
|
||||
.with_about("about.mark-notifications-seen-before-id")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"mark-unseen",
|
||||
from_fn_async(mark_unseen)
|
||||
.no_display()
|
||||
.with_about("Mark given notifications as unseen")
|
||||
.with_about("about.mark-notifications-unseen")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"create",
|
||||
from_fn_async(create)
|
||||
.no_display()
|
||||
.with_about("Persist a newly created notification")
|
||||
.with_about("about.persist-new-notification")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -78,8 +78,10 @@ pub fn notification<C: Context>() -> ParentHandler<C> {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ListNotificationParams {
|
||||
#[arg(help = "help.arg.notification-before-id")]
|
||||
#[ts(type = "number | null")]
|
||||
before: Option<u32>,
|
||||
#[arg(help = "help.arg.notification-limit")]
|
||||
#[ts(type = "number | null")]
|
||||
limit: Option<usize>,
|
||||
}
|
||||
@@ -141,6 +143,7 @@ pub async fn list(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ModifyNotificationParams {
|
||||
#[arg(help = "help.arg.notification-ids")]
|
||||
#[ts(type = "number[]")]
|
||||
ids: Vec<u32>,
|
||||
}
|
||||
@@ -175,6 +178,7 @@ pub async fn remove(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ModifyNotificationBeforeParams {
|
||||
#[arg(help = "help.arg.notification-before-id")]
|
||||
#[ts(type = "number")]
|
||||
before: u32,
|
||||
}
|
||||
@@ -296,9 +300,13 @@ pub async fn mark_unseen(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct CreateParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
package: Option<PackageId>,
|
||||
#[arg(help = "help.arg.notification-level")]
|
||||
level: NotificationLevel,
|
||||
#[arg(help = "help.arg.notification-title")]
|
||||
title: String,
|
||||
#[arg(help = "help.arg.notification-message")]
|
||||
message: String,
|
||||
}
|
||||
|
||||
@@ -346,7 +354,7 @@ pub struct InvalidNotificationLevel(String);
|
||||
impl From<InvalidNotificationLevel> for crate::Error {
|
||||
fn from(val: InvalidNotificationLevel) -> Self {
|
||||
Error::new(
|
||||
eyre!("Invalid Notification Level: {}", val.0),
|
||||
eyre!("{}", t!("notifications.invalid-level", level = val.0)),
|
||||
ErrorKind::ParseDbField,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,138 +1,207 @@
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use gpt::GptConfig;
|
||||
use gpt::disk::LogicalBlockSize;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::disk::util::DiskInfo;
|
||||
use crate::os_install::partition_for;
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
|
||||
pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result<OsPartitionInfo, Error> {
|
||||
let efi = {
|
||||
let disk = disk.clone();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let use_efi = Path::new("/sys/firmware/efi").exists();
|
||||
let mut device = Box::new(
|
||||
std::fs::File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&disk.logicalname)?,
|
||||
);
|
||||
let (mut gpt, guid_part) = if overwrite {
|
||||
let mbr = gpt::mbr::ProtectiveMBR::with_lb_size(
|
||||
u32::try_from((disk.capacity / 512) - 1).unwrap_or(0xFF_FF_FF_FF),
|
||||
);
|
||||
mbr.overwrite_lba0(&mut device)?;
|
||||
(
|
||||
GptConfig::new()
|
||||
.writable(true)
|
||||
.logical_block_size(LogicalBlockSize::Lb512)
|
||||
.create_from_device(device, None)?,
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
let gpt = GptConfig::new()
|
||||
.writable(true)
|
||||
pub async fn partition(
|
||||
disk_path: &Path,
|
||||
capacity: u64,
|
||||
protect: Option<&Path>,
|
||||
use_efi: bool,
|
||||
) -> Result<OsPartitionInfo, Error> {
|
||||
// Guard: cannot protect the whole disk
|
||||
if let Some(p) = protect {
|
||||
if p == disk_path {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Cannot protect the entire disk {}; must specify a partition",
|
||||
disk_path.display()
|
||||
),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let disk_path = disk_path.to_owned();
|
||||
let disk_path_clone = disk_path.clone();
|
||||
let protect = protect.map(|p| p.to_owned());
|
||||
let (efi, data_part) = tokio::task::spawn_blocking(move || {
|
||||
let disk_path = disk_path_clone;
|
||||
|
||||
let protected_partition_info: Option<(u64, u64, PathBuf)> =
|
||||
if let Some(ref protect_path) = protect {
|
||||
let existing_gpt = GptConfig::new()
|
||||
.writable(false)
|
||||
.logical_block_size(LogicalBlockSize::Lb512)
|
||||
.open_from_device(device)?;
|
||||
let mut guid_part = None;
|
||||
for (idx, part_info) in disk
|
||||
.partitions
|
||||
.open_from_device(Box::new(
|
||||
std::fs::File::options().read(true).open(&disk_path)?,
|
||||
))?;
|
||||
let info = existing_gpt
|
||||
.partitions()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, x)| (idx + 1, x))
|
||||
{
|
||||
if let Some(entry) = gpt.partitions().get(&(idx as u32)) {
|
||||
if part_info.guid.is_some() {
|
||||
if entry.first_lba < if use_efi { 33759266 } else { 33570850 } {
|
||||
return Err(Error::new(
|
||||
eyre!("Not enough space before StartOS data"),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
guid_part = Some(entry.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
.find(|(num, _)| partition_for(&disk_path, **num) == *protect_path)
|
||||
.map(|(_, p)| (p.first_lba, p.last_lba, protect_path.clone()));
|
||||
if info.is_none() {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Protected partition {} not found in GPT on {}",
|
||||
protect_path.display(),
|
||||
disk_path.display()
|
||||
),
|
||||
crate::ErrorKind::NotFound,
|
||||
));
|
||||
}
|
||||
(gpt, guid_part)
|
||||
};
|
||||
|
||||
gpt.update_partitions(Default::default())?;
|
||||
|
||||
let efi = if use_efi {
|
||||
gpt.add_partition("efi", 100 * 1024 * 1024, gpt::partition_types::EFI, 0, None)?;
|
||||
true
|
||||
info
|
||||
} else {
|
||||
gpt.add_partition(
|
||||
"bios-grub",
|
||||
8 * 1024 * 1024,
|
||||
gpt::partition_types::BIOS,
|
||||
0,
|
||||
None,
|
||||
)?;
|
||||
false
|
||||
None
|
||||
};
|
||||
gpt.add_partition(
|
||||
"boot",
|
||||
1024 * 1024 * 1024,
|
||||
gpt::partition_types::LINUX_FS,
|
||||
0,
|
||||
None,
|
||||
)?;
|
||||
gpt.add_partition(
|
||||
"root",
|
||||
15 * 1024 * 1024 * 1024,
|
||||
match crate::ARCH {
|
||||
"x86_64" => gpt::partition_types::LINUX_ROOT_X64,
|
||||
"aarch64" => gpt::partition_types::LINUX_ROOT_ARM_64,
|
||||
_ => gpt::partition_types::LINUX_FS,
|
||||
},
|
||||
0,
|
||||
None,
|
||||
)?;
|
||||
|
||||
if overwrite {
|
||||
gpt.add_partition(
|
||||
"data",
|
||||
gpt.find_free_sectors()
|
||||
.iter()
|
||||
.map(|(_, size)| *size * u64::from(*gpt.logical_block_size()))
|
||||
.max()
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("No free space left on device"),
|
||||
crate::ErrorKind::BlockDevice,
|
||||
)
|
||||
})?,
|
||||
gpt::partition_types::LINUX_LVM,
|
||||
0,
|
||||
None,
|
||||
)?;
|
||||
} else if let Some(guid_part) = guid_part {
|
||||
let mut parts = gpt.partitions().clone();
|
||||
parts.insert(
|
||||
gpt.find_next_partition_id().ok_or_else(|| {
|
||||
Error::new(eyre!("Partition table is full"), ErrorKind::DiskManagement)
|
||||
})?,
|
||||
guid_part,
|
||||
);
|
||||
gpt.update_partitions(parts)?;
|
||||
let mut device = Box::new(
|
||||
std::fs::File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&disk_path)?,
|
||||
);
|
||||
|
||||
let mbr = gpt::mbr::ProtectiveMBR::with_lb_size(
|
||||
u32::try_from((capacity / 512) - 1).unwrap_or(0xFF_FF_FF_FF),
|
||||
);
|
||||
mbr.overwrite_lba0(&mut device)?;
|
||||
let mut gpt = GptConfig::new()
|
||||
.writable(true)
|
||||
.logical_block_size(LogicalBlockSize::Lb512)
|
||||
.create_from_device(device, None)?;
|
||||
|
||||
gpt.update_partitions(Default::default())?;
|
||||
|
||||
let efi = if use_efi {
|
||||
gpt.add_partition("efi", 100 * 1024 * 1024, gpt::partition_types::EFI, 0, None)?;
|
||||
true
|
||||
} else {
|
||||
gpt.add_partition(
|
||||
"bios-grub",
|
||||
8 * 1024 * 1024,
|
||||
gpt::partition_types::BIOS,
|
||||
0,
|
||||
None,
|
||||
)?;
|
||||
false
|
||||
};
|
||||
gpt.add_partition(
|
||||
"boot",
|
||||
2 * 1024 * 1024 * 1024,
|
||||
gpt::partition_types::LINUX_FS,
|
||||
0,
|
||||
None,
|
||||
)?;
|
||||
gpt.add_partition(
|
||||
"root",
|
||||
14 * 1024 * 1024 * 1024,
|
||||
match crate::ARCH {
|
||||
"x86_64" => gpt::partition_types::LINUX_ROOT_X64,
|
||||
"aarch64" => gpt::partition_types::LINUX_ROOT_ARM_64,
|
||||
_ => gpt::partition_types::LINUX_FS,
|
||||
},
|
||||
0,
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Check if protected partition would be overwritten by OS partitions
|
||||
if let Some((first_lba, _, ref path)) = protected_partition_info {
|
||||
// Get the actual end sector of the last OS partition (root = partition 3)
|
||||
let os_partitions_end_sector =
|
||||
gpt.partitions().get(&3).map(|p| p.last_lba).unwrap_or(0);
|
||||
if first_lba <= os_partitions_end_sector {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
concat!(
|
||||
"Protected partition {} starts at sector {}",
|
||||
" which would be overwritten by OS partitions ending at sector {}"
|
||||
),
|
||||
path.display(),
|
||||
first_lba,
|
||||
os_partitions_end_sector
|
||||
),
|
||||
crate::ErrorKind::DiskManagement,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
gpt.write()?;
|
||||
let data_part = if let Some((first_lba, last_lba, path)) = protected_partition_info {
|
||||
// Re-create the data partition entry at the same location
|
||||
let length_lba = last_lba - first_lba + 1;
|
||||
let next_id = gpt.partitions().keys().max().map(|k| k + 1).unwrap_or(1);
|
||||
gpt.add_partition_at(
|
||||
"data",
|
||||
next_id,
|
||||
first_lba,
|
||||
length_lba,
|
||||
gpt::partition_types::LINUX_LVM,
|
||||
0,
|
||||
)?;
|
||||
Some(path)
|
||||
} else {
|
||||
gpt.add_partition(
|
||||
"data",
|
||||
gpt.find_free_sectors()
|
||||
.iter()
|
||||
.map(|(_, size)| *size * u64::from(*gpt.logical_block_size()))
|
||||
.max()
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("No free space left on device"),
|
||||
crate::ErrorKind::BlockDevice,
|
||||
)
|
||||
})?,
|
||||
gpt::partition_types::LINUX_LVM,
|
||||
0,
|
||||
None,
|
||||
)?;
|
||||
gpt.partitions()
|
||||
.last_key_value()
|
||||
.map(|(num, _)| partition_for(&disk_path, *num))
|
||||
};
|
||||
|
||||
Ok(efi)
|
||||
})
|
||||
gpt.write()?;
|
||||
|
||||
Ok::<_, Error>((efi, data_part))
|
||||
})
|
||||
.await
|
||||
.unwrap()?;
|
||||
|
||||
// Re-read partition table and wait for udev to create device nodes
|
||||
Command::new("vgchange")
|
||||
.arg("-an")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await
|
||||
.unwrap()?
|
||||
};
|
||||
.ok();
|
||||
Command::new("dmsetup")
|
||||
.arg("remove_all")
|
||||
.arg("--force")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await
|
||||
.ok();
|
||||
Command::new("blockdev")
|
||||
.arg("--rereadpt")
|
||||
.arg(&disk_path)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
Command::new("udevadm")
|
||||
.arg("settle")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
|
||||
Ok(OsPartitionInfo {
|
||||
efi: efi.then(|| partition_for(&disk.logicalname, 1)),
|
||||
bios: (!efi).then(|| partition_for(&disk.logicalname, 1)),
|
||||
boot: partition_for(&disk.logicalname, 2),
|
||||
root: partition_for(&disk.logicalname, 3),
|
||||
efi: efi.then(|| partition_for(&disk_path, 1)),
|
||||
bios: (!efi).then(|| partition_for(&disk_path, 1)),
|
||||
boot: partition_for(&disk_path, 2),
|
||||
root: partition_for(&disk_path, 3),
|
||||
data: data_part,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,88 +1,173 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use mbrman::{CHS, MBR, MBRPartitionEntry};
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::Error;
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::disk::util::DiskInfo;
|
||||
use crate::os_install::partition_for;
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
|
||||
pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result<OsPartitionInfo, Error> {
|
||||
{
|
||||
let sectors = (disk.capacity / 512) as u32;
|
||||
let disk = disk.clone();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut file = std::fs::File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&disk.logicalname)?;
|
||||
let (mut mbr, guid_part) = if overwrite {
|
||||
(MBR::new_from(&mut file, 512, rand::random())?, None)
|
||||
} else {
|
||||
let mut mbr = MBR::read_from(&mut file, 512)?;
|
||||
let mut guid_part = None;
|
||||
for (idx, part_info) in disk
|
||||
.partitions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, x)| (idx + 1, x))
|
||||
{
|
||||
if let Some(entry) = mbr.get_mut(idx) {
|
||||
if part_info.guid.is_some() {
|
||||
if entry.starting_lba < 33556480 {
|
||||
return Err(Error::new(
|
||||
eyre!("Not enough space before embassy data"),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
guid_part = Some(std::mem::replace(entry, MBRPartitionEntry::empty()));
|
||||
pub async fn partition(
|
||||
disk_path: &Path,
|
||||
capacity: u64,
|
||||
protect: Option<&Path>,
|
||||
) -> Result<OsPartitionInfo, Error> {
|
||||
// Guard: cannot protect the whole disk
|
||||
if let Some(p) = protect {
|
||||
if p == disk_path {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Cannot protect the entire disk {}; must specify a partition",
|
||||
disk_path.display()
|
||||
),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let disk_path = disk_path.to_owned();
|
||||
let disk_path_clone = disk_path.clone();
|
||||
let protect = protect.map(|p| p.to_owned());
|
||||
let sectors = (capacity / 512) as u32;
|
||||
let data_part = tokio::task::spawn_blocking(move || {
|
||||
let disk_path = disk_path_clone;
|
||||
|
||||
// If protecting a partition, read its location from the existing MBR
|
||||
let protected_partition_info: Option<(u32, u32, PathBuf)> =
|
||||
if let Some(ref protect_path) = protect {
|
||||
let mut file = std::fs::File::options().read(true).open(&disk_path)?;
|
||||
let existing_mbr = MBR::read_from(&mut file, 512)?;
|
||||
// Find the partition matching the protected path (check partitions 1-4)
|
||||
let info = (1..=4u32)
|
||||
.find(|&idx| partition_for(&disk_path, idx) == *protect_path)
|
||||
.and_then(|idx| {
|
||||
let entry = &existing_mbr[idx as usize];
|
||||
if entry.sectors > 0 {
|
||||
Some((entry.starting_lba, entry.sectors, protect_path.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
*entry = MBRPartitionEntry::empty();
|
||||
}
|
||||
});
|
||||
if info.is_none() {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Protected partition {} not found in MBR on {}",
|
||||
protect_path.display(),
|
||||
disk_path.display()
|
||||
),
|
||||
crate::ErrorKind::NotFound,
|
||||
));
|
||||
}
|
||||
(mbr, guid_part)
|
||||
info
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
mbr[1] = MBRPartitionEntry {
|
||||
boot: 0x80,
|
||||
first_chs: CHS::empty(),
|
||||
sys: 0x0b,
|
||||
last_chs: CHS::empty(),
|
||||
starting_lba: 2048,
|
||||
sectors: 2099200 - 2048,
|
||||
};
|
||||
mbr[2] = MBRPartitionEntry {
|
||||
// MBR partition layout:
|
||||
// Partition 1 (boot): starts at 2048, ends at 4196352 (sectors: 4194304 = 2GB)
|
||||
// Partition 2 (root): starts at 4196352, ends at 33556480 (sectors: 29360128 = 14GB)
|
||||
// OS partitions end at sector 33556480
|
||||
let os_partitions_end_sector: u32 = 33556480;
|
||||
|
||||
// Check if protected partition would be overwritten
|
||||
if let Some((starting_lba, _, ref path)) = protected_partition_info {
|
||||
if starting_lba < os_partitions_end_sector {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
concat!(
|
||||
"Protected partition {} starts at sector {}",
|
||||
" which would be overwritten by OS partitions ending at sector {}"
|
||||
),
|
||||
path.display(),
|
||||
starting_lba,
|
||||
os_partitions_end_sector
|
||||
),
|
||||
crate::ErrorKind::DiskManagement,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut file = std::fs::File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&disk_path)?;
|
||||
let mut mbr = MBR::new_from(&mut file, 512, rand::random())?;
|
||||
|
||||
mbr[1] = MBRPartitionEntry {
|
||||
boot: 0x80,
|
||||
first_chs: CHS::empty(),
|
||||
sys: 0x0b,
|
||||
last_chs: CHS::empty(),
|
||||
starting_lba: 2048,
|
||||
sectors: 4196352 - 2048,
|
||||
};
|
||||
mbr[2] = MBRPartitionEntry {
|
||||
boot: 0,
|
||||
first_chs: CHS::empty(),
|
||||
sys: 0x83,
|
||||
last_chs: CHS::empty(),
|
||||
starting_lba: 4196352,
|
||||
sectors: 33556480 - 4196352,
|
||||
};
|
||||
|
||||
let data_part = if let Some((starting_lba, part_sectors, path)) = protected_partition_info {
|
||||
// Re-create the data partition entry at the same location
|
||||
mbr[3] = MBRPartitionEntry {
|
||||
boot: 0,
|
||||
first_chs: CHS::empty(),
|
||||
sys: 0x83,
|
||||
sys: 0x8e,
|
||||
last_chs: CHS::empty(),
|
||||
starting_lba: 2099200,
|
||||
sectors: 33556480 - 2099200,
|
||||
starting_lba,
|
||||
sectors: part_sectors,
|
||||
};
|
||||
Some(path)
|
||||
} else {
|
||||
mbr[3] = MBRPartitionEntry {
|
||||
boot: 0,
|
||||
first_chs: CHS::empty(),
|
||||
sys: 0x8e,
|
||||
last_chs: CHS::empty(),
|
||||
starting_lba: 33556480,
|
||||
sectors: sectors - 33556480,
|
||||
};
|
||||
Some(partition_for(&disk_path, 3))
|
||||
};
|
||||
mbr.write_into(&mut file)?;
|
||||
|
||||
if overwrite {
|
||||
mbr[3] = MBRPartitionEntry {
|
||||
boot: 0,
|
||||
first_chs: CHS::empty(),
|
||||
sys: 0x8e,
|
||||
last_chs: CHS::empty(),
|
||||
starting_lba: 33556480,
|
||||
sectors: sectors - 33556480,
|
||||
}
|
||||
} else if let Some(guid_part) = guid_part {
|
||||
mbr[3] = guid_part;
|
||||
}
|
||||
mbr.write_into(&mut file)?;
|
||||
Ok::<_, Error>(data_part)
|
||||
})
|
||||
.await
|
||||
.unwrap()?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
// Re-read partition table and wait for udev to create device nodes
|
||||
Command::new("vgchange")
|
||||
.arg("-an")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await
|
||||
.unwrap()?;
|
||||
}
|
||||
.ok();
|
||||
Command::new("dmsetup")
|
||||
.arg("remove_all")
|
||||
.arg("--force")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await
|
||||
.ok();
|
||||
Command::new("blockdev")
|
||||
.arg("--rereadpt")
|
||||
.arg(&disk_path)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
Command::new("udevadm")
|
||||
.arg("settle")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
|
||||
Ok(OsPartitionInfo {
|
||||
efi: None,
|
||||
bios: None,
|
||||
boot: partition_for(&disk.logicalname, 1),
|
||||
root: partition_for(&disk.logicalname, 2),
|
||||
boot: partition_for(&disk_path, 1),
|
||||
root: partition_for(&disk_path, 2),
|
||||
data: data_part,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::process::Command;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::Error;
|
||||
use crate::context::config::ServerConfig;
|
||||
use crate::context::{CliContext, InstallContext};
|
||||
use crate::context::{CliContext, SetupContext};
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::disk::mount::filesystem::bind::Bind;
|
||||
use crate::disk::mount::filesystem::block_dev::BlockDev;
|
||||
@@ -16,81 +16,31 @@ use crate::disk::mount::filesystem::efivarfs::EfiVarFs;
|
||||
use crate::disk::mount::filesystem::overlayfs::OverlayFs;
|
||||
use crate::disk::mount::filesystem::{MountType, ReadWrite};
|
||||
use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard};
|
||||
use crate::disk::util::{DiskInfo, PartitionTable};
|
||||
use crate::net::utils::find_eth_iface;
|
||||
use crate::disk::util::PartitionTable;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||
use crate::setup::SetupInfo;
|
||||
use crate::util::Invoke;
|
||||
use crate::util::io::{TmpDir, delete_file, open_file};
|
||||
use crate::util::io::{TmpDir, delete_file, open_file, write_file_atomic};
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::{ARCH, Error};
|
||||
|
||||
mod gpt;
|
||||
mod mbr;
|
||||
|
||||
pub fn install<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand("disk", disk::<C>().with_about("Command to list disk info"))
|
||||
.subcommand(
|
||||
"execute",
|
||||
from_fn_async(execute::<InstallContext>)
|
||||
.no_display()
|
||||
.with_about("Install StartOS over existing version")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"reboot",
|
||||
from_fn_async(reboot)
|
||||
.no_display()
|
||||
.with_about("Restart the server")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
/// Probe a squashfs image to determine its target architecture
|
||||
async fn probe_squashfs_arch(squashfs_path: &Path) -> Result<InternedString, Error> {
|
||||
let output = String::from_utf8(
|
||||
Command::new("unsquashfs")
|
||||
.arg("-cat")
|
||||
.arg(squashfs_path)
|
||||
.arg("usr/lib/startos/PLATFORM.txt")
|
||||
.invoke(ErrorKind::ParseSysInfo)
|
||||
.await?,
|
||||
)?;
|
||||
Ok(crate::platform_to_arch(&output.trim()).into())
|
||||
}
|
||||
|
||||
pub fn disk<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new().subcommand(
|
||||
"list",
|
||||
from_fn_async(list)
|
||||
.no_display()
|
||||
.with_about("List disk info")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn list(_: InstallContext) -> Result<Vec<DiskInfo>, Error> {
|
||||
let skip = match async {
|
||||
Ok::<_, Error>(
|
||||
Path::new(
|
||||
&String::from_utf8(
|
||||
Command::new("grub-probe-default")
|
||||
.arg("-t")
|
||||
.arg("disk")
|
||||
.arg("/run/live/medium")
|
||||
.invoke(crate::ErrorKind::Grub)
|
||||
.await?,
|
||||
)?
|
||||
.trim(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
}
|
||||
.await
|
||||
{
|
||||
Ok(a) => Some(a),
|
||||
Err(e) => {
|
||||
tracing::error!("Could not determine live usb device: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
Ok(crate::disk::util::list(&Default::default())
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|i| Some(&*i.logicalname) != skip.as_deref())
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn partition_for(disk: impl AsRef<Path>, idx: usize) -> PathBuf {
|
||||
pub fn partition_for(disk: impl AsRef<Path>, idx: u32) -> PathBuf {
|
||||
let disk_path = disk.as_ref();
|
||||
let (root, leaf) = if let (Some(root), Some(leaf)) = (
|
||||
disk_path.parent(),
|
||||
@@ -107,49 +57,90 @@ pub fn partition_for(disk: impl AsRef<Path>, idx: usize) -> PathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
async fn partition(disk: &mut DiskInfo, overwrite: bool) -> Result<OsPartitionInfo, Error> {
|
||||
let partition_type = match (overwrite, disk.partition_table) {
|
||||
async fn partition(
|
||||
disk_path: &Path,
|
||||
capacity: u64,
|
||||
partition_table: Option<PartitionTable>,
|
||||
protect: Option<&Path>,
|
||||
use_efi: bool,
|
||||
) -> Result<OsPartitionInfo, Error> {
|
||||
let partition_type = match (protect.is_none(), partition_table) {
|
||||
(true, _) | (_, None) => PartitionTable::Gpt,
|
||||
(_, Some(t)) => t,
|
||||
};
|
||||
disk.partition_table = Some(partition_type);
|
||||
match partition_type {
|
||||
PartitionTable::Gpt => gpt::partition(disk, overwrite).await,
|
||||
PartitionTable::Mbr => mbr::partition(disk, overwrite).await,
|
||||
PartitionTable::Gpt => gpt::partition(disk_path, capacity, protect, use_efi).await,
|
||||
PartitionTable::Mbr => mbr::partition(disk_path, capacity, protect).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_block_device_size(path: impl AsRef<Path>) -> Result<u64, Error> {
|
||||
let path = path.as_ref();
|
||||
let device_name = path.file_name().and_then(|s| s.to_str()).ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Invalid block device path: {}", path.display()),
|
||||
ErrorKind::BlockDevice,
|
||||
)
|
||||
})?;
|
||||
let size_path = Path::new("/sys/block").join(device_name).join("size");
|
||||
let sectors: u64 = tokio::fs::read_to_string(&size_path)
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
ErrorKind::BlockDevice,
|
||||
format!("reading {}", size_path.display()),
|
||||
)
|
||||
})?
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(|e| {
|
||||
Error::new(
|
||||
eyre!("Failed to parse block device size: {}", e),
|
||||
ErrorKind::BlockDevice,
|
||||
)
|
||||
})?;
|
||||
Ok(sectors * 512)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ExecuteParams {
|
||||
logicalname: PathBuf,
|
||||
#[arg(short = 'o')]
|
||||
overwrite: bool,
|
||||
pub struct InstallOsParams {
|
||||
#[arg(help = "help.arg.os-drive-path")]
|
||||
os_drive: PathBuf,
|
||||
#[command(flatten)]
|
||||
data_drive: Option<DataDrive>,
|
||||
}
|
||||
|
||||
pub async fn execute<C: Context>(
|
||||
_: C,
|
||||
ExecuteParams {
|
||||
logicalname,
|
||||
mut overwrite,
|
||||
}: ExecuteParams,
|
||||
) -> Result<(), Error> {
|
||||
let mut disk = crate::disk::util::list(&Default::default())
|
||||
.await?
|
||||
.into_iter()
|
||||
.find(|d| &d.logicalname == &logicalname)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Unknown disk {}", logicalname.display()),
|
||||
crate::ErrorKind::DiskManagement,
|
||||
)
|
||||
})?;
|
||||
let eth_iface = find_eth_iface().await?;
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
struct DataDrive {
|
||||
#[arg(long = "data-drive", help = "help.arg.data-drive-path")]
|
||||
logicalname: PathBuf,
|
||||
#[arg(long, help = "help.arg.wipe-drive")]
|
||||
wipe: bool,
|
||||
}
|
||||
|
||||
overwrite |= disk.guid.is_none() && disk.partitions.iter().all(|p| p.guid.is_none());
|
||||
pub struct InstallOsResult {
|
||||
pub part_info: OsPartitionInfo,
|
||||
pub rootfs: TmpMountGuard,
|
||||
}
|
||||
|
||||
let part_info = partition(&mut disk, overwrite).await?;
|
||||
pub async fn install_os_to(
|
||||
squashfs_path: impl AsRef<Path>,
|
||||
disk_path: impl AsRef<Path>,
|
||||
capacity: u64,
|
||||
partition_table: Option<PartitionTable>,
|
||||
protect: Option<impl AsRef<Path>>,
|
||||
arch: &str,
|
||||
use_efi: bool,
|
||||
) -> Result<InstallOsResult, Error> {
|
||||
let squashfs_path = squashfs_path.as_ref();
|
||||
let disk_path = disk_path.as_ref();
|
||||
let protect = protect.as_ref().map(|p| p.as_ref());
|
||||
|
||||
let part_info = partition(disk_path, capacity, partition_table, protect, use_efi).await?;
|
||||
|
||||
if let Some(efi) = &part_info.efi {
|
||||
Command::new("mkfs.vfat")
|
||||
@@ -173,7 +164,7 @@ pub async fn execute<C: Context>(
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
|
||||
if !overwrite {
|
||||
if protect.is_some() {
|
||||
if let Ok(guard) =
|
||||
TmpMountGuard::mount(&BlockDev::new(part_info.root.clone()), MountType::ReadWrite).await
|
||||
{
|
||||
@@ -234,13 +225,13 @@ pub async fn execute<C: Context>(
|
||||
tokio::fs::create_dir_all(&images_path).await?;
|
||||
let image_path = images_path
|
||||
.join(hex::encode(
|
||||
&MultiCursorFile::from(open_file("/run/live/medium/live/filesystem.squashfs").await?)
|
||||
&MultiCursorFile::from(open_file(squashfs_path).await?)
|
||||
.blake3_mmap()
|
||||
.await?
|
||||
.as_bytes()[..16],
|
||||
))
|
||||
.with_extension("rootfs");
|
||||
tokio::fs::copy("/run/live/medium/live/filesystem.squashfs", &image_path).await?;
|
||||
tokio::fs::copy(squashfs_path, &image_path).await?;
|
||||
// TODO: check hash of fs
|
||||
let unsquash_target = TmpDir::new().await?;
|
||||
let bootfs = MountGuard::mount(
|
||||
@@ -254,7 +245,7 @@ pub async fn execute<C: Context>(
|
||||
.arg("-f")
|
||||
.arg("-d")
|
||||
.arg(&*unsquash_target)
|
||||
.arg("/run/live/medium/live/filesystem.squashfs")
|
||||
.arg(squashfs_path)
|
||||
.arg("boot")
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
@@ -271,7 +262,6 @@ pub async fn execute<C: Context>(
|
||||
rootfs.path().join("config/config.yaml"),
|
||||
IoFormat::Yaml.to_vec(&ServerConfig {
|
||||
os_partitions: Some(part_info.clone()),
|
||||
ethernet_interface: Some(eth_iface),
|
||||
..Default::default()
|
||||
})?,
|
||||
)
|
||||
@@ -357,13 +347,13 @@ pub async fn execute<C: Context>(
|
||||
|
||||
let mut install = Command::new("chroot");
|
||||
install.arg(overlay.path()).arg("grub-install");
|
||||
if tokio::fs::metadata("/sys/firmware/efi").await.is_err() {
|
||||
match ARCH {
|
||||
if !use_efi {
|
||||
match arch {
|
||||
"x86_64" => install.arg("--target=i386-pc"),
|
||||
_ => &mut install,
|
||||
};
|
||||
} else {
|
||||
match ARCH {
|
||||
match arch {
|
||||
"x86_64" => install.arg("--target=x86_64-efi"),
|
||||
"aarch64" => install.arg("--target=arm64-efi"),
|
||||
"riscv64" => install.arg("--target=riscv64-efi"),
|
||||
@@ -371,7 +361,7 @@ pub async fn execute<C: Context>(
|
||||
};
|
||||
}
|
||||
install
|
||||
.arg(&disk.logicalname)
|
||||
.arg(disk_path)
|
||||
.invoke(crate::ErrorKind::Grub)
|
||||
.await?;
|
||||
|
||||
@@ -396,15 +386,158 @@ pub async fn execute<C: Context>(
|
||||
tokio::fs::remove_dir_all(&work).await?;
|
||||
lower.unmount().await?;
|
||||
|
||||
Ok(InstallOsResult { part_info, rootfs })
|
||||
}
|
||||
|
||||
pub async fn install_os(
|
||||
ctx: SetupContext,
|
||||
InstallOsParams {
|
||||
os_drive,
|
||||
data_drive,
|
||||
}: InstallOsParams,
|
||||
) -> Result<SetupInfo, Error> {
|
||||
let mut disks = crate::disk::util::list(&Default::default()).await?;
|
||||
let disk = disks
|
||||
.iter_mut()
|
||||
.find(|d| &d.logicalname == &os_drive)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Unknown disk {}", os_drive.display()),
|
||||
crate::ErrorKind::DiskManagement,
|
||||
)
|
||||
})?;
|
||||
|
||||
let protect: Option<PathBuf> = data_drive.as_ref().and_then(|dd| {
|
||||
if dd.wipe {
|
||||
return None;
|
||||
}
|
||||
if disk.guid.as_ref().map_or(false, |g| {
|
||||
g.starts_with("EMBASSY_") || g.starts_with("STARTOS_")
|
||||
}) && disk.logicalname == dd.logicalname
|
||||
{
|
||||
return Some(disk.logicalname.clone());
|
||||
}
|
||||
disk.partitions
|
||||
.iter()
|
||||
.find(|p| {
|
||||
p.guid.as_ref().map_or(false, |g| {
|
||||
g.starts_with("EMBASSY_") || g.starts_with("STARTOS_")
|
||||
})
|
||||
})
|
||||
.map(|p| p.logicalname.clone())
|
||||
});
|
||||
|
||||
let use_efi = tokio::fs::metadata("/sys/firmware/efi").await.is_ok();
|
||||
let InstallOsResult { part_info, rootfs } = install_os_to(
|
||||
"/run/live/medium/live/filesystem.squashfs",
|
||||
&disk.logicalname,
|
||||
disk.capacity,
|
||||
disk.partition_table,
|
||||
protect.as_ref(),
|
||||
crate::ARCH,
|
||||
use_efi,
|
||||
)
|
||||
.await?;
|
||||
|
||||
ctx.config
|
||||
.mutate(|c| c.os_partitions = Some(part_info.clone()));
|
||||
|
||||
let mut setup_info = SetupInfo::default();
|
||||
|
||||
if let Some(data_drive) = data_drive {
|
||||
let mut logicalname = &*data_drive.logicalname;
|
||||
if logicalname == &os_drive {
|
||||
logicalname = part_info.data.as_deref().ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("not enough room on OS drive for data"),
|
||||
ErrorKind::InvalidRequest,
|
||||
)
|
||||
})?;
|
||||
}
|
||||
if let Some(guid) = (!data_drive.wipe)
|
||||
.then(|| disks.iter())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.find_map(|d| {
|
||||
d.guid
|
||||
.as_ref()
|
||||
.filter(|_| &d.logicalname == logicalname)
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
d.partitions.iter().find_map(|p| {
|
||||
p.guid
|
||||
.as_ref()
|
||||
.filter(|_| &p.logicalname == logicalname)
|
||||
.cloned()
|
||||
})
|
||||
})
|
||||
})
|
||||
{
|
||||
setup_info.guid = Some(guid);
|
||||
setup_info.attach = true;
|
||||
} else {
|
||||
let guid = crate::setup::setup_data_drive(&ctx, logicalname).await?;
|
||||
setup_info.guid = Some(guid);
|
||||
}
|
||||
}
|
||||
|
||||
let config = MountGuard::mount(
|
||||
&Bind::new(rootfs.path().join("config")),
|
||||
"/media/startos/config",
|
||||
ReadWrite,
|
||||
)
|
||||
.await?;
|
||||
|
||||
write_file_atomic(
|
||||
"/media/startos/config/setup.json",
|
||||
IoFormat::JsonPretty.to_vec(&setup_info)?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
ctx.install_rootfs.replace(Some((rootfs, config)));
|
||||
|
||||
Ok(setup_info)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct CliInstallOsParams {
|
||||
#[arg(help = "help.arg.squashfs-image-path")]
|
||||
squashfs: PathBuf,
|
||||
#[arg(help = "help.arg.target-disk")]
|
||||
disk: PathBuf,
|
||||
#[arg(long, help = "help.arg.use-efi-boot")]
|
||||
efi: Option<bool>,
|
||||
}
|
||||
|
||||
pub async fn cli_install_os(
|
||||
_ctx: CliContext,
|
||||
CliInstallOsParams {
|
||||
squashfs,
|
||||
disk,
|
||||
efi,
|
||||
}: CliInstallOsParams,
|
||||
) -> Result<OsPartitionInfo, Error> {
|
||||
let capacity = get_block_device_size(&disk).await?;
|
||||
let partition_table = crate::disk::util::get_partition_table(&disk).await?;
|
||||
|
||||
let arch = probe_squashfs_arch(&squashfs).await?;
|
||||
|
||||
let use_efi = efi.unwrap_or_else(|| !matches!(partition_table, Some(PartitionTable::Mbr)));
|
||||
|
||||
let InstallOsResult { part_info, rootfs } = install_os_to(
|
||||
&squashfs,
|
||||
&disk,
|
||||
capacity,
|
||||
partition_table,
|
||||
None::<&str>,
|
||||
&*arch,
|
||||
use_efi,
|
||||
)
|
||||
.await?;
|
||||
|
||||
rootfs.unmount().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reboot(ctx: InstallContext) -> Result<(), Error> {
|
||||
Command::new("sync")
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
ctx.shutdown.send(()).unwrap();
|
||||
Ok(())
|
||||
Ok(part_info)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub use color_eyre::eyre::eyre;
|
||||
pub use imbl_value::InternedString;
|
||||
pub use lazy_format::lazy_format;
|
||||
pub use rust_i18n::t;
|
||||
pub use tracing::instrument;
|
||||
|
||||
pub use crate::db::prelude::*;
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn admin_api<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"signer",
|
||||
signers_api::<C>().with_about("Commands to add or list signers"),
|
||||
signers_api::<C>().with_about("about.commands-add-list-signers"),
|
||||
)
|
||||
.subcommand(
|
||||
"add",
|
||||
@@ -33,14 +33,14 @@ pub fn admin_api<C: Context>() -> ParentHandler<C> {
|
||||
"add",
|
||||
from_fn_async(cli_add_admin)
|
||||
.no_display()
|
||||
.with_about("Add admin signer"),
|
||||
.with_about("about.add-admin-signer"),
|
||||
)
|
||||
.subcommand(
|
||||
"remove",
|
||||
from_fn_async(remove_admin)
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Remove an admin signer")
|
||||
.with_about("about.remove-admin-signer")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -49,7 +49,7 @@ pub fn admin_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|handle, result| display_signers(handle.params, result))
|
||||
.with_about("List admin signers")
|
||||
.with_about("about.list-admin-signers")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -62,7 +62,7 @@ fn signers_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|handle, result| display_signers(handle.params, result))
|
||||
.with_about("List signers")
|
||||
.with_about("about.list-signers")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -73,13 +73,14 @@ fn signers_api<C: Context>() -> ParentHandler<C> {
|
||||
)
|
||||
.subcommand(
|
||||
"add",
|
||||
from_fn_async(cli_add_signer).with_about("Add signer"),
|
||||
from_fn_async(cli_add_signer).with_about("about.add-signer"),
|
||||
)
|
||||
.subcommand(
|
||||
"edit",
|
||||
from_fn_async(edit_signer)
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("about.edit-signer")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -93,7 +94,7 @@ impl Model<BTreeMap<Guid, SignerInfo>> {
|
||||
.next()
|
||||
.transpose()?
|
||||
.map(|(a, _)| a)
|
||||
.ok_or_else(|| Error::new(eyre!("unknown signer"), ErrorKind::Authorization))
|
||||
.ok_or_else(|| Error::new(eyre!("{}", t!("registry.admin.unknown-signer")), ErrorKind::Authorization))
|
||||
}
|
||||
|
||||
pub fn get_signer_info(&self, key: &AnyVerifyingKey) -> Result<(Guid, SignerInfo), Error> {
|
||||
@@ -103,7 +104,7 @@ impl Model<BTreeMap<Guid, SignerInfo>> {
|
||||
.filter_ok(|(_, s)| s.keys.contains(key))
|
||||
.next()
|
||||
.transpose()?
|
||||
.ok_or_else(|| Error::new(eyre!("unknown signer"), ErrorKind::Authorization))
|
||||
.ok_or_else(|| Error::new(eyre!("{}", t!("registry.admin.unknown-signer")), ErrorKind::Authorization))
|
||||
}
|
||||
|
||||
pub fn add_signer(&mut self, signer: &SignerInfo) -> Result<Guid, Error> {
|
||||
@@ -117,9 +118,8 @@ impl Model<BTreeMap<Guid, SignerInfo>> {
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"A signer {} ({}) already exists with a matching key",
|
||||
guid,
|
||||
s.name
|
||||
"{}",
|
||||
t!("registry.admin.signer-already-exists", guid = guid, name = s.name)
|
||||
),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
@@ -206,16 +206,17 @@ pub async fn add_signer(ctx: RegistryContext, signer: SignerInfo) -> Result<Guid
|
||||
#[command(rename_all = "kebab-case")]
|
||||
#[ts(export)]
|
||||
pub struct EditSignerParams {
|
||||
#[arg(help = "help.arg.signer-id")]
|
||||
pub id: Guid,
|
||||
#[arg(short = 'n', long)]
|
||||
#[arg(short = 'n', long, help = "help.arg.set-signer-name")]
|
||||
pub set_name: Option<String>,
|
||||
#[arg(short = 'c', long)]
|
||||
#[arg(short = 'c', long, help = "help.arg.add-signer-contact")]
|
||||
pub add_contact: Vec<ContactInfo>,
|
||||
#[arg(short = 'k', long)]
|
||||
#[arg(short = 'k', long, help = "help.arg.add-signer-key")]
|
||||
pub add_key: Vec<AnyVerifyingKey>,
|
||||
#[arg(short = 'C', long)]
|
||||
#[arg(short = 'C', long, help = "help.arg.remove-signer-contact")]
|
||||
pub remove_contact: Vec<ContactInfo>,
|
||||
#[arg(short = 'K', long)]
|
||||
#[arg(short = 'K', long, help = "help.arg.remove-signer-key")]
|
||||
pub remove_key: Vec<AnyVerifyingKey>,
|
||||
}
|
||||
|
||||
@@ -264,12 +265,13 @@ pub async fn edit_signer(
|
||||
#[command(rename_all = "kebab-case")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliAddSignerParams {
|
||||
#[arg(long = "name", short = 'n')]
|
||||
#[arg(long = "name", short = 'n', help = "help.arg.signer-name")]
|
||||
pub name: String,
|
||||
#[arg(long = "contact", short = 'c')]
|
||||
#[arg(long = "contact", short = 'c', help = "help.arg.signer-contact")]
|
||||
pub contact: Vec<ContactInfo>,
|
||||
#[arg(long = "key")]
|
||||
#[arg(long = "key", help = "help.arg.signer-key")]
|
||||
pub keys: Vec<AnyVerifyingKey>,
|
||||
#[arg(help = "help.arg.database-path")]
|
||||
pub database: Option<PathBuf>,
|
||||
}
|
||||
|
||||
@@ -339,6 +341,7 @@ pub async fn add_admin(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct RemoveAdminParams {
|
||||
#[arg(help = "help.arg.signer-id")]
|
||||
pub signer: Guid,
|
||||
}
|
||||
|
||||
@@ -360,7 +363,9 @@ pub async fn remove_admin(
|
||||
#[command(rename_all = "kebab-case")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliAddAdminParams {
|
||||
#[arg(help = "help.arg.signer-id")]
|
||||
pub signer: Guid,
|
||||
#[arg(help = "help.arg.database-path")]
|
||||
pub database: Option<PathBuf>,
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ impl<Commitment> RegistryAsset<Commitment> {
|
||||
}
|
||||
}
|
||||
Err(Error::new(
|
||||
eyre!("Failed to load any http url"),
|
||||
eyre!("{}", t!("registry.asset.failed-to-load-http-url")),
|
||||
ErrorKind::Network,
|
||||
))
|
||||
}
|
||||
@@ -64,7 +64,7 @@ impl<Commitment> RegistryAsset<Commitment> {
|
||||
}
|
||||
}
|
||||
Err(Error::new(
|
||||
eyre!("Failed to load any http url"),
|
||||
eyre!("{}", t!("registry.asset.failed-to-load-http-url")),
|
||||
ErrorKind::Network,
|
||||
))
|
||||
}
|
||||
@@ -80,7 +80,7 @@ impl<Commitment> RegistryAsset<Commitment> {
|
||||
}
|
||||
}
|
||||
Err(Error::new(
|
||||
eyre!("Failed to load any http url"),
|
||||
eyre!("{}", t!("registry.asset.failed-to-load-http-url")),
|
||||
ErrorKind::Network,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -42,17 +42,17 @@ const DEFAULT_REGISTRY_LISTEN: SocketAddr =
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct RegistryConfig {
|
||||
#[arg(short = 'c', long = "config")]
|
||||
#[arg(short = 'c', long = "config", help = "help.arg.config-file-path")]
|
||||
pub config: Option<PathBuf>,
|
||||
#[arg(short = 'l', long = "listen")]
|
||||
#[arg(short = 'l', long = "listen", help = "help.arg.registry-listen-address")]
|
||||
pub registry_listen: Option<SocketAddr>,
|
||||
#[arg(short = 'H', long = "hostname")]
|
||||
#[arg(short = 'H', long = "hostname", help = "help.arg.registry-hostname")]
|
||||
pub registry_hostname: Vec<InternedString>,
|
||||
#[arg(short = 'p', long = "tor-proxy")]
|
||||
#[arg(short = 'p', long = "tor-proxy", help = "help.arg.tor-proxy-url")]
|
||||
pub tor_proxy: Option<Url>,
|
||||
#[arg(short = 'd', long = "datadir")]
|
||||
#[arg(short = 'd', long = "datadir", help = "help.arg.data-directory")]
|
||||
pub datadir: Option<PathBuf>,
|
||||
#[arg(short = 'u', long = "pg-connection-url")]
|
||||
#[arg(short = 'u', long = "pg-connection-url", help = "help.arg.postgres-connection-url")]
|
||||
pub pg_connection_url: Option<String>,
|
||||
}
|
||||
impl ContextConfig for RegistryConfig {
|
||||
@@ -124,7 +124,7 @@ impl RegistryContext {
|
||||
};
|
||||
if config.registry_hostname.is_empty() {
|
||||
return Err(Error::new(
|
||||
eyre!("missing required configuration: registry-hostname"),
|
||||
eyre!("{}", t!("registry.context.missing-hostname")),
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
}
|
||||
@@ -165,6 +165,7 @@ impl Deref for RegistryContext {
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Parser)]
|
||||
pub struct RegistryUrlParams {
|
||||
#[arg(help = "help.arg.registry-url")]
|
||||
pub registry: Url,
|
||||
}
|
||||
|
||||
@@ -195,7 +196,7 @@ impl CallRemote<RegistryContext> for CliContext {
|
||||
url
|
||||
} else {
|
||||
return Err(
|
||||
Error::new(eyre!("`--registry` required"), ErrorKind::InvalidRequest).into(),
|
||||
Error::new(eyre!("{}", t!("registry.context.registry-required")), ErrorKind::InvalidRequest).into(),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -330,7 +331,7 @@ impl SignatureAuthContext for RegistryContext {
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization))
|
||||
Err(Error::new(eyre!("{}", t!("registry.context.unauthorized")), ErrorKind::Authorization))
|
||||
}
|
||||
async fn post_auth_hook(
|
||||
&self,
|
||||
|
||||
@@ -22,7 +22,7 @@ pub fn db_api<C: Context>() -> ParentHandler<C> {
|
||||
"dump",
|
||||
from_fn_async(cli_dump)
|
||||
.with_display_serializable()
|
||||
.with_about("Filter/query db to display tables and records"),
|
||||
.with_about("about.filter-query-db"),
|
||||
)
|
||||
.subcommand(
|
||||
"dump",
|
||||
@@ -34,7 +34,7 @@ pub fn db_api<C: Context>() -> ParentHandler<C> {
|
||||
"apply",
|
||||
from_fn_async(cli_apply)
|
||||
.no_display()
|
||||
.with_about("Update a db record"),
|
||||
.with_about("about.update-db-record"),
|
||||
)
|
||||
.subcommand(
|
||||
"apply",
|
||||
@@ -48,8 +48,9 @@ pub fn db_api<C: Context>() -> ParentHandler<C> {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct CliDumpParams {
|
||||
#[arg(long = "pointer", short = 'p')]
|
||||
#[arg(long = "pointer", short = 'p', help = "help.arg.db-pointer")]
|
||||
pointer: Option<JsonPointer>,
|
||||
#[arg(help = "help.arg.database-path")]
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
@@ -81,7 +82,7 @@ async fn cli_dump(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct DumpParams {
|
||||
#[arg(long = "pointer", short = 'p')]
|
||||
#[arg(long = "pointer", short = 'p', help = "help.arg.db-pointer")]
|
||||
#[ts(type = "string | null")]
|
||||
pointer: Option<JsonPointer>,
|
||||
}
|
||||
@@ -97,7 +98,9 @@ pub async fn dump(ctx: RegistryContext, DumpParams { pointer }: DumpParams) -> R
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct CliApplyParams {
|
||||
#[arg(help = "help.arg.db-apply-expr")]
|
||||
expr: String,
|
||||
#[arg(help = "help.arg.database-path")]
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
@@ -152,7 +155,9 @@ async fn cli_apply(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ApplyParams {
|
||||
#[arg(help = "help.arg.db-apply-expr")]
|
||||
expr: String,
|
||||
#[arg(help = "help.arg.database-path")]
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
|
||||
@@ -44,28 +44,44 @@ impl DeviceInfo {
|
||||
impl DeviceInfo {
|
||||
pub fn to_header_value(&self) -> HeaderValue {
|
||||
let mut url: Url = "http://localhost".parse().unwrap();
|
||||
url.query_pairs_mut()
|
||||
.append_pair("os.version", &self.os.version.to_string())
|
||||
let mut qp = url.query_pairs_mut();
|
||||
qp.append_pair("os.version", &self.os.version.to_string())
|
||||
.append_pair("os.compat", &self.os.compat.to_string())
|
||||
.append_pair("os.platform", &*self.os.platform);
|
||||
if let Some(lang) = self.os.language.as_deref() {
|
||||
qp.append_pair("os.language", lang);
|
||||
}
|
||||
drop(qp);
|
||||
|
||||
HeaderValue::from_str(url.query().unwrap_or_default()).unwrap()
|
||||
}
|
||||
pub fn from_header_value(header: &HeaderValue) -> Result<Self, Error> {
|
||||
let query: BTreeMap<_, _> = form_urlencoded::parse(header.as_bytes()).collect();
|
||||
let has_hw_info = query.keys().any(|k| k.starts_with("hardware."));
|
||||
let version = query
|
||||
.get("os.version")
|
||||
.or_not_found("os.version")?
|
||||
.parse()?;
|
||||
Ok(Self {
|
||||
os: OsInfo {
|
||||
version: query
|
||||
.get("os.version")
|
||||
.or_not_found("os.version")?
|
||||
.parse()?,
|
||||
compat: query.get("os.compat").or_not_found("os.compat")?.parse()?,
|
||||
platform: query
|
||||
.get("os.platform")
|
||||
.or_not_found("os.platform")?
|
||||
.deref()
|
||||
.into(),
|
||||
language: query
|
||||
.get("os.language")
|
||||
.map(|v| v.deref())
|
||||
.map(InternedString::intern)
|
||||
.or_else(|| {
|
||||
if version < "0.4.0-alpha.18".parse().ok()? {
|
||||
Some(rust_i18n::locale().deref().into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
version,
|
||||
},
|
||||
hardware: has_hw_info
|
||||
.then(|| {
|
||||
@@ -190,8 +206,8 @@ pub struct OsInfo {
|
||||
pub version: Version,
|
||||
#[ts(type = "string")]
|
||||
pub compat: VersionRange,
|
||||
#[ts(type = "string")]
|
||||
pub platform: InternedString,
|
||||
pub language: Option<InternedString>,
|
||||
}
|
||||
impl From<&RpcContext> for OsInfo {
|
||||
fn from(_: &RpcContext) -> Self {
|
||||
@@ -199,6 +215,7 @@ impl From<&RpcContext> for OsInfo {
|
||||
version: crate::version::Current::default().semver(),
|
||||
compat: crate::version::Current::default().compat().clone(),
|
||||
platform: InternedString::intern(&*crate::PLATFORM),
|
||||
language: Some(InternedString::intern(&*rust_i18n::locale())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn info_api<C: Context>() -> ParentHandler<C, WithIoFormat<Empty>> {
|
||||
from_fn_async(get_info)
|
||||
.with_metadata("authenticated", Value::Bool(false))
|
||||
.with_display_serializable()
|
||||
.with_about("Display registry name, icon, and package categories")
|
||||
.with_about("about.display-registry-info")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -29,7 +29,7 @@ pub fn info_api<C: Context>() -> ParentHandler<C, WithIoFormat<Empty>> {
|
||||
from_fn_async(set_name)
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Set the name for the registry")
|
||||
.with_about("about.set-registry-name")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -42,7 +42,7 @@ pub fn info_api<C: Context>() -> ParentHandler<C, WithIoFormat<Empty>> {
|
||||
"set-icon",
|
||||
from_fn_async(cli_set_icon)
|
||||
.no_display()
|
||||
.with_about("Set the icon for the registry"),
|
||||
.with_about("about.set-registry-icon"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ pub async fn get_info(ctx: RegistryContext) -> Result<RegistryInfo, Error> {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct SetNameParams {
|
||||
#[arg(help = "help.arg.registry-name")]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
@@ -104,6 +105,7 @@ pub async fn set_icon(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct CliSetIconParams {
|
||||
#[arg(help = "help.arg.icon-path")]
|
||||
pub icon: PathBuf,
|
||||
}
|
||||
|
||||
|
||||
@@ -76,26 +76,26 @@ pub fn registry_api<C: Context>() -> ParentHandler<C> {
|
||||
"index",
|
||||
from_fn_async(get_full_index)
|
||||
.with_display_serializable()
|
||||
.with_about("List info including registry name and packages")
|
||||
.with_about("about.list-registry-info-packages")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand("info", info::info_api::<C>())
|
||||
// set info and categories
|
||||
.subcommand(
|
||||
"os",
|
||||
os::os_api::<C>().with_about("Commands related to OS assets and versions"),
|
||||
os::os_api::<C>().with_about("about.commands-os-assets-versions"),
|
||||
)
|
||||
.subcommand(
|
||||
"package",
|
||||
package::package_api::<C>().with_about("Commands to index, add, or get packages"),
|
||||
package::package_api::<C>().with_about("about.commands-index-add-get-packages"),
|
||||
)
|
||||
.subcommand(
|
||||
"admin",
|
||||
admin::admin_api::<C>().with_about("Commands to add or list admins or signers"),
|
||||
admin::admin_api::<C>().with_about("about.commands-add-list-admins-signers"),
|
||||
)
|
||||
.subcommand(
|
||||
"db",
|
||||
db::db_api::<C>().with_about("Commands to interact with the db i.e. dump and apply"),
|
||||
db::db_api::<C>().with_about("about.commands-registry-db"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ async fn add_asset(
|
||||
.mutate(|s| {
|
||||
if s.commitment != commitment {
|
||||
Err(Error::new(
|
||||
eyre!("commitment does not match"),
|
||||
eyre!("{}", t!("registry.os.asset.commitment-mismatch")),
|
||||
ErrorKind::InvalidSignature,
|
||||
))
|
||||
} else {
|
||||
@@ -154,7 +154,7 @@ async fn add_asset(
|
||||
})?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization))
|
||||
Err(Error::new(eyre!("{}", t!("registry.os.asset.unauthorized")), ErrorKind::Authorization))
|
||||
}
|
||||
})
|
||||
.await
|
||||
@@ -179,11 +179,13 @@ pub async fn add_squashfs(ctx: RegistryContext, params: AddAssetParams) -> Resul
|
||||
#[command(rename_all = "kebab-case")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliAddAssetParams {
|
||||
#[arg(short = 'p', long = "platform")]
|
||||
#[arg(short = 'p', long = "platform", help = "help.arg.platform")]
|
||||
pub platform: InternedString,
|
||||
#[arg(short = 'v', long = "version")]
|
||||
#[arg(short = 'v', long = "version", help = "help.arg.os-version")]
|
||||
pub version: Version,
|
||||
#[arg(help = "help.arg.asset-file-path")]
|
||||
pub file: PathBuf,
|
||||
#[arg(help = "help.arg.asset-url")]
|
||||
pub url: Url,
|
||||
}
|
||||
|
||||
@@ -208,7 +210,7 @@ pub async fn cli_add_asset(
|
||||
Some("squashfs") => "squashfs",
|
||||
_ => {
|
||||
return Err(Error::new(
|
||||
eyre!("Unknown extension"),
|
||||
eyre!("{}", t!("registry.os.asset.unknown-extension")),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
@@ -232,7 +234,7 @@ pub async fn cli_add_asset(
|
||||
let size = file
|
||||
.size()
|
||||
.await
|
||||
.ok_or_else(|| Error::new(eyre!("failed to read file metadata"), ErrorKind::Filesystem))?;
|
||||
.ok_or_else(|| Error::new(eyre!("{}", t!("registry.os.asset.failed-read-metadata")), ErrorKind::Filesystem))?;
|
||||
let commitment = Blake3Commitment {
|
||||
hash: Base64(*blake3.as_bytes()),
|
||||
size,
|
||||
@@ -334,7 +336,7 @@ async fn remove_asset(
|
||||
.remove(&platform)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization))
|
||||
Err(Error::new(eyre!("{}", t!("registry.os.asset.unauthorized")), ErrorKind::Authorization))
|
||||
}
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -34,7 +34,7 @@ pub fn get_api<C: Context>() -> ParentHandler<C> {
|
||||
"iso",
|
||||
from_fn_async(cli_get_os_asset)
|
||||
.no_display()
|
||||
.with_about("Download iso"),
|
||||
.with_about("about.download-iso"),
|
||||
)
|
||||
.subcommand(
|
||||
"img",
|
||||
@@ -46,7 +46,7 @@ pub fn get_api<C: Context>() -> ParentHandler<C> {
|
||||
"img",
|
||||
from_fn_async(cli_get_os_asset)
|
||||
.no_display()
|
||||
.with_about("Download img"),
|
||||
.with_about("about.download-img"),
|
||||
)
|
||||
.subcommand(
|
||||
"squashfs",
|
||||
@@ -58,7 +58,7 @@ pub fn get_api<C: Context>() -> ParentHandler<C> {
|
||||
"squashfs",
|
||||
from_fn_async(cli_get_os_asset)
|
||||
.no_display()
|
||||
.with_about("Download squashfs"),
|
||||
.with_about("about.download-squashfs"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -121,18 +121,20 @@ pub async fn get_squashfs(
|
||||
#[command(rename_all = "kebab-case")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliGetOsAssetParams {
|
||||
#[arg(help = "help.arg.os-version")]
|
||||
pub version: Version,
|
||||
#[arg(help = "help.arg.platform")]
|
||||
pub platform: InternedString,
|
||||
#[arg(
|
||||
long = "download",
|
||||
short = 'd',
|
||||
help = "The path of the directory to download to"
|
||||
help = "help.arg.download-directory"
|
||||
)]
|
||||
pub download: Option<PathBuf>,
|
||||
#[arg(
|
||||
long = "reverify",
|
||||
short = 'r',
|
||||
help = "verify the hash of the file a second time after download"
|
||||
help = "help.arg.reverify-hash"
|
||||
)]
|
||||
pub reverify: bool,
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ pub fn asset_api<C: Context>() -> ParentHandler<C> {
|
||||
"add",
|
||||
from_fn_async(add::cli_add_asset)
|
||||
.no_display()
|
||||
.with_about("Add asset to registry"),
|
||||
.with_about("about.add-asset-registry"),
|
||||
)
|
||||
.subcommand("remove", add::remove_api::<C>())
|
||||
.subcommand("sign", sign::sign_api::<C>())
|
||||
@@ -19,10 +19,10 @@ pub fn asset_api<C: Context>() -> ParentHandler<C> {
|
||||
"sign",
|
||||
from_fn_async(sign::cli_sign_asset)
|
||||
.no_display()
|
||||
.with_about("Sign file and add to registry index"),
|
||||
.with_about("about.sign-file-add-registry"),
|
||||
)
|
||||
.subcommand(
|
||||
"get",
|
||||
get::get_api::<C>().with_about("Commands to download image, iso, or squashfs files"),
|
||||
get::get_api::<C>().with_about("about.commands-download-assets"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ async fn sign_asset(
|
||||
.contains(&guid)
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("signer {guid} is not authorized"),
|
||||
eyre!("{}", t!("registry.os.asset.signer-not-authorized", guid = guid)),
|
||||
ErrorKind::Authorization,
|
||||
));
|
||||
}
|
||||
@@ -136,10 +136,11 @@ pub async fn sign_squashfs(ctx: RegistryContext, params: SignAssetParams) -> Res
|
||||
#[command(rename_all = "kebab-case")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliSignAssetParams {
|
||||
#[arg(short = 'p', long = "platform")]
|
||||
#[arg(short = 'p', long = "platform", help = "help.arg.platform")]
|
||||
pub platform: InternedString,
|
||||
#[arg(short = 'v', long = "version")]
|
||||
#[arg(short = 'v', long = "version", help = "help.arg.os-version")]
|
||||
pub version: Version,
|
||||
#[arg(help = "help.arg.asset-file-path")]
|
||||
pub file: PathBuf,
|
||||
}
|
||||
|
||||
@@ -163,7 +164,7 @@ pub async fn cli_sign_asset(
|
||||
Some("squashfs") => "squashfs",
|
||||
_ => {
|
||||
return Err(Error::new(
|
||||
eyre!("Unknown extension"),
|
||||
eyre!("{}", t!("registry.os.asset.unknown-extension")),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
@@ -186,7 +187,7 @@ pub async fn cli_sign_asset(
|
||||
let size = file
|
||||
.size()
|
||||
.await
|
||||
.ok_or_else(|| Error::new(eyre!("failed to read file metadata"), ErrorKind::Filesystem))?;
|
||||
.ok_or_else(|| Error::new(eyre!("{}", t!("registry.os.asset.failed-read-metadata")), ErrorKind::Filesystem))?;
|
||||
let commitment = Blake3Commitment {
|
||||
hash: Base64(*blake3.as_bytes()),
|
||||
size,
|
||||
|
||||
@@ -17,16 +17,16 @@ pub fn os_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(index::get_os_index)
|
||||
.with_metadata("authenticated", Value::Bool(false))
|
||||
.with_display_serializable()
|
||||
.with_about("List index of OS versions")
|
||||
.with_about("about.list-os-versions-index")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"asset",
|
||||
asset::asset_api::<C>().with_about("Commands to add, sign, or get registry assets"),
|
||||
asset::asset_api::<C>().with_about("about.commands-add-sign-get-assets"),
|
||||
)
|
||||
.subcommand(
|
||||
"version",
|
||||
version::version_api::<C>()
|
||||
.with_about("Commands to add, remove, or list versions or version signers"),
|
||||
.with_about("about.commands-add-remove-list-versions"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ pub fn version_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.with_metadata("get_signer", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Add OS version")
|
||||
.with_about("about.add-os-version")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -35,12 +35,12 @@ pub fn version_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(remove_version)
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Remove OS version")
|
||||
.with_about("about.remove-os-version")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"signer",
|
||||
signer::signer_api::<C>().with_about("Add, remove, and list version signers"),
|
||||
signer::signer_api::<C>().with_about("about.add-remove-list-version-signers"),
|
||||
)
|
||||
.subcommand(
|
||||
"get",
|
||||
@@ -51,7 +51,7 @@ pub fn version_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_custom_display_fn(|handle, result| {
|
||||
display_version_info(handle.params, result)
|
||||
})
|
||||
.with_about("Get OS versions and related version info")
|
||||
.with_about("about.get-os-versions-info")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -62,10 +62,14 @@ pub fn version_api<C: Context>() -> ParentHandler<C> {
|
||||
#[ts(export)]
|
||||
pub struct AddVersionParams {
|
||||
#[ts(type = "string")]
|
||||
#[arg(help = "help.arg.os-version")]
|
||||
pub version: Version,
|
||||
#[arg(help = "help.arg.version-headline")]
|
||||
pub headline: String,
|
||||
#[arg(help = "help.arg.release-notes")]
|
||||
pub release_notes: String,
|
||||
#[ts(type = "string")]
|
||||
#[arg(help = "help.arg.source-version-range")]
|
||||
pub source_version: VersionRange,
|
||||
#[arg(skip)]
|
||||
#[ts(skip)]
|
||||
@@ -110,6 +114,7 @@ pub async fn add_version(
|
||||
#[ts(export)]
|
||||
pub struct RemoveVersionParams {
|
||||
#[ts(type = "string")]
|
||||
#[arg(help = "help.arg.os-version")]
|
||||
pub version: Version,
|
||||
}
|
||||
|
||||
@@ -135,15 +140,15 @@ pub async fn remove_version(
|
||||
#[ts(export)]
|
||||
pub struct GetOsVersionParams {
|
||||
#[ts(type = "string | null")]
|
||||
#[arg(long = "src")]
|
||||
#[arg(long = "src", help = "help.arg.source-version")]
|
||||
pub source_version: Option<Version>,
|
||||
#[ts(type = "string | null")]
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.target-version-range")]
|
||||
pub target_version: Option<VersionRange>,
|
||||
#[arg(long = "id")]
|
||||
#[arg(long = "id", help = "help.arg.server-id")]
|
||||
server_id: Option<String>,
|
||||
#[ts(type = "string | null")]
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.platform")]
|
||||
platform: Option<InternedString>,
|
||||
#[ts(skip)]
|
||||
#[arg(skip)]
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn signer_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(add_version_signer)
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Add version signer")
|
||||
.with_about("about.add-version-signer")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -29,7 +29,7 @@ pub fn signer_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(remove_version_signer)
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Remove version signer")
|
||||
.with_about("about.remove-version-signer")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -38,7 +38,7 @@ pub fn signer_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_metadata("authenticated", Value::Bool(false))
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|handle, result| display_signers(handle.params, result))
|
||||
.with_about("List version signers and related signer info")
|
||||
.with_about("about.list-version-signers")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -95,7 +95,7 @@ pub async fn remove_version_signer(
|
||||
.mutate(|s| Ok(s.remove(&signer)))?
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("signer {signer} is not authorized to sign for v{version}"),
|
||||
eyre!("{}", t!("registry.os.version.signer-not-authorized", signer = signer, version = version)),
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ pub async fn add_package(
|
||||
|
||||
let Some(([url], rest)) = urls.split_at_checked(1) else {
|
||||
return Err(Error::new(
|
||||
eyre!("must specify at least 1 url"),
|
||||
eyre!("{}", t!("registry.package.add.must-specify-url")),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
};
|
||||
@@ -112,7 +112,7 @@ pub async fn add_package(
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization))
|
||||
Err(Error::new(eyre!("{}", t!("registry.package.add.unauthorized")), ErrorKind::Authorization))
|
||||
}
|
||||
})
|
||||
.await
|
||||
@@ -123,10 +123,11 @@ pub async fn add_package(
|
||||
#[command(rename_all = "kebab-case")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliAddPackageParams {
|
||||
#[arg(help = "help.arg.s9pk-file-path")]
|
||||
pub file: PathBuf,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.package-url")]
|
||||
pub url: Vec<Url>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.no-verify")]
|
||||
pub no_verify: bool,
|
||||
}
|
||||
|
||||
@@ -205,9 +206,11 @@ pub async fn cli_add_package(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct RemovePackageParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub id: PackageId,
|
||||
#[arg(help = "help.arg.package-version")]
|
||||
pub version: VersionString,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.signature-hash")]
|
||||
pub sighash: Option<Base64<[u8; 32]>>,
|
||||
#[ts(skip)]
|
||||
#[arg(skip)]
|
||||
@@ -226,7 +229,7 @@ pub async fn remove_package(
|
||||
) -> Result<bool, Error> {
|
||||
let peek = ctx.db.peek().await;
|
||||
let signer =
|
||||
signer.ok_or_else(|| Error::new(eyre!("missing signer"), ErrorKind::InvalidRequest))?;
|
||||
signer.ok_or_else(|| Error::new(eyre!("{}", t!("registry.package.missing-signer")), ErrorKind::InvalidRequest))?;
|
||||
let signer_guid = peek.as_index().as_signers().get_signer(&signer)?;
|
||||
|
||||
let rev = ctx
|
||||
@@ -267,7 +270,7 @@ pub async fn remove_package(
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization))
|
||||
Err(Error::new(eyre!("{}", t!("registry.package.unauthorized")), ErrorKind::Authorization))
|
||||
}
|
||||
})
|
||||
.await;
|
||||
@@ -342,7 +345,7 @@ pub async fn add_mirror(
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization))
|
||||
Err(Error::new(eyre!("{}", t!("registry.package.add-mirror.unauthorized")), ErrorKind::Authorization))
|
||||
}
|
||||
})
|
||||
.await
|
||||
@@ -353,8 +356,11 @@ pub async fn add_mirror(
|
||||
#[command(rename_all = "kebab-case")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliAddMirrorParams {
|
||||
#[arg(help = "help.arg.s9pk-file-path")]
|
||||
pub file: PathBuf,
|
||||
#[arg(help = "help.arg.mirror-url")]
|
||||
pub url: Url,
|
||||
#[arg(long, help = "help.arg.no-verify")]
|
||||
pub no_verify: bool,
|
||||
}
|
||||
|
||||
@@ -432,9 +438,11 @@ pub async fn cli_add_mirror(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct RemoveMirrorParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub id: PackageId,
|
||||
#[arg(help = "help.arg.package-version")]
|
||||
pub version: VersionString,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.mirror-url")]
|
||||
#[ts(type = "string")]
|
||||
pub url: Url,
|
||||
#[ts(skip)]
|
||||
@@ -454,7 +462,7 @@ pub async fn remove_mirror(
|
||||
) -> Result<(), Error> {
|
||||
let peek = ctx.db.peek().await;
|
||||
let signer =
|
||||
signer.ok_or_else(|| Error::new(eyre!("missing signer"), ErrorKind::InvalidRequest))?;
|
||||
signer.ok_or_else(|| Error::new(eyre!("{}", t!("registry.package.missing-signer")), ErrorKind::InvalidRequest))?;
|
||||
let signer_guid = peek.as_index().as_signers().get_signer(&signer)?;
|
||||
|
||||
ctx.db
|
||||
@@ -483,7 +491,7 @@ pub async fn remove_mirror(
|
||||
.for_each(|(_, asset)| asset.urls.retain(|u| u != &url));
|
||||
if s.iter().any(|(_, asset)| asset.urls.is_empty()) {
|
||||
Err(Error::new(
|
||||
eyre!("cannot remove last mirror from an s9pk"),
|
||||
eyre!("{}", t!("registry.package.cannot-remove-last-mirror")),
|
||||
ErrorKind::InvalidRequest,
|
||||
))
|
||||
} else {
|
||||
@@ -493,7 +501,7 @@ pub async fn remove_mirror(
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization))
|
||||
Err(Error::new(eyre!("{}", t!("registry.package.remove-mirror.unauthorized")), ErrorKind::Authorization))
|
||||
}
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::context::CliContext;
|
||||
use crate::prelude::*;
|
||||
use crate::registry::context::RegistryContext;
|
||||
use crate::registry::package::index::Category;
|
||||
use crate::s9pk::manifest::LocaleString;
|
||||
use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable};
|
||||
|
||||
pub fn category_api<C: Context>() -> ParentHandler<C> {
|
||||
@@ -20,7 +21,7 @@ pub fn category_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(add_category)
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Add a category to the registry")
|
||||
.with_about("about.add-category-registry")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -28,7 +29,7 @@ pub fn category_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(remove_category)
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Remove a category from the registry")
|
||||
.with_about("about.remove-category-registry")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -36,7 +37,7 @@ pub fn category_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(add_package)
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Add a package to a category")
|
||||
.with_about("about.add-package-category")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -44,7 +45,7 @@ pub fn category_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(remove_package)
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Remove a package from a category")
|
||||
.with_about("about.remove-package-category")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -66,7 +67,7 @@ pub fn category_api<C: Context>() -> ParentHandler<C> {
|
||||
pub struct AddCategoryParams {
|
||||
#[ts(type = "string")]
|
||||
pub id: InternedString,
|
||||
pub name: String,
|
||||
pub name: LocaleString,
|
||||
}
|
||||
|
||||
pub async fn add_category(
|
||||
@@ -196,7 +197,7 @@ pub fn display_categories<T>(
|
||||
"NAME",
|
||||
]);
|
||||
for (id, info) in categories {
|
||||
table.add_row(row![&*id, &info.name]);
|
||||
table.add_row(row![&*id, &info.name.localized()]);
|
||||
}
|
||||
table.print_tty(false)?;
|
||||
Ok(())
|
||||
|
||||
@@ -51,17 +51,18 @@ pub struct PackageInfoShort {
|
||||
#[ts(export)]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct GetPackageParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub id: Option<PackageId>,
|
||||
#[ts(type = "string | null")]
|
||||
#[arg(long, short = 'v')]
|
||||
#[arg(long, short = 'v', help = "help.arg.target-version-range")]
|
||||
pub target_version: Option<VersionRange>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.source-version")]
|
||||
pub source_version: Option<VersionString>,
|
||||
#[ts(skip)]
|
||||
#[arg(skip)]
|
||||
#[serde(rename = "__DeviceInfo_device_info")]
|
||||
pub device_info: Option<DeviceInfo>,
|
||||
#[arg(default_value = "none")]
|
||||
#[arg(default_value = "none", help = "help.arg.other-versions-detail")]
|
||||
pub other_versions: Option<PackageDetailLevel>,
|
||||
}
|
||||
|
||||
@@ -78,20 +79,20 @@ pub struct GetPackageResponse {
|
||||
pub other_versions: Option<BTreeMap<VersionString, PackageInfoShort>>,
|
||||
}
|
||||
impl GetPackageResponse {
|
||||
pub fn tables(&self) -> Vec<prettytable::Table> {
|
||||
pub fn tables(self) -> Vec<prettytable::Table> {
|
||||
use prettytable::*;
|
||||
|
||||
let mut res = Vec::with_capacity(self.best.len());
|
||||
|
||||
for (version, info) in &self.best {
|
||||
let mut table = info.table(version);
|
||||
for (version, info) in self.best {
|
||||
let mut table = info.table(&version);
|
||||
|
||||
let lesser_versions: BTreeMap<_, _> = self
|
||||
.other_versions
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|(v, _)| ***v < **version)
|
||||
.filter(|(v, _)| ***v < *version)
|
||||
.collect();
|
||||
|
||||
if !lesser_versions.is_empty() {
|
||||
@@ -120,13 +121,17 @@ pub struct GetPackageResponseFull {
|
||||
pub other_versions: BTreeMap<VersionString, PackageVersionInfo>,
|
||||
}
|
||||
impl GetPackageResponseFull {
|
||||
pub fn tables(&self) -> Vec<prettytable::Table> {
|
||||
pub fn tables(self) -> Vec<prettytable::Table> {
|
||||
let mut res = Vec::with_capacity(self.best.len());
|
||||
|
||||
let all: BTreeMap<_, _> = self.best.iter().chain(self.other_versions.iter()).collect();
|
||||
let all: BTreeMap<_, _> = self
|
||||
.best
|
||||
.into_iter()
|
||||
.chain(self.other_versions.into_iter())
|
||||
.collect();
|
||||
|
||||
for (version, info) in all {
|
||||
res.push(info.table(version));
|
||||
res.push(info.table(&version));
|
||||
}
|
||||
|
||||
res
|
||||
@@ -401,11 +406,12 @@ pub fn display_package_info(
|
||||
#[derive(Debug, Deserialize, Serialize, TS, Parser)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliDownloadParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub id: PackageId,
|
||||
#[arg(long, short = 'v')]
|
||||
#[arg(long, short = 'v', help = "help.arg.target-version-range")]
|
||||
#[ts(type = "string | null")]
|
||||
pub target_version: Option<VersionRange>,
|
||||
#[arg(short, long)]
|
||||
#[arg(short, long, help = "help.arg.destination-path")]
|
||||
pub dest: Option<PathBuf>,
|
||||
}
|
||||
|
||||
@@ -441,8 +447,12 @@ pub async fn cli_download(
|
||||
0 => {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Could not find a version of {id} that satisfies {}",
|
||||
target_version.unwrap_or(VersionRange::Any)
|
||||
"{}",
|
||||
t!(
|
||||
"registry.package.get.version-not-found",
|
||||
id = id,
|
||||
version = target_version.unwrap_or(VersionRange::Any)
|
||||
)
|
||||
),
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
@@ -462,8 +472,12 @@ pub async fn cli_download(
|
||||
0 => {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Could not find a version of {id} that satisfies {}",
|
||||
target_version.unwrap_or(VersionRange::Any)
|
||||
"{}",
|
||||
t!(
|
||||
"registry.package.get.version-not-found",
|
||||
id = id,
|
||||
version = target_version.unwrap_or(VersionRange::Any)
|
||||
)
|
||||
),
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
@@ -551,7 +565,7 @@ pub async fn cli_download(
|
||||
progress_tracker.complete();
|
||||
progress.await.unwrap();
|
||||
|
||||
println!("Download Complete");
|
||||
println!("{}", t!("registry.package.get.download-complete"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::registry::device_info::DeviceInfo;
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::s9pk::S9pk;
|
||||
use crate::s9pk::git_hash::GitHash;
|
||||
use crate::s9pk::manifest::{Alerts, Description, HardwareRequirements};
|
||||
use crate::s9pk::manifest::{Alerts, Description, HardwareRequirements, LocaleString};
|
||||
use crate::s9pk::merkle_archive::source::FileSource;
|
||||
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||
use crate::sign::{AnySignature, AnyVerifyingKey};
|
||||
@@ -49,22 +49,27 @@ pub struct PackageInfo {
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct Category {
|
||||
pub name: String,
|
||||
pub name: LocaleString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS, PartialEq, Eq)]
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct DependencyMetadata {
|
||||
#[ts(type = "string | null")]
|
||||
pub title: Option<InternedString>,
|
||||
pub title: Option<LocaleString>,
|
||||
pub icon: Option<DataUrl<'static>>,
|
||||
pub description: Option<String>,
|
||||
pub description: Option<LocaleString>,
|
||||
pub optional: bool,
|
||||
}
|
||||
impl DependencyMetadata {
|
||||
pub fn localize_for(&mut self, locale: &str) {
|
||||
self.title.as_mut().map(|t| t.localize_for(locale));
|
||||
self.description.as_mut().map(|d| d.localize_for(locale));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS, PartialEq, Eq)]
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct PackageMetadata {
|
||||
@@ -72,7 +77,7 @@ pub struct PackageMetadata {
|
||||
pub title: InternedString,
|
||||
pub icon: DataUrl<'static>,
|
||||
pub description: Description,
|
||||
pub release_notes: String,
|
||||
pub release_notes: LocaleString,
|
||||
pub git_hash: Option<GitHash>,
|
||||
#[ts(type = "string")]
|
||||
pub license: InternedString,
|
||||
@@ -199,20 +204,20 @@ impl PackageVersionInfo {
|
||||
self.s9pks.sort_by_key(|(h, _)| h.specificity_desc());
|
||||
Ok(())
|
||||
}
|
||||
pub fn table(&self, version: &VersionString) -> prettytable::Table {
|
||||
pub fn table(self, version: &VersionString) -> prettytable::Table {
|
||||
use prettytable::*;
|
||||
|
||||
let mut table = Table::new();
|
||||
|
||||
table.add_row(row![bc => &self.metadata.title]);
|
||||
table.add_row(row![br -> "VERSION", AsRef::<str>::as_ref(version)]);
|
||||
table.add_row(row![br -> "RELEASE NOTES", &self.metadata.release_notes]);
|
||||
table.add_row(row![br -> "RELEASE NOTES", &self.metadata.release_notes.localized()]);
|
||||
table.add_row(
|
||||
row![br -> "ABOUT", &textwrap::wrap(&self.metadata.description.short, 80).join("\n")],
|
||||
row![br -> "ABOUT", &textwrap::wrap(&self.metadata.description.short.localized(), 80).join("\n")],
|
||||
);
|
||||
table.add_row(row![
|
||||
br -> "DESCRIPTION",
|
||||
&textwrap::wrap(&self.metadata.description.long, 80).join("\n")
|
||||
&textwrap::wrap(&self.metadata.description.long.localized(), 80).join("\n")
|
||||
]);
|
||||
table.add_row(row![br -> "GIT HASH", self.metadata.git_hash.as_deref().unwrap_or("N/A")]);
|
||||
table.add_row(row![br -> "LICENSE", &self.metadata.license]);
|
||||
@@ -280,6 +285,24 @@ impl Model<PackageVersionInfo> {
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Some(locale) = device_info.os.language.as_deref() {
|
||||
let metadata = self.as_metadata_mut();
|
||||
metadata
|
||||
.as_alerts_mut()
|
||||
.mutate(|a| Ok(a.localize_for(locale)))?;
|
||||
metadata
|
||||
.as_dependency_metadata_mut()
|
||||
.as_entries_mut()?
|
||||
.into_iter()
|
||||
.try_for_each(|(_, d)| d.mutate(|d| Ok(d.localize_for(locale))))?;
|
||||
metadata
|
||||
.as_description_mut()
|
||||
.mutate(|d| Ok(d.localize_for(locale)))?;
|
||||
metadata
|
||||
.as_release_notes_mut()
|
||||
.mutate(|r| Ok(r.localize_for(locale)))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn package_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(index::get_package_index)
|
||||
.with_metadata("authenticated", Value::Bool(false))
|
||||
.with_display_serializable()
|
||||
.with_about("List packages and categories")
|
||||
.with_about("about.list-packages-categories")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -30,7 +30,7 @@ pub fn package_api<C: Context>() -> ParentHandler<C> {
|
||||
"add",
|
||||
from_fn_async(add::cli_add_package)
|
||||
.no_display()
|
||||
.with_about("Add package to registry index"),
|
||||
.with_about("about.add-package-registry"),
|
||||
)
|
||||
.subcommand(
|
||||
"add-mirror",
|
||||
@@ -42,7 +42,7 @@ pub fn package_api<C: Context>() -> ParentHandler<C> {
|
||||
"add-mirror",
|
||||
from_fn_async(add::cli_add_mirror)
|
||||
.no_display()
|
||||
.with_about("Add a mirror for an s9pk"),
|
||||
.with_about("about.add-mirror-s9pk"),
|
||||
)
|
||||
.subcommand(
|
||||
"remove",
|
||||
@@ -51,17 +51,17 @@ pub fn package_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_custom_display_fn(|args, changed| {
|
||||
if !changed {
|
||||
tracing::warn!(
|
||||
"{}@{}{} does not exist, so not removed",
|
||||
args.params.id,
|
||||
args.params.version,
|
||||
args.params
|
||||
.sighash
|
||||
.map_or(String::new(), |h| format!("#{h}"))
|
||||
"{}",
|
||||
t!("registry.package.remove-not-exist",
|
||||
id = args.params.id,
|
||||
version = args.params.version,
|
||||
sighash = args.params.sighash.map_or(String::new(), |h| format!("#{h}"))
|
||||
)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.with_about("Remove package from registry index")
|
||||
.with_about("about.remove-package-registry")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -69,12 +69,12 @@ pub fn package_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(add::remove_mirror)
|
||||
.with_metadata("get_signer", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Remove a mirror from a package")
|
||||
.with_about("about.remove-mirror-package")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"signer",
|
||||
signer::signer_api::<C>().with_about("Add, remove, and list package signers"),
|
||||
signer::signer_api::<C>().with_about("about.add-remove-list-package-signers"),
|
||||
)
|
||||
.subcommand(
|
||||
"get",
|
||||
@@ -85,18 +85,18 @@ pub fn package_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_custom_display_fn(|handle, result| {
|
||||
get::display_package_info(handle.params, result)
|
||||
})
|
||||
.with_about("List installation candidate package(s)")
|
||||
.with_about("about.list-installation-candidates")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"download",
|
||||
from_fn_async_local(get::cli_download)
|
||||
.no_display()
|
||||
.with_about("Download an s9pk"),
|
||||
.with_about("about.download-s9pk"),
|
||||
)
|
||||
.subcommand(
|
||||
"category",
|
||||
category::category_api::<C>()
|
||||
.with_about("Update the categories for packages on the registry"),
|
||||
.with_about("about.update-categories-registry"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ pub fn signer_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(add_package_signer)
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Add package signer")
|
||||
.with_about("about.add-package-signer")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -30,7 +30,7 @@ pub fn signer_api<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(remove_package_signer)
|
||||
.with_metadata("admin", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("Remove package signer")
|
||||
.with_about("about.remove-package-signer")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -41,7 +41,7 @@ pub fn signer_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_custom_display_fn(|handle, result| {
|
||||
display_package_signers(handle.params, result)
|
||||
})
|
||||
.with_about("List package signers and related signer info")
|
||||
.with_about("about.list-package-signers")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -51,9 +51,11 @@ pub fn signer_api<C: Context>() -> ParentHandler<C> {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct AddPackageSignerParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub id: PackageId,
|
||||
#[arg(help = "help.arg.signer-id")]
|
||||
pub signer: Guid,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.version-range")]
|
||||
#[ts(type = "string | null")]
|
||||
pub versions: Option<VersionRange>,
|
||||
}
|
||||
@@ -93,7 +95,9 @@ pub async fn add_package_signer(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct RemovePackageSignerParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub id: PackageId,
|
||||
#[arg(help = "help.arg.signer-id")]
|
||||
pub signer: Guid,
|
||||
}
|
||||
|
||||
@@ -114,7 +118,7 @@ pub async fn remove_package_signer(
|
||||
.is_some()
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("signer {signer} is not authorized to sign for {id}"),
|
||||
eyre!("{}", t!("registry.package.signer.not-authorized", signer = signer, id = id)),
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
}
|
||||
@@ -130,6 +134,7 @@ pub async fn remove_package_signer(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct ListPackageSignersParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub id: PackageId,
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ impl AcceptSigners {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("signer(s) not accepted"),
|
||||
eyre!("{}", t!("registry.signer.not-accepted")),
|
||||
ErrorKind::InvalidSignature,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ pub fn s9pk() -> ParentHandler<CliContext> {
|
||||
"pack",
|
||||
from_fn_async(super::v2::pack::pack)
|
||||
.no_display()
|
||||
.with_about("Package s9pk input files into valid s9pk"),
|
||||
.with_about("about.package-s9pk-input-files-into-valid-s9pk"),
|
||||
)
|
||||
.subcommand(
|
||||
"list-ingredients",
|
||||
@@ -45,26 +45,27 @@ pub fn s9pk() -> ParentHandler<CliContext> {
|
||||
println!();
|
||||
Ok(())
|
||||
})
|
||||
.with_about("List paths of package ingredients"),
|
||||
.with_about("about.list-paths-of-package-ingredients"),
|
||||
)
|
||||
.subcommand(
|
||||
"edit",
|
||||
edit().with_about("Commands to add an image to an s9pk or edit the manifest"),
|
||||
edit().with_about("about.commands-add-image-or-edit-manifest"),
|
||||
)
|
||||
.subcommand(
|
||||
"inspect",
|
||||
inspect().with_about("Commands to display file paths, file contents, or manifest"),
|
||||
inspect().with_about("about.commands-display-file-paths-contents-manifest"),
|
||||
)
|
||||
.subcommand(
|
||||
"convert",
|
||||
from_fn_async(convert)
|
||||
.no_display()
|
||||
.with_about("Convert s9pk from v1 to v2"),
|
||||
.with_about("about.convert-s9pk-v1-to-v2"),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
struct S9pkPath {
|
||||
#[arg(help = "help.arg.s9pk-file-path")]
|
||||
s9pk: PathBuf,
|
||||
}
|
||||
|
||||
@@ -76,14 +77,14 @@ fn edit() -> ParentHandler<CliContext, S9pkPath> {
|
||||
from_fn_async(add_image)
|
||||
.with_inherited(only_parent)
|
||||
.no_display()
|
||||
.with_about("Add image to s9pk"),
|
||||
.with_about("about.add-image-to-s9pk"),
|
||||
)
|
||||
.subcommand(
|
||||
"manifest",
|
||||
from_fn_async(edit_manifest)
|
||||
.with_inherited(only_parent)
|
||||
.with_display_serializable()
|
||||
.with_about("Edit s9pk manifest"),
|
||||
.with_about("about.edit-s9pk-manifest"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -95,26 +96,27 @@ fn inspect() -> ParentHandler<CliContext, S9pkPath> {
|
||||
from_fn_async(file_tree)
|
||||
.with_inherited(only_parent)
|
||||
.with_display_serializable()
|
||||
.with_about("Display list of paths"),
|
||||
.with_about("about.display-list-of-paths"),
|
||||
)
|
||||
.subcommand(
|
||||
"cat",
|
||||
from_fn_async(cat)
|
||||
.with_inherited(only_parent)
|
||||
.no_display()
|
||||
.with_about("Display file contents"),
|
||||
.with_about("about.display-file-contents"),
|
||||
)
|
||||
.subcommand(
|
||||
"manifest",
|
||||
from_fn_async(inspect_manifest)
|
||||
.with_inherited(only_parent)
|
||||
.with_display_serializable()
|
||||
.with_about("Display s9pk manifest"),
|
||||
.with_about("about.display-s9pk-manifest"),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
struct AddImageParams {
|
||||
#[arg(help = "help.arg.image-id")]
|
||||
id: ImageId,
|
||||
#[command(flatten)]
|
||||
config: ImageConfig,
|
||||
@@ -148,6 +150,7 @@ async fn add_image(
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
struct EditManifestParams {
|
||||
#[arg(help = "help.arg.db-apply-expr")]
|
||||
expression: String,
|
||||
}
|
||||
async fn edit_manifest(
|
||||
@@ -194,6 +197,7 @@ async fn file_tree(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
struct CatParams {
|
||||
#[arg(help = "help.arg.file-path")]
|
||||
file_path: PathBuf,
|
||||
}
|
||||
async fn cat(
|
||||
|
||||
@@ -16,5 +16,6 @@ pub const SIG_CONTEXT: &[u8] = b"s9pk";
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct VerifyParams {
|
||||
#[arg(help = "help.arg.s9pk-file-path")]
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use tokio::process::Command;
|
||||
|
||||
use crate::dependencies::{DepInfo, Dependencies};
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::{DeviceFilter, Manifest};
|
||||
use crate::s9pk::manifest::{DeviceFilter, LocaleString, Manifest};
|
||||
use crate::s9pk::merkle_archive::directory_contents::DirectoryContents;
|
||||
use crate::s9pk::merkle_archive::source::TmpSource;
|
||||
use crate::s9pk::merkle_archive::{Entry, MerkleArchive};
|
||||
@@ -198,7 +198,7 @@ impl TryFrom<ManifestV1> for Manifest {
|
||||
title: format!("{} (Legacy)", value.title).into(),
|
||||
version: version.into(),
|
||||
satisfies: BTreeSet::new(),
|
||||
release_notes: value.release_notes,
|
||||
release_notes: LocaleString::Translated(value.release_notes),
|
||||
can_migrate_from: VersionRange::any(),
|
||||
can_migrate_to: VersionRange::none(),
|
||||
license: value.license.into(),
|
||||
@@ -226,7 +226,7 @@ impl TryFrom<ManifestV1> for Manifest {
|
||||
(
|
||||
id,
|
||||
DepInfo {
|
||||
description: value.description,
|
||||
description: value.description.map(LocaleString::Translated),
|
||||
optional: !value.requirement.required(),
|
||||
metadata: None,
|
||||
},
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::Path;
|
||||
|
||||
use clap::builder::ValueParserFactory;
|
||||
use color_eyre::eyre::eyre;
|
||||
use exver::{Version, VersionRange};
|
||||
use imbl_value::InternedString;
|
||||
use imbl_value::{InOMap, InternedString};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
use url::Url;
|
||||
@@ -17,7 +18,7 @@ use crate::s9pk::merkle_archive::expected::{Expected, Filter};
|
||||
use crate::s9pk::v2::pack::ImageConfig;
|
||||
use crate::util::lshw::{LshwDevice, LshwDisplay, LshwProcessor};
|
||||
use crate::util::serde::Regex;
|
||||
use crate::util::{VersionString, mime};
|
||||
use crate::util::{FromStrParser, VersionString, mime};
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::{ImageId, VolumeId};
|
||||
|
||||
@@ -35,7 +36,7 @@ pub struct Manifest {
|
||||
pub title: InternedString,
|
||||
pub version: VersionString,
|
||||
pub satisfies: BTreeSet<VersionString>,
|
||||
pub release_notes: String,
|
||||
pub release_notes: LocaleString,
|
||||
#[ts(type = "string")]
|
||||
pub can_migrate_to: VersionRange,
|
||||
#[ts(type = "string")]
|
||||
@@ -190,6 +191,118 @@ impl HardwareRequirements {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, TS)]
|
||||
#[ts(type = "string | Record<string, string>")]
|
||||
pub enum LocaleString {
|
||||
Translated(String),
|
||||
LanguageMap(InOMap<InternedString, String>),
|
||||
}
|
||||
impl std::str::FromStr for LocaleString {
|
||||
type Err = std::convert::Infallible;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// Try JSON parse first (for maps or quoted strings)
|
||||
if let Ok(parsed) = serde_json::from_str::<LocaleString>(s) {
|
||||
return Ok(parsed);
|
||||
}
|
||||
// Fall back to plain string
|
||||
Ok(LocaleString::Translated(s.to_owned()))
|
||||
}
|
||||
}
|
||||
impl LocaleString {
|
||||
pub fn localize_for(&mut self, locale: &str) {
|
||||
if let Self::LanguageMap(map) = self {
|
||||
if let Some(translated) = map.remove(locale) {
|
||||
*self = Self::Translated(translated);
|
||||
return;
|
||||
}
|
||||
let prefix = locale.split_inclusive("_").next().unwrap();
|
||||
let mut first = None;
|
||||
for (lang, translated) in std::mem::take(map) {
|
||||
if lang.starts_with(prefix) {
|
||||
*self = Self::Translated(translated);
|
||||
return;
|
||||
}
|
||||
if first.is_none() {
|
||||
first = Some(translated);
|
||||
}
|
||||
}
|
||||
*self = Self::Translated(first.unwrap_or_default())
|
||||
}
|
||||
}
|
||||
pub fn localized_for(mut self, locale: &str) -> String {
|
||||
self.localize_for(locale);
|
||||
if let Self::Translated(s) = self {
|
||||
s
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
pub fn localize(&mut self) {
|
||||
self.localize_for(&*rust_i18n::locale());
|
||||
}
|
||||
pub fn localized(mut self) -> String {
|
||||
self.localized_for(&*rust_i18n::locale())
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for LocaleString {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct LocaleStringVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for LocaleStringVisitor {
|
||||
type Value = LocaleString;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a string or a map of language codes to strings")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(LocaleString::Translated(value.to_owned()))
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(LocaleString::Translated(value))
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
|
||||
where
|
||||
M: serde::de::MapAccess<'de>,
|
||||
{
|
||||
let language_map =
|
||||
InOMap::deserialize(serde::de::value::MapAccessDeserializer::new(map))?;
|
||||
Ok(LocaleString::LanguageMap(language_map))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(LocaleStringVisitor)
|
||||
}
|
||||
}
|
||||
impl Serialize for LocaleString {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
LocaleString::Translated(s) => serializer.serialize_str(s),
|
||||
LocaleString::LanguageMap(map) => map.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ValueParserFactory for LocaleString {
|
||||
type Parser = FromStrParser<Self>;
|
||||
fn value_parser() -> Self::Parser {
|
||||
FromStrParser::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
@@ -294,21 +407,32 @@ impl DeviceFilter {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS, PartialEq)]
|
||||
#[ts(export)]
|
||||
pub struct Description {
|
||||
pub short: String,
|
||||
pub long: String,
|
||||
pub short: LocaleString,
|
||||
pub long: LocaleString,
|
||||
}
|
||||
impl Description {
|
||||
pub fn localize_for(&mut self, locale: &str) {
|
||||
self.short.localize_for(locale);
|
||||
self.long.localize_for(locale);
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), Error> {
|
||||
if self.short.chars().skip(160).next().is_some() {
|
||||
if match &self.short {
|
||||
LocaleString::Translated(s) => s.len() > 160,
|
||||
LocaleString::LanguageMap(map) => map.values().any(|s| s.len() > 160),
|
||||
} {
|
||||
return Err(Error::new(
|
||||
eyre!("Short description must be 160 characters or less."),
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
));
|
||||
}
|
||||
if self.long.chars().skip(5000).next().is_some() {
|
||||
if match &self.short {
|
||||
LocaleString::Translated(s) => s.len() > 5000,
|
||||
LocaleString::LanguageMap(map) => map.values().any(|s| s.len() > 5000),
|
||||
} {
|
||||
return Err(Error::new(
|
||||
eyre!("Long description must be 5000 characters or less."),
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
@@ -318,13 +442,22 @@ impl Description {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct Alerts {
|
||||
pub install: Option<String>,
|
||||
pub uninstall: Option<String>,
|
||||
pub restore: Option<String>,
|
||||
pub start: Option<String>,
|
||||
pub stop: Option<String>,
|
||||
pub install: Option<LocaleString>,
|
||||
pub uninstall: Option<LocaleString>,
|
||||
pub restore: Option<LocaleString>,
|
||||
pub start: Option<LocaleString>,
|
||||
pub stop: Option<LocaleString>,
|
||||
}
|
||||
impl Alerts {
|
||||
pub fn localize_for(&mut self, locale: &str) {
|
||||
self.install.as_mut().map(|s| s.localize_for(locale));
|
||||
self.uninstall.as_mut().map(|s| s.localize_for(locale));
|
||||
self.restore.as_mut().map(|s| s.localize_for(locale));
|
||||
self.start.as_mut().map(|s| s.localize_for(locale));
|
||||
self.stop.as_mut().map(|s| s.localize_for(locale));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::prelude::*;
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::s9pk::S9pk;
|
||||
use crate::s9pk::git_hash::GitHash;
|
||||
use crate::s9pk::manifest::Manifest;
|
||||
use crate::s9pk::manifest::{LocaleString, Manifest};
|
||||
use crate::s9pk::merkle_archive::directory_contents::DirectoryContents;
|
||||
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||
@@ -155,20 +155,21 @@ impl From<PackSource> for DynFileSource {
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct PackParams {
|
||||
#[arg(help = "help.arg.input-path")]
|
||||
pub path: Option<PathBuf>,
|
||||
#[arg(short, long)]
|
||||
#[arg(short, long, help = "help.arg.output-path")]
|
||||
pub output: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.javascript-path")]
|
||||
pub javascript: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.icon-path")]
|
||||
pub icon: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.license-path")]
|
||||
pub license: Option<PathBuf>,
|
||||
#[arg(long, conflicts_with = "no-assets")]
|
||||
#[arg(long, conflicts_with = "no-assets", help = "help.arg.assets-path")]
|
||||
pub assets: Option<PathBuf>,
|
||||
#[arg(long, conflicts_with = "assets")]
|
||||
#[arg(long, conflicts_with = "assets", help = "help.arg.no-assets")]
|
||||
pub no_assets: bool,
|
||||
#[arg(long, help = "Architecture Mask")]
|
||||
#[arg(long, help = "help.arg.architecture-mask")]
|
||||
pub arch: Vec<InternedString>,
|
||||
}
|
||||
impl PackParams {
|
||||
@@ -280,19 +281,19 @@ pub struct ImageConfig {
|
||||
|
||||
#[derive(Parser)]
|
||||
struct CliImageConfig {
|
||||
#[arg(long, conflicts_with("docker-tag"))]
|
||||
#[arg(long, conflicts_with("docker-tag"), help = "help.arg.docker-build")]
|
||||
docker_build: bool,
|
||||
#[arg(long, requires("docker-build"))]
|
||||
#[arg(long, requires("docker-build"), help = "help.arg.dockerfile-path")]
|
||||
dockerfile: Option<PathBuf>,
|
||||
#[arg(long, requires("docker-build"))]
|
||||
#[arg(long, requires("docker-build"), help = "help.arg.workdir-path")]
|
||||
workdir: Option<PathBuf>,
|
||||
#[arg(long, conflicts_with_all(["dockerfile", "workdir"]))]
|
||||
#[arg(long, conflicts_with_all(["dockerfile", "workdir"]), help = "help.arg.docker-tag")]
|
||||
docker_tag: Option<String>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.architecture-mask")]
|
||||
arch: Vec<InternedString>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.emulate-missing-arch")]
|
||||
emulate_missing_as: Option<InternedString>,
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.nvidia-container")]
|
||||
nvidia_container: bool,
|
||||
}
|
||||
impl TryFrom<CliImageConfig> for ImageConfig {
|
||||
@@ -755,7 +756,7 @@ pub async fn pack(ctx: CliContext, params: PackParams) -> Result<(), Error> {
|
||||
}
|
||||
};
|
||||
Some((
|
||||
s9pk.as_manifest().title.clone(),
|
||||
LocaleString::Translated(s9pk.as_manifest().title.to_string()),
|
||||
s9pk.icon_data_url().await?,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ pub fn update_tasks(
|
||||
}
|
||||
}
|
||||
None => {
|
||||
tracing::error!("action request exists in an invalid state {:?}", v.task);
|
||||
tracing::error!("{}", t!("service.action.action-request-invalid-state", task = format!("{:?}", v.task)));
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -151,7 +151,7 @@ impl Handler<RunAction> for ServiceActor {
|
||||
.de()?;
|
||||
if matches!(&action.visibility, ActionVisibility::Disabled(_)) {
|
||||
return Err(Error::new(
|
||||
eyre!("action {action_id} is disabled"),
|
||||
eyre!("{}", t!("service.action.action-is-disabled", action_id = action_id)),
|
||||
ErrorKind::Action,
|
||||
));
|
||||
}
|
||||
@@ -162,7 +162,7 @@ impl Handler<RunAction> for ServiceActor {
|
||||
_ => false,
|
||||
} {
|
||||
return Err(Error::new(
|
||||
eyre!("service is not in allowed status for {action_id}"),
|
||||
eyre!("{}", t!("service.action.service-not-in-allowed-status", action_id = action_id)),
|
||||
ErrorKind::Action,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::service::effects::context::EffectContext;
|
||||
|
||||
#[derive(Debug, Default, Parser)]
|
||||
pub struct ContainerClientConfig {
|
||||
#[arg(long = "socket")]
|
||||
#[arg(long = "socket", help = "help.arg.socket-path")]
|
||||
pub socket: Option<PathBuf>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||
use rust_i18n::t;
|
||||
|
||||
use crate::action::{ActionInput, ActionResult, display_action_result};
|
||||
use crate::db::model::package::{
|
||||
@@ -80,7 +81,7 @@ pub async fn export_action(
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClearActionsParams {
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "help.arg.except-actions")]
|
||||
pub except: Vec<ActionId>,
|
||||
}
|
||||
|
||||
@@ -117,7 +118,9 @@ pub struct GetActionInputParams {
|
||||
#[arg(skip)]
|
||||
procedure_id: Guid,
|
||||
#[ts(optional)]
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
package_id: Option<PackageId>,
|
||||
#[arg(help = "help.arg.action-id")]
|
||||
action_id: ActionId,
|
||||
}
|
||||
async fn get_action_input(
|
||||
@@ -155,9 +158,12 @@ pub struct RunActionParams {
|
||||
#[arg(skip)]
|
||||
procedure_id: Guid,
|
||||
#[ts(optional)]
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
package_id: Option<PackageId>,
|
||||
#[arg(help = "help.arg.action-id")]
|
||||
action_id: ActionId,
|
||||
#[ts(type = "any")]
|
||||
#[arg(help = "help.arg.action-input")]
|
||||
input: Value,
|
||||
}
|
||||
async fn run_action(
|
||||
@@ -175,7 +181,7 @@ async fn run_action(
|
||||
|
||||
if package_id != &context.seed.id {
|
||||
return Err(Error::new(
|
||||
eyre!("calling actions on other packages is unsupported at this time"),
|
||||
eyre!("{}", t!("service.effects.action.calling-actions-on-other-packages-unsupported")),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
context
|
||||
@@ -220,7 +226,7 @@ async fn create_task(
|
||||
TaskCondition::InputNotMatches => {
|
||||
let Some(input) = task.input.as_ref() else {
|
||||
return Err(Error::new(
|
||||
eyre!("input-not-matches trigger requires input to be specified"),
|
||||
eyre!("{}", t!("service.effects.action.input-not-matches-requires-input")),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
};
|
||||
@@ -238,9 +244,7 @@ async fn create_task(
|
||||
else {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"action {} of {} has no input",
|
||||
task.action_id,
|
||||
task.package_id
|
||||
"{}", t!("service.effects.action.action-has-no-input", action_id = task.action_id, package_id = task.package_id)
|
||||
),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
@@ -286,9 +290,9 @@ async fn create_task(
|
||||
#[ts(type = "{ only: string[] } | { except: string[] }")]
|
||||
#[ts(export)]
|
||||
pub struct ClearTasksParams {
|
||||
#[arg(long, conflicts_with = "except")]
|
||||
#[arg(long, conflicts_with = "except", help = "help.arg.only-tasks")]
|
||||
pub only: Option<Vec<ReplayId>>,
|
||||
#[arg(long, conflicts_with = "only")]
|
||||
#[arg(long, conflicts_with = "only", help = "help.arg.except-tasks")]
|
||||
pub except: Option<Vec<ReplayId>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -319,9 +319,9 @@ impl CallbackHandlers {
|
||||
#[ts(type = "{ only: number[] } | { except: number[] }")]
|
||||
#[ts(export)]
|
||||
pub struct ClearCallbacksParams {
|
||||
#[arg(long, conflicts_with = "except")]
|
||||
#[arg(long, conflicts_with = "except", help = "help.arg.only-callbacks")]
|
||||
pub only: Option<Vec<CallbackId>>,
|
||||
#[arg(long, conflicts_with = "only")]
|
||||
#[arg(long, conflicts_with = "only", help = "help.arg.except-callbacks")]
|
||||
pub except: Option<Vec<CallbackId>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::str::FromStr;
|
||||
|
||||
use clap::builder::ValueParserFactory;
|
||||
use exver::VersionRange;
|
||||
use imbl_value::InternedString;
|
||||
use rust_i18n::t;
|
||||
|
||||
use crate::db::model::package::{
|
||||
CurrentDependencies, CurrentDependencyInfo, CurrentDependencyKind, ManifestPreference,
|
||||
@@ -148,13 +148,25 @@ impl FromStr for DependencyRequirement {
|
||||
.map(|id| id.parse().map_err(Error::from))
|
||||
.collect(),
|
||||
Some((kind, _)) => Err(Error::new(
|
||||
eyre!("unknown dependency kind {kind}"),
|
||||
eyre!(
|
||||
"{}",
|
||||
t!(
|
||||
"service.effects.dependency.unknown-dependency-kind",
|
||||
kind = kind
|
||||
)
|
||||
),
|
||||
ErrorKind::InvalidRequest,
|
||||
)),
|
||||
None => match rest {
|
||||
"r" | "running" => Ok(BTreeSet::new()),
|
||||
kind => Err(Error::new(
|
||||
eyre!("unknown dependency kind {kind}"),
|
||||
eyre!(
|
||||
"{}",
|
||||
t!(
|
||||
"service.effects.dependency.unknown-dependency-kind",
|
||||
kind = kind
|
||||
)
|
||||
),
|
||||
ErrorKind::InvalidRequest,
|
||||
)),
|
||||
},
|
||||
@@ -293,8 +305,7 @@ pub struct CheckDependenciesParam {
|
||||
#[ts(export)]
|
||||
pub struct CheckDependenciesResult {
|
||||
package_id: PackageId,
|
||||
#[ts(type = "string | null")]
|
||||
title: Option<InternedString>,
|
||||
title: Option<String>,
|
||||
installed_version: Option<VersionString>,
|
||||
satisfies: BTreeSet<VersionString>,
|
||||
is_running: bool,
|
||||
@@ -334,7 +345,7 @@ pub async fn check_dependencies(
|
||||
.collect();
|
||||
results.push(CheckDependenciesResult {
|
||||
package_id,
|
||||
title,
|
||||
title: title.map(|t| t.localized()),
|
||||
installed_version: None,
|
||||
satisfies: BTreeSet::new(),
|
||||
is_running: false,
|
||||
@@ -360,7 +371,7 @@ pub async fn check_dependencies(
|
||||
.collect();
|
||||
results.push(CheckDependenciesResult {
|
||||
package_id,
|
||||
title,
|
||||
title: title.map(|t| t.localized()),
|
||||
installed_version,
|
||||
satisfies,
|
||||
is_running,
|
||||
|
||||
@@ -11,6 +11,6 @@ pub(super) use crate::service::effects::context::EffectContext;
|
||||
#[ts(export)]
|
||||
pub struct EventId {
|
||||
#[serde(default)]
|
||||
#[arg(default_value_t, long)]
|
||||
#[arg(default_value_t, long, help = "help.arg.event-id")]
|
||||
pub event_id: Guid,
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user