mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 20:43:41 +00:00
Compare commits
411 Commits
next/minor
...
feat/rando
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04196df976 | ||
|
|
30b1654666 | ||
|
|
f41710c892 | ||
|
|
df3f79f282 | ||
|
|
f8df692865 | ||
|
|
0c6d3b188d | ||
|
|
e7a38863ab | ||
|
|
720e0fcdab | ||
|
|
bf8ff84522 | ||
|
|
5a9510238e | ||
|
|
7b3c74179b | ||
|
|
cd70fa4c32 | ||
|
|
83133ced6a | ||
|
|
6c5179a179 | ||
|
|
e33ab39b85 | ||
|
|
9567bcec1b | ||
|
|
550b16dc0b | ||
|
|
5d8331b7f7 | ||
|
|
e35b643e51 | ||
|
|
bc6a92677b | ||
|
|
f52072e6ec | ||
|
|
9c43c43a46 | ||
|
|
0430e0f930 | ||
|
|
b945243d1a | ||
|
|
d8484a8b26 | ||
|
|
3c27499795 | ||
|
|
7c772e873d | ||
|
|
db2fab245e | ||
|
|
a9c9917f1a | ||
|
|
23e2e9e9cc | ||
|
|
2369e92460 | ||
|
|
a53b15f2a3 | ||
|
|
72eb8b1eb6 | ||
|
|
4db54f3b83 | ||
|
|
24eb27f005 | ||
|
|
009d76ea35 | ||
|
|
6e8a425eb1 | ||
|
|
66188d791b | ||
|
|
015ff02d71 | ||
|
|
10bfaf5415 | ||
|
|
e3e0b85e0c | ||
|
|
ad0632892e | ||
|
|
f26791ba39 | ||
|
|
2fbaaebf44 | ||
|
|
edb916338c | ||
|
|
f7e947d37d | ||
|
|
a9e3d1ed75 | ||
|
|
ce97827c42 | ||
|
|
3efec07338 | ||
|
|
68f401bfa3 | ||
|
|
1ea525feaa | ||
|
|
57c4a7527e | ||
|
|
5aa9c045e1 | ||
|
|
6f1900f3bb | ||
|
|
bc62de795e | ||
|
|
c62ca4b183 | ||
|
|
876e5bc683 | ||
|
|
b99f3b73cd | ||
|
|
7eecf29449 | ||
|
|
1d331d7810 | ||
|
|
68414678d8 | ||
|
|
2f6b9dac26 | ||
|
|
d1812d875b | ||
|
|
723dea100f | ||
|
|
c4419ed31f | ||
|
|
754ab86e51 | ||
|
|
04dab532cd | ||
|
|
add01ebc68 | ||
|
|
1cc9a1a30b | ||
|
|
92a1de7500 | ||
|
|
a6fedcff80 | ||
|
|
55eb999305 | ||
|
|
377b7b12ce | ||
|
|
ba2906a42e | ||
|
|
ee27f14be0 | ||
|
|
46c8be63a7 | ||
|
|
7ba66c419a | ||
|
|
340775a593 | ||
|
|
35d2ec8a44 | ||
|
|
2983b9950f | ||
|
|
dbf08a6cf8 | ||
|
|
28f31be36f | ||
|
|
3ec4db0225 | ||
|
|
f5688e077a | ||
|
|
2464d255d5 | ||
|
|
586d950b8c | ||
|
|
e7469388cc | ||
|
|
ab6ca8e16a | ||
|
|
02413a4fac | ||
|
|
05b8dd9ad8 | ||
|
|
29c9419a6e | ||
|
|
90e61989a4 | ||
|
|
b1f9f90fec | ||
|
|
b40849f672 | ||
|
|
44560c8da8 | ||
|
|
46fd01c264 | ||
|
|
100695c262 | ||
|
|
54b5a4ae55 | ||
|
|
ffb252962b | ||
|
|
ae31270e63 | ||
|
|
9b2b54d585 | ||
|
|
e1ccc583a3 | ||
|
|
7750e33f82 | ||
|
|
d2c4741f0b | ||
|
|
c79c4f6bde | ||
|
|
3849d0d1a9 | ||
|
|
8bd71ccd5e | ||
|
|
b731f7fb64 | ||
|
|
cd554f77f3 | ||
|
|
8c977c51ca | ||
|
|
a3252f9671 | ||
|
|
9bc945f76f | ||
|
|
f6b4dfffb6 | ||
|
|
68955c29cb | ||
|
|
97e4d036dc | ||
|
|
0f49f54c29 | ||
|
|
828e13adbb | ||
|
|
e6f0067728 | ||
|
|
5c473eb9cc | ||
|
|
2adf34fbaf | ||
|
|
05dd760388 | ||
|
|
2cf4864078 | ||
|
|
df4c92672f | ||
|
|
5b173315f9 | ||
|
|
c85ea7d8fa | ||
|
|
113154702f | ||
|
|
33ae46f76a | ||
|
|
27272680a2 | ||
|
|
b1621f6b34 | ||
|
|
2c65033c0a | ||
|
|
dcfbaa9243 | ||
|
|
accef65ede | ||
|
|
50755d8ba3 | ||
|
|
47b6509f70 | ||
|
|
89f3fdc05f | ||
|
|
03f8b73627 | ||
|
|
2e6e9635c3 | ||
|
|
6a312e3fdd | ||
|
|
0e8961efe3 | ||
|
|
fc2be42418 | ||
|
|
ab4336cfd7 | ||
|
|
63a29d3a4a | ||
|
|
31856d9895 | ||
|
|
f51dcf23d6 | ||
|
|
1883c9666e | ||
|
|
4b4cf76641 | ||
|
|
495bbecc01 | ||
|
|
e6af7e9885 | ||
|
|
182b8c2283 | ||
|
|
5318cccc5f | ||
|
|
99739575d4 | ||
|
|
6f9069a4fb | ||
|
|
a18ab7f1e9 | ||
|
|
be0371fb11 | ||
|
|
fa3329abf2 | ||
|
|
e830fade06 | ||
|
|
ac392dcb96 | ||
|
|
00a5fdf491 | ||
|
|
7fff9579c0 | ||
|
|
1b006599cf | ||
|
|
ce2842d365 | ||
|
|
7d1096dbd8 | ||
|
|
95722802dc | ||
|
|
95cad7bdd9 | ||
|
|
b2b98643d8 | ||
|
|
bb8109f67d | ||
|
|
e6f02bf8f7 | ||
|
|
57e75e3614 | ||
|
|
89ab67e067 | ||
|
|
115c599fd8 | ||
|
|
3121c08ee8 | ||
|
|
a5bac39196 | ||
|
|
9f640b24b3 | ||
|
|
75e7556bfa | ||
|
|
beb3a9f60a | ||
|
|
dfda2f7d5d | ||
|
|
a77ebd3b55 | ||
|
|
00114287e5 | ||
|
|
a9569d0ed9 | ||
|
|
88d9388be2 | ||
|
|
93c72ecea5 | ||
|
|
b5b0ac50bd | ||
|
|
4d2afdb1a9 | ||
|
|
39a177bd70 | ||
|
|
34fb6ac837 | ||
|
|
f868a454d9 | ||
|
|
751ceab04e | ||
|
|
b6c48d0f98 | ||
|
|
097d77f7b3 | ||
|
|
7a0586684b | ||
|
|
8f34d1c555 | ||
|
|
5270a6781f | ||
|
|
fa93e195cb | ||
|
|
befa9eb16d | ||
|
|
a278c630bb | ||
|
|
76eb0f1775 | ||
|
|
0abe08f243 | ||
|
|
015131f198 | ||
|
|
a730543c76 | ||
|
|
b43ad93c54 | ||
|
|
7850681ce1 | ||
|
|
846189b15b | ||
|
|
657aac0d68 | ||
|
|
81932c8cff | ||
|
|
20f6a5e797 | ||
|
|
949f1c648a | ||
|
|
d159dde2ca | ||
|
|
729a510c5b | ||
|
|
fffc7f4098 | ||
|
|
c7a2e7ada1 | ||
|
|
a2b1968d6e | ||
|
|
398eb13a7f | ||
|
|
956c8a8e03 | ||
|
|
6aba166c82 | ||
|
|
fd7c7ea6b7 | ||
|
|
d85e621bb3 | ||
|
|
25801f374c | ||
|
|
8fd2d0b35c | ||
|
|
dd196c0e11 | ||
|
|
6e2cf8bb3f | ||
|
|
b8eb8a90a5 | ||
|
|
bd4d89fc21 | ||
|
|
6234391229 | ||
|
|
206c185a3b | ||
|
|
7689cbbe0d | ||
|
|
b57a9351b3 | ||
|
|
f0ae9e21ae | ||
|
|
9510c92288 | ||
|
|
755f3f05d8 | ||
|
|
5d8114b475 | ||
|
|
85b39ecf99 | ||
|
|
230838c22b | ||
|
|
a7bfcdcb01 | ||
|
|
47ff630c55 | ||
|
|
70dc53bda7 | ||
|
|
7e1b433c17 | ||
|
|
ec878defab | ||
|
|
1786b70e14 | ||
|
|
7f525fa7dc | ||
|
|
8b89e03999 | ||
|
|
2693b9a42d | ||
|
|
6b336b7b2f | ||
|
|
3c0e77241d | ||
|
|
87461c7f72 | ||
|
|
a67f2b4976 | ||
|
|
8594781780 | ||
|
|
b2c8907635 | ||
|
|
05f4df1a30 | ||
|
|
35fe06a892 | ||
|
|
cd933ce6e4 | ||
|
|
0b93988450 | ||
|
|
12a323f691 | ||
|
|
9c4c211233 | ||
|
|
74ba68ff2c | ||
|
|
7273b37c16 | ||
|
|
0d4ebffc0e | ||
|
|
352b2fb4e7 | ||
|
|
6e6ef57303 | ||
|
|
b80e41503f | ||
|
|
7f28fc17ca | ||
|
|
70d4a0c022 | ||
|
|
8cfd994170 | ||
|
|
641e829e3f | ||
|
|
d202cb731d | ||
|
|
4ab7300376 | ||
|
|
18cc5e0ee8 | ||
|
|
af0cda5dbf | ||
|
|
a730a3719b | ||
|
|
3b669193f6 | ||
|
|
22cd2e3337 | ||
|
|
7e9d453a2c | ||
|
|
a4338b0d03 | ||
|
|
2021431e2f | ||
|
|
5e6a7e134f | ||
|
|
f4fadd366e | ||
|
|
a5b1b4e103 | ||
|
|
7b41b295b7 | ||
|
|
69d5f521a5 | ||
|
|
c0a55142b5 | ||
|
|
513fb3428a | ||
|
|
9a0ae549f6 | ||
|
|
4410d7f195 | ||
|
|
92aa70182d | ||
|
|
90f5864f1e | ||
|
|
e47f126bd5 | ||
|
|
ea6f70e3c5 | ||
|
|
0469aab433 | ||
|
|
ad13b5eb4e | ||
|
|
7324a4973f | ||
|
|
8bc93d23b2 | ||
|
|
c708b685e1 | ||
|
|
cbde91744f | ||
|
|
147e24204b | ||
|
|
13c50e428f | ||
|
|
8403ccd3da | ||
|
|
e92bd61545 | ||
|
|
8215e0221a | ||
|
|
4b44d6fb83 | ||
|
|
0ae3e83ce4 | ||
|
|
f4b573379d | ||
|
|
862ca375ee | ||
|
|
530de6741b | ||
|
|
35c1ff9014 | ||
|
|
3f4caed922 | ||
|
|
09303ab2fb | ||
|
|
df1ac8e1e2 | ||
|
|
7a55c91349 | ||
|
|
c491dfdd3a | ||
|
|
d9cc21f761 | ||
|
|
06207145af | ||
|
|
b195e3435f | ||
|
|
34b4577c0b | ||
|
|
8034e5bbcb | ||
|
|
df7a30bd14 | ||
|
|
d9dfacaaf4 | ||
|
|
d43767b945 | ||
|
|
cb36754c46 | ||
|
|
7e18aafe20 | ||
|
|
f7b079b1b4 | ||
|
|
72ffedead7 | ||
|
|
cf3a501562 | ||
|
|
7becdc3034 | ||
|
|
f0d599781d | ||
|
|
3386105048 | ||
|
|
3b8fb70db1 | ||
|
|
c3ae146580 | ||
|
|
0d079f0d89 | ||
|
|
9f5a90ee9c | ||
|
|
a5307fd8cc | ||
|
|
180589144a | ||
|
|
d9c1867bd7 | ||
|
|
da37d649ec | ||
|
|
4204b4af90 | ||
|
|
941650f668 | ||
|
|
9c0c6c1bd6 | ||
|
|
bd0ddafcd0 | ||
|
|
19f5e92a74 | ||
|
|
3202c38061 | ||
|
|
e35a8c942b | ||
|
|
31811eb91e | ||
|
|
b9316a4112 | ||
|
|
b7abd878ac | ||
|
|
38c2c47789 | ||
|
|
c03778ec8b | ||
|
|
29b0850a94 | ||
|
|
712fde46eb | ||
|
|
c2e79ca5a7 | ||
|
|
c3a52b3989 | ||
|
|
7213d82f1b | ||
|
|
5bcad69cf7 | ||
|
|
c9a487fa4d | ||
|
|
3804a46f3b | ||
|
|
52c0bb5302 | ||
|
|
8aa19e6420 | ||
|
|
4d1c7a3884 | ||
|
|
25f2c057b7 | ||
|
|
010be05920 | ||
|
|
4c465850a2 | ||
|
|
8313dfaeb9 | ||
|
|
873f2b2814 | ||
|
|
e53c90f8f0 | ||
|
|
9499ea8ca9 | ||
|
|
f6c09109ba | ||
|
|
273b5768c4 | ||
|
|
ee13cf7dd9 | ||
|
|
fecbae761e | ||
|
|
e0ee89bdd9 | ||
|
|
833c1f22a3 | ||
|
|
6fed6c8d30 | ||
|
|
94cdaf5314 | ||
|
|
f83ae27352 | ||
|
|
6badf047c3 | ||
|
|
47de9ad15f | ||
|
|
09b91cc663 | ||
|
|
ded16549f7 | ||
|
|
c89e47577b | ||
|
|
bb50beb7ab | ||
|
|
e4cd4d64d7 | ||
|
|
5675fc51a0 | ||
|
|
c7438c4aff | ||
|
|
4a6a3da36c | ||
|
|
a657c332b1 | ||
|
|
cc9cd3fc14 | ||
|
|
234258a077 | ||
|
|
13cda80ee6 | ||
|
|
f6e142baf5 | ||
|
|
ddf1f9bcd5 | ||
|
|
aa950669f6 | ||
|
|
dacd5d3e6b | ||
|
|
e76ccba2f7 | ||
|
|
3933819d53 | ||
|
|
99019c2b1f | ||
|
|
4bf5eb398b | ||
|
|
dbfbac62c0 | ||
|
|
7685293da4 | ||
|
|
ee9c328606 | ||
|
|
cb7790ccba | ||
|
|
6556fcc531 | ||
|
|
178391e7b2 | ||
|
|
18922a1c6d | ||
|
|
5e9e26fa67 | ||
|
|
f5430f9151 | ||
|
|
4dfdf2f92f | ||
|
|
e4d283cc99 | ||
|
|
8ee64d22b3 | ||
|
|
10e3e80042 | ||
|
|
f77a208e2c | ||
|
|
9366dbb96e | ||
|
|
550b17552b | ||
|
|
bec307d0e9 | ||
|
|
93c751f6eb |
118
.github/workflows/start-cli.yaml
vendored
Normal file
118
.github/workflows/start-cli.yaml
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
name: start-cli
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
environment:
|
||||||
|
type: choice
|
||||||
|
description: Environment
|
||||||
|
options:
|
||||||
|
- NONE
|
||||||
|
- dev
|
||||||
|
- unstable
|
||||||
|
- dev-unstable
|
||||||
|
runner:
|
||||||
|
type: choice
|
||||||
|
description: Runner
|
||||||
|
options:
|
||||||
|
- standard
|
||||||
|
- fast
|
||||||
|
arch:
|
||||||
|
type: choice
|
||||||
|
description: Architecture
|
||||||
|
options:
|
||||||
|
- ALL
|
||||||
|
- x86_64
|
||||||
|
- x86_64-apple
|
||||||
|
- aarch64
|
||||||
|
- aarch64-apple
|
||||||
|
- riscv64
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next/*
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next/*
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODEJS_VERSION: "24.11.0"
|
||||||
|
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
compile:
|
||||||
|
name: Build Debian Package
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
triple: >-
|
||||||
|
${{
|
||||||
|
fromJson('{
|
||||||
|
"x86_64": ["x86_64-unknown-linux-musl"],
|
||||||
|
"x86_64-apple": ["x86_64-apple-darwin"],
|
||||||
|
"aarch64": ["aarch64-unknown-linux-musl"],
|
||||||
|
"x86_64-apple": ["aarch64-apple-darwin"],
|
||||||
|
"riscv64": ["riscv64gc-unknown-linux-musl"],
|
||||||
|
"ALL": ["x86_64-unknown-linux-musl", "x86_64-apple-darwin", "aarch64-unknown-linux-musl", "aarch64-apple-darwin", "riscv64gc-unknown-linux-musl"]
|
||||||
|
}')[github.event.inputs.platform || 'ALL']
|
||||||
|
}}
|
||||||
|
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||||
|
steps:
|
||||||
|
- name: Cleaning up unnecessary files
|
||||||
|
run: |
|
||||||
|
sudo apt-get remove --purge -y mono-* \
|
||||||
|
ghc* cabal-install* \
|
||||||
|
dotnet* \
|
||||||
|
php* \
|
||||||
|
ruby* \
|
||||||
|
mysql-* \
|
||||||
|
postgresql-* \
|
||||||
|
azure-cli \
|
||||||
|
powershell \
|
||||||
|
google-cloud-sdk \
|
||||||
|
msodbcsql* mssql-tools* \
|
||||||
|
imagemagick* \
|
||||||
|
libgl1-mesa-dri \
|
||||||
|
google-chrome-stable \
|
||||||
|
firefox
|
||||||
|
sudo apt-get autoremove -y
|
||||||
|
sudo apt-get clean
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
sudo mount -t tmpfs tmpfs .
|
||||||
|
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODEJS_VERSION }}
|
||||||
|
|
||||||
|
- name: Set up docker QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Configure sccache
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
|
||||||
|
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||||
|
|
||||||
|
- name: Make
|
||||||
|
run: TARGET=${{ matrix.triple }} make cli
|
||||||
|
env:
|
||||||
|
PLATFORM: ${{ matrix.arch }}
|
||||||
|
SCCACHE_GHA_ENABLED: on
|
||||||
|
SCCACHE_GHA_VERSION: 0
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: start-cli_${{ matrix.triple }}
|
||||||
|
path: core/target/${{ matrix.triple }}/release/start-cli
|
||||||
203
.github/workflows/start-registry.yaml
vendored
Normal file
203
.github/workflows/start-registry.yaml
vendored
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
name: Start-Registry
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
environment:
|
||||||
|
type: choice
|
||||||
|
description: Environment
|
||||||
|
options:
|
||||||
|
- NONE
|
||||||
|
- dev
|
||||||
|
- unstable
|
||||||
|
- dev-unstable
|
||||||
|
runner:
|
||||||
|
type: choice
|
||||||
|
description: Runner
|
||||||
|
options:
|
||||||
|
- standard
|
||||||
|
- fast
|
||||||
|
arch:
|
||||||
|
type: choice
|
||||||
|
description: Architecture
|
||||||
|
options:
|
||||||
|
- ALL
|
||||||
|
- x86_64
|
||||||
|
- aarch64
|
||||||
|
- riscv64
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next/*
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next/*
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODEJS_VERSION: "24.11.0"
|
||||||
|
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
compile:
|
||||||
|
name: Build Debian Package
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
arch: >-
|
||||||
|
${{
|
||||||
|
fromJson('{
|
||||||
|
"x86_64": ["x86_64"],
|
||||||
|
"aarch64": ["aarch64"],
|
||||||
|
"riscv64": ["riscv64"],
|
||||||
|
"ALL": ["x86_64", "aarch64", "riscv64"]
|
||||||
|
}')[github.event.inputs.platform || 'ALL']
|
||||||
|
}}
|
||||||
|
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||||
|
steps:
|
||||||
|
- name: Cleaning up unnecessary files
|
||||||
|
run: |
|
||||||
|
sudo apt-get remove --purge -y mono-* \
|
||||||
|
ghc* cabal-install* \
|
||||||
|
dotnet* \
|
||||||
|
php* \
|
||||||
|
ruby* \
|
||||||
|
mysql-* \
|
||||||
|
postgresql-* \
|
||||||
|
azure-cli \
|
||||||
|
powershell \
|
||||||
|
google-cloud-sdk \
|
||||||
|
msodbcsql* mssql-tools* \
|
||||||
|
imagemagick* \
|
||||||
|
libgl1-mesa-dri \
|
||||||
|
google-chrome-stable \
|
||||||
|
firefox
|
||||||
|
sudo apt-get autoremove -y
|
||||||
|
sudo apt-get clean
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
sudo mount -t tmpfs tmpfs .
|
||||||
|
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODEJS_VERSION }}
|
||||||
|
|
||||||
|
- name: Set up docker QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Configure sccache
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
|
||||||
|
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||||
|
|
||||||
|
- name: Make
|
||||||
|
run: make registry-deb
|
||||||
|
env:
|
||||||
|
PLATFORM: ${{ matrix.arch }}
|
||||||
|
SCCACHE_GHA_ENABLED: on
|
||||||
|
SCCACHE_GHA_VERSION: 0
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: start-registry_${{ matrix.arch }}.deb
|
||||||
|
path: results/start-registry-*_${{ matrix.arch }}.deb
|
||||||
|
|
||||||
|
create-image:
|
||||||
|
name: Create Docker Image
|
||||||
|
needs: [compile]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||||
|
steps:
|
||||||
|
- name: Cleaning up unnecessary files
|
||||||
|
run: |
|
||||||
|
sudo apt-get remove --purge -y google-chrome-stable firefox mono-devel
|
||||||
|
sudo apt-get autoremove -y
|
||||||
|
sudo apt-get clean
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
sudo mount -t tmpfs tmpfs .
|
||||||
|
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||||
|
|
||||||
|
- name: Set up docker QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: "Login to GitHub Container Registry"
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{github.actor}}
|
||||||
|
password: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/Start9Labs/startos-registry
|
||||||
|
tags: |
|
||||||
|
type=raw,value=${{ github.ref_name }}
|
||||||
|
|
||||||
|
- name: Download debian package
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: start-registry_*.deb
|
||||||
|
|
||||||
|
- name: Map matrix.arch to docker platform
|
||||||
|
run: |
|
||||||
|
platforms=""
|
||||||
|
for deb in *.deb; do
|
||||||
|
filename=$(basename "$deb" .deb)
|
||||||
|
arch="${filename#*_}"
|
||||||
|
case "$arch" in
|
||||||
|
x86_64)
|
||||||
|
platform="linux/amd64"
|
||||||
|
;;
|
||||||
|
aarch64)
|
||||||
|
platform="linux/arm64"
|
||||||
|
;;
|
||||||
|
riscv64)
|
||||||
|
platform="linux/riscv64"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown architecture: $arch" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [ -z "$platforms" ]; then
|
||||||
|
platforms="$platform"
|
||||||
|
else
|
||||||
|
platforms="$platforms,$platform"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "DOCKER_PLATFORM=$platforms" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
cat | docker buildx build --platform "$DOCKER_PLATFORM" --push -t ${{ steps.meta.outputs.tags }} -f - . << 'EOF'
|
||||||
|
FROM debian:trixie
|
||||||
|
|
||||||
|
ADD *.deb .
|
||||||
|
|
||||||
|
RUN apt-get install -y ./*_$(uname -m).deb && rm *.deb
|
||||||
|
|
||||||
|
VOLUME /var/lib/startos
|
||||||
|
|
||||||
|
ENV RUST_LOG=startos=debug
|
||||||
|
|
||||||
|
ENTRYPOINT ["start-registryd"]
|
||||||
|
|
||||||
|
EOF
|
||||||
114
.github/workflows/start-tunnel.yaml
vendored
Normal file
114
.github/workflows/start-tunnel.yaml
vendored
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
name: Start-Tunnel
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
environment:
|
||||||
|
type: choice
|
||||||
|
description: Environment
|
||||||
|
options:
|
||||||
|
- NONE
|
||||||
|
- dev
|
||||||
|
- unstable
|
||||||
|
- dev-unstable
|
||||||
|
runner:
|
||||||
|
type: choice
|
||||||
|
description: Runner
|
||||||
|
options:
|
||||||
|
- standard
|
||||||
|
- fast
|
||||||
|
arch:
|
||||||
|
type: choice
|
||||||
|
description: Architecture
|
||||||
|
options:
|
||||||
|
- ALL
|
||||||
|
- x86_64
|
||||||
|
- aarch64
|
||||||
|
- riscv64
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next/*
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next/*
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODEJS_VERSION: "24.11.0"
|
||||||
|
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
compile:
|
||||||
|
name: Build Debian Package
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
arch: >-
|
||||||
|
${{
|
||||||
|
fromJson('{
|
||||||
|
"x86_64": ["x86_64"],
|
||||||
|
"aarch64": ["aarch64"],
|
||||||
|
"riscv64": ["riscv64"],
|
||||||
|
"ALL": ["x86_64", "aarch64", "riscv64"]
|
||||||
|
}')[github.event.inputs.platform || 'ALL']
|
||||||
|
}}
|
||||||
|
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||||
|
steps:
|
||||||
|
- name: Cleaning up unnecessary files
|
||||||
|
run: |
|
||||||
|
sudo apt-get remove --purge -y mono-* \
|
||||||
|
ghc* cabal-install* \
|
||||||
|
dotnet* \
|
||||||
|
php* \
|
||||||
|
ruby* \
|
||||||
|
mysql-* \
|
||||||
|
postgresql-* \
|
||||||
|
azure-cli \
|
||||||
|
powershell \
|
||||||
|
google-cloud-sdk \
|
||||||
|
msodbcsql* mssql-tools* \
|
||||||
|
imagemagick* \
|
||||||
|
libgl1-mesa-dri \
|
||||||
|
google-chrome-stable \
|
||||||
|
firefox
|
||||||
|
sudo apt-get autoremove -y
|
||||||
|
sudo apt-get clean
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
sudo mount -t tmpfs tmpfs .
|
||||||
|
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODEJS_VERSION }}
|
||||||
|
|
||||||
|
- name: Set up docker QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Configure sccache
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
|
||||||
|
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||||
|
|
||||||
|
- name: Make
|
||||||
|
run: make tunnel-deb
|
||||||
|
env:
|
||||||
|
PLATFORM: ${{ matrix.arch }}
|
||||||
|
SCCACHE_GHA_ENABLED: on
|
||||||
|
SCCACHE_GHA_VERSION: 0
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: start-tunnel_${{ matrix.arch }}.deb
|
||||||
|
path: results/start-tunnel-*_${{ matrix.arch }}.deb
|
||||||
89
.github/workflows/startos-iso.yaml
vendored
89
.github/workflows/startos-iso.yaml
vendored
@@ -28,6 +28,7 @@ on:
|
|||||||
- aarch64
|
- aarch64
|
||||||
- aarch64-nonfree
|
- aarch64-nonfree
|
||||||
- raspberrypi
|
- raspberrypi
|
||||||
|
- riscv64
|
||||||
deploy:
|
deploy:
|
||||||
type: choice
|
type: choice
|
||||||
description: Deploy
|
description: Deploy
|
||||||
@@ -45,7 +46,7 @@ on:
|
|||||||
- next/*
|
- next/*
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODEJS_VERSION: "20.16.0"
|
NODEJS_VERSION: "24.11.0"
|
||||||
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -62,11 +63,48 @@ jobs:
|
|||||||
"aarch64": ["aarch64"],
|
"aarch64": ["aarch64"],
|
||||||
"aarch64-nonfree": ["aarch64"],
|
"aarch64-nonfree": ["aarch64"],
|
||||||
"raspberrypi": ["aarch64"],
|
"raspberrypi": ["aarch64"],
|
||||||
"ALL": ["x86_64", "aarch64"]
|
"riscv64": ["riscv64"],
|
||||||
|
"ALL": ["x86_64", "aarch64", "riscv64"]
|
||||||
}')[github.event.inputs.platform || 'ALL']
|
}')[github.event.inputs.platform || 'ALL']
|
||||||
}}
|
}}
|
||||||
runs-on: ${{ fromJson('["ubuntu-22.04", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
runs-on: >-
|
||||||
|
${{
|
||||||
|
fromJson(
|
||||||
|
format(
|
||||||
|
'["{0}", "{1}"]',
|
||||||
|
fromJson('{
|
||||||
|
"x86_64": "ubuntu-latest",
|
||||||
|
"aarch64": "ubuntu-24.04-arm",
|
||||||
|
"riscv64": "ubuntu-latest"
|
||||||
|
}')[matrix.arch],
|
||||||
|
fromJson('{
|
||||||
|
"x86_64": "buildjet-32vcpu-ubuntu-2204",
|
||||||
|
"aarch64": "buildjet-32vcpu-ubuntu-2204-arm",
|
||||||
|
"riscv64": "buildjet-32vcpu-ubuntu-2204"
|
||||||
|
}')[matrix.arch]
|
||||||
|
)
|
||||||
|
)[github.event.inputs.runner == 'fast']
|
||||||
|
}}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Cleaning up unnecessary files
|
||||||
|
run: |
|
||||||
|
sudo apt-get remove --purge -y azure-cli || true
|
||||||
|
sudo apt-get remove --purge -y firefox || true
|
||||||
|
sudo apt-get remove --purge -y ghc-* || true
|
||||||
|
sudo apt-get remove --purge -y google-cloud-sdk || true
|
||||||
|
sudo apt-get remove --purge -y google-chrome-stable || true
|
||||||
|
sudo apt-get remove --purge -y powershell || true
|
||||||
|
sudo apt-get remove --purge -y php* || true
|
||||||
|
sudo apt-get remove --purge -y ruby* || true
|
||||||
|
sudo apt-get remove --purge -y mono-* || true
|
||||||
|
sudo apt-get autoremove -y
|
||||||
|
sudo apt-get clean
|
||||||
|
sudo rm -rf /usr/lib/jvm # All JDKs
|
||||||
|
sudo rm -rf /usr/local/.ghcup # Haskell toolchain
|
||||||
|
sudo rm -rf /usr/local/lib/android # Android SDK/NDK, emulator
|
||||||
|
sudo rm -rf /usr/share/dotnet # .NET SDKs
|
||||||
|
sudo rm -rf /usr/share/swift # Swift toolchain (if present)
|
||||||
|
sudo rm -rf "$AGENT_TOOLSDIRECTORY" # Pre-cached tool cache (Go, Node, etc.)
|
||||||
- run: |
|
- run: |
|
||||||
sudo mount -t tmpfs tmpfs .
|
sudo mount -t tmpfs tmpfs .
|
||||||
if: ${{ github.event.inputs.runner == 'fast' }}
|
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||||
@@ -93,8 +131,18 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Configure sccache
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
|
||||||
|
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||||
|
|
||||||
- name: Make
|
- name: Make
|
||||||
run: make ARCH=${{ matrix.arch }} compiled-${{ matrix.arch }}.tar
|
run: make ARCH=${{ matrix.arch }} compiled-${{ matrix.arch }}.tar
|
||||||
|
env:
|
||||||
|
SCCACHE_GHA_ENABLED: on
|
||||||
|
SCCACHE_GHA_VERSION: 0
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -112,7 +160,7 @@ jobs:
|
|||||||
format(
|
format(
|
||||||
'[
|
'[
|
||||||
["{0}"],
|
["{0}"],
|
||||||
["x86_64", "x86_64-nonfree", "aarch64", "aarch64-nonfree", "raspberrypi"]
|
["x86_64", "x86_64-nonfree", "aarch64", "aarch64-nonfree", "riscv64", "raspberrypi"]
|
||||||
]',
|
]',
|
||||||
github.event.inputs.platform || 'ALL'
|
github.event.inputs.platform || 'ALL'
|
||||||
)
|
)
|
||||||
@@ -122,13 +170,22 @@ jobs:
|
|||||||
${{
|
${{
|
||||||
fromJson(
|
fromJson(
|
||||||
format(
|
format(
|
||||||
'["ubuntu-22.04", "{0}"]',
|
'["{0}", "{1}"]',
|
||||||
|
fromJson('{
|
||||||
|
"x86_64": "ubuntu-latest",
|
||||||
|
"x86_64-nonfree": "ubuntu-latest",
|
||||||
|
"aarch64": "ubuntu-24.04-arm",
|
||||||
|
"aarch64-nonfree": "ubuntu-24.04-arm",
|
||||||
|
"raspberrypi": "ubuntu-24.04-arm",
|
||||||
|
"riscv64": "ubuntu-24.04-arm",
|
||||||
|
}')[matrix.platform],
|
||||||
fromJson('{
|
fromJson('{
|
||||||
"x86_64": "buildjet-8vcpu-ubuntu-2204",
|
"x86_64": "buildjet-8vcpu-ubuntu-2204",
|
||||||
"x86_64-nonfree": "buildjet-8vcpu-ubuntu-2204",
|
"x86_64-nonfree": "buildjet-8vcpu-ubuntu-2204",
|
||||||
"aarch64": "buildjet-8vcpu-ubuntu-2204-arm",
|
"aarch64": "buildjet-8vcpu-ubuntu-2204-arm",
|
||||||
"aarch64-nonfree": "buildjet-8vcpu-ubuntu-2204-arm",
|
"aarch64-nonfree": "buildjet-8vcpu-ubuntu-2204-arm",
|
||||||
"raspberrypi": "buildjet-8vcpu-ubuntu-2204-arm",
|
"raspberrypi": "buildjet-8vcpu-ubuntu-2204-arm",
|
||||||
|
"riscv64": "buildjet-8vcpu-ubuntu-2204",
|
||||||
}')[matrix.platform]
|
}')[matrix.platform]
|
||||||
)
|
)
|
||||||
)[github.event.inputs.runner == 'fast']
|
)[github.event.inputs.runner == 'fast']
|
||||||
@@ -142,11 +199,29 @@ jobs:
|
|||||||
"aarch64": "aarch64",
|
"aarch64": "aarch64",
|
||||||
"aarch64-nonfree": "aarch64",
|
"aarch64-nonfree": "aarch64",
|
||||||
"raspberrypi": "aarch64",
|
"raspberrypi": "aarch64",
|
||||||
|
"riscv64": "riscv64",
|
||||||
}')[matrix.platform]
|
}')[matrix.platform]
|
||||||
}}
|
}}
|
||||||
steps:
|
steps:
|
||||||
- name: Free space
|
- name: Free space
|
||||||
run: rm -rf /opt/hostedtoolcache*
|
run: |
|
||||||
|
sudo apt-get remove --purge -y azure-cli || true
|
||||||
|
sudo apt-get remove --purge -y firefox || true
|
||||||
|
sudo apt-get remove --purge -y ghc-* || true
|
||||||
|
sudo apt-get remove --purge -y google-cloud-sdk || true
|
||||||
|
sudo apt-get remove --purge -y google-chrome-stable || true
|
||||||
|
sudo apt-get remove --purge -y powershell || true
|
||||||
|
sudo apt-get remove --purge -y php* || true
|
||||||
|
sudo apt-get remove --purge -y ruby* || true
|
||||||
|
sudo apt-get remove --purge -y mono-* || true
|
||||||
|
sudo apt-get autoremove -y
|
||||||
|
sudo apt-get clean
|
||||||
|
sudo rm -rf /usr/lib/jvm # All JDKs
|
||||||
|
sudo rm -rf /usr/local/.ghcup # Haskell toolchain
|
||||||
|
sudo rm -rf /usr/local/lib/android # Android SDK/NDK, emulator
|
||||||
|
sudo rm -rf /usr/share/dotnet # .NET SDKs
|
||||||
|
sudo rm -rf /usr/share/swift # Swift toolchain (if present)
|
||||||
|
sudo rm -rf "$AGENT_TOOLSDIRECTORY" # Pre-cached tool cache (Go, Node, etc.)
|
||||||
if: ${{ github.event.inputs.runner != 'fast' }}
|
if: ${{ github.event.inputs.runner != 'fast' }}
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -253,7 +328,7 @@ jobs:
|
|||||||
index:
|
index:
|
||||||
if: ${{ github.event.inputs.deploy != '' && github.event.inputs.deploy != 'NONE' }}
|
if: ${{ github.event.inputs.deploy != '' && github.event.inputs.deploy != 'NONE' }}
|
||||||
needs: [image]
|
needs: [image]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- run: >-
|
- run: >-
|
||||||
curl "https://${{
|
curl "https://${{
|
||||||
|
|||||||
4
.github/workflows/test.yaml
vendored
4
.github/workflows/test.yaml
vendored
@@ -11,13 +11,13 @@ on:
|
|||||||
- next/*
|
- next/*
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODEJS_VERSION: "20.16.0"
|
NODEJS_VERSION: "24.11.0"
|
||||||
ENVIRONMENT: dev-unstable
|
ENVIRONMENT: dev-unstable
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Run Automated Tests
|
name: Run Automated Tests
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,8 +1,5 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.idea
|
.idea
|
||||||
system-images/binfmt/binfmt.tar
|
|
||||||
system-images/compat/compat.tar
|
|
||||||
system-images/util/util.tar
|
|
||||||
/*.img
|
/*.img
|
||||||
/*.img.gz
|
/*.img.gz
|
||||||
/*.img.xz
|
/*.img.xz
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# Contributing to StartOS
|
# Contributing to StartOS
|
||||||
|
|
||||||
This guide is for contributing to the StartOS. If you are interested in packaging a service for StartOS, visit the [service packaging guide](https://docs.start9.com/latest/developer-docs/). If you are interested in promoting, providing technical support, creating tutorials, or helping in other ways, please visit the [Start9 website](https://start9.com/contribute).
|
This guide is for contributing to the StartOS. If you are interested in packaging a service for StartOS, visit the [service packaging guide](https://docs.start9.com/latest/packaging-guide/). If you are interested in promoting, providing technical support, creating tutorials, or helping in other ways, please visit the [Start9 website](https://start9.com/contribute).
|
||||||
|
|
||||||
|
|
||||||
## Collaboration
|
## Collaboration
|
||||||
|
|
||||||
@@ -13,64 +12,77 @@ This guide is for contributing to the StartOS. If you are interested in packagin
|
|||||||
```bash
|
```bash
|
||||||
/
|
/
|
||||||
├── assets/
|
├── assets/
|
||||||
|
├── container-runtime/
|
||||||
├── core/
|
├── core/
|
||||||
├── build/
|
├── build/
|
||||||
├── debian/
|
├── debian/
|
||||||
├── web/
|
├── web/
|
||||||
├── image-recipe/
|
├── image-recipe/
|
||||||
├── patch-db
|
├── patch-db
|
||||||
└── system-images/
|
└── sdk/
|
||||||
```
|
```
|
||||||
|
|
||||||
#### assets
|
#### assets
|
||||||
|
|
||||||
screenshots for the StartOS README
|
screenshots for the StartOS README
|
||||||
|
|
||||||
|
#### container-runtime
|
||||||
|
|
||||||
|
A NodeJS program that dynamically loads maintainer scripts and communicates with the OS to manage packages
|
||||||
|
|
||||||
#### core
|
#### core
|
||||||
An API, daemon (startd), CLI (start-cli), and SDK (start-sdk) that together provide the core functionality of StartOS.
|
|
||||||
|
An API, daemon (startd), and CLI (start-cli) that together provide the core functionality of StartOS.
|
||||||
|
|
||||||
#### build
|
#### build
|
||||||
|
|
||||||
Auxiliary files and scripts to include in deployed StartOS images
|
Auxiliary files and scripts to include in deployed StartOS images
|
||||||
|
|
||||||
#### debian
|
#### debian
|
||||||
|
|
||||||
Maintainer scripts for the StartOS Debian package
|
Maintainer scripts for the StartOS Debian package
|
||||||
|
|
||||||
#### web
|
#### web
|
||||||
|
|
||||||
Web UIs served under various conditions and used to interact with StartOS APIs.
|
Web UIs served under various conditions and used to interact with StartOS APIs.
|
||||||
|
|
||||||
#### image-recipe
|
#### image-recipe
|
||||||
|
|
||||||
Scripts for building StartOS images
|
Scripts for building StartOS images
|
||||||
|
|
||||||
#### patch-db (submodule)
|
#### patch-db (submodule)
|
||||||
|
|
||||||
A diff based data store used to synchronize data between the web interfaces and server.
|
A diff based data store used to synchronize data between the web interfaces and server.
|
||||||
|
|
||||||
#### system-images
|
#### sdk
|
||||||
Docker images that assist with creating backups.
|
|
||||||
|
A typescript sdk for building start-os packages
|
||||||
|
|
||||||
## Environment Setup
|
## Environment Setup
|
||||||
|
|
||||||
#### Clone the StartOS repository
|
#### Clone the StartOS repository
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/Start9Labs/start-os.git
|
git clone https://github.com/Start9Labs/start-os.git --recurse-submodules
|
||||||
cd start-os
|
cd start-os
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Load the PatchDB submodule
|
|
||||||
```sh
|
|
||||||
git submodule update --init --recursive
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Continue to your project of interest for additional instructions:
|
#### Continue to your project of interest for additional instructions:
|
||||||
|
|
||||||
- [`core`](core/README.md)
|
- [`core`](core/README.md)
|
||||||
- [`web-interfaces`](web-interfaces/README.md)
|
- [`web-interfaces`](web-interfaces/README.md)
|
||||||
- [`build`](build/README.md)
|
- [`build`](build/README.md)
|
||||||
- [`patch-db`](https://github.com/Start9Labs/patch-db)
|
- [`patch-db`](https://github.com/Start9Labs/patch-db)
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
This project uses [GNU Make](https://www.gnu.org/software/make/) to build its components. To build any specific component, simply run `make <TARGET>` replacing `<TARGET>` with the name of the target you'd like to build
|
This project uses [GNU Make](https://www.gnu.org/software/make/) to build its components. To build any specific component, simply run `make <TARGET>` replacing `<TARGET>` with the name of the target you'd like to build
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- [GNU Make](https://www.gnu.org/software/make/)
|
- [GNU Make](https://www.gnu.org/software/make/)
|
||||||
- [Docker](https://docs.docker.com/get-docker/)
|
- [Docker](https://docs.docker.com/get-docker/)
|
||||||
- [NodeJS v18.15.0](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
|
- [NodeJS v20.16.0](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
|
||||||
- [sed](https://www.gnu.org/software/sed/)
|
- [sed](https://www.gnu.org/software/sed/)
|
||||||
- [grep](https://www.gnu.org/software/grep/)
|
- [grep](https://www.gnu.org/software/grep/)
|
||||||
- [awk](https://www.gnu.org/software/gawk/)
|
- [awk](https://www.gnu.org/software/gawk/)
|
||||||
@@ -79,41 +91,43 @@ This project uses [GNU Make](https://www.gnu.org/software/make/) to build its co
|
|||||||
- [brotli](https://github.com/google/brotli)
|
- [brotli](https://github.com/google/brotli)
|
||||||
|
|
||||||
### Environment variables
|
### Environment variables
|
||||||
|
|
||||||
- `PLATFORM`: which platform you would like to build for. Must be one of `x86_64`, `x86_64-nonfree`, `aarch64`, `aarch64-nonfree`, `raspberrypi`
|
- `PLATFORM`: which platform you would like to build for. Must be one of `x86_64`, `x86_64-nonfree`, `aarch64`, `aarch64-nonfree`, `raspberrypi`
|
||||||
- NOTE: `nonfree` images are for including `nonfree` firmware packages in the built ISO
|
- NOTE: `nonfree` images are for including `nonfree` firmware packages in the built ISO
|
||||||
- `ENVIRONMENT`: a hyphen separated set of feature flags to enable
|
- `ENVIRONMENT`: a hyphen separated set of feature flags to enable
|
||||||
- `dev`: enables password ssh (INSECURE!) and does not compress frontends
|
- `dev`: enables password ssh (INSECURE!) and does not compress frontends
|
||||||
- `unstable`: enables assertions that will cause errors on unexpected inconsistencies that are undesirable in production use either for performance or reliability reasons
|
- `unstable`: enables assertions that will cause errors on unexpected inconsistencies that are undesirable in production use either for performance or reliability reasons
|
||||||
- `docker`: use `docker` instead of `podman`
|
- `docker`: use `docker` instead of `podman`
|
||||||
- `GIT_BRANCH_AS_HASH`: set to `1` to use the current git branch name as the git hash so that the project does not need to be rebuilt on each commit
|
- `GIT_BRANCH_AS_HASH`: set to `1` to use the current git branch name as the git hash so that the project does not need to be rebuilt on each commit
|
||||||
|
|
||||||
### Useful Make Targets
|
### Useful Make Targets
|
||||||
|
|
||||||
- `iso`: Create a full `.iso` image
|
- `iso`: Create a full `.iso` image
|
||||||
- Only possible from Debian
|
- Only possible from Debian
|
||||||
- Not available for `PLATFORM=raspberrypi`
|
- Not available for `PLATFORM=raspberrypi`
|
||||||
- Additional Requirements:
|
- Additional Requirements:
|
||||||
- [debspawn](https://github.com/lkhq/debspawn)
|
- [debspawn](https://github.com/lkhq/debspawn)
|
||||||
- `img`: Create a full `.img` image
|
- `img`: Create a full `.img` image
|
||||||
- Only possible from Debian
|
- Only possible from Debian
|
||||||
- Only available for `PLATFORM=raspberrypi`
|
- Only available for `PLATFORM=raspberrypi`
|
||||||
- Additional Requirements:
|
- Additional Requirements:
|
||||||
- [debspawn](https://github.com/lkhq/debspawn)
|
- [debspawn](https://github.com/lkhq/debspawn)
|
||||||
- `format`: Run automatic code formatting for the project
|
- `format`: Run automatic code formatting for the project
|
||||||
- Additional Requirements:
|
- Additional Requirements:
|
||||||
- [rust](https://rustup.rs/)
|
- [rust](https://rustup.rs/)
|
||||||
- `test`: Run automated tests for the project
|
- `test`: Run automated tests for the project
|
||||||
- Additional Requirements:
|
- Additional Requirements:
|
||||||
- [rust](https://rustup.rs/)
|
- [rust](https://rustup.rs/)
|
||||||
- `update`: Deploy the current working project to a device over ssh as if through an over-the-air update
|
- `update`: Deploy the current working project to a device over ssh as if through an over-the-air update
|
||||||
- Requires an argument `REMOTE` which is the ssh address of the device, i.e. `start9@192.168.122.2`
|
- Requires an argument `REMOTE` which is the ssh address of the device, i.e. `start9@192.168.122.2`
|
||||||
- `reflash`: Deploy the current working project to a device over ssh as if using a live `iso` image to reflash it
|
- `reflash`: Deploy the current working project to a device over ssh as if using a live `iso` image to reflash it
|
||||||
- Requires an argument `REMOTE` which is the ssh address of the device, i.e. `start9@192.168.122.2`
|
- Requires an argument `REMOTE` which is the ssh address of the device, i.e. `start9@192.168.122.2`
|
||||||
- `update-overlay`: Deploy the current working project to a device over ssh to the in-memory overlay without restarting it
|
- `update-overlay`: Deploy the current working project to a device over ssh to the in-memory overlay without restarting it
|
||||||
- WARNING: changes will be reverted after the device is rebooted
|
- WARNING: changes will be reverted after the device is rebooted
|
||||||
- WARNING: changes to `init` will not take effect as the device is already initialized
|
- WARNING: changes to `init` will not take effect as the device is already initialized
|
||||||
- Requires an argument `REMOTE` which is the ssh address of the device, i.e. `start9@192.168.122.2`
|
- Requires an argument `REMOTE` which is the ssh address of the device, i.e. `start9@192.168.122.2`
|
||||||
- `wormhole`: Deploy the `startbox` to a device using [magic-wormhole](https://github.com/magic-wormhole/magic-wormhole)
|
- `wormhole`: Deploy the `startbox` to a device using [magic-wormhole](https://github.com/magic-wormhole/magic-wormhole)
|
||||||
- When the build it complete will emit a command to paste into the shell of the device to upgrade it
|
- When the build it complete will emit a command to paste into the shell of the device to upgrade it
|
||||||
- Additional Requirements:
|
- Additional Requirements:
|
||||||
- [magic-wormhole](https://github.com/magic-wormhole/magic-wormhole)
|
- [magic-wormhole](https://github.com/magic-wormhole/magic-wormhole)
|
||||||
- `clean`: Delete all compiled artifacts
|
- `clean`: Delete all compiled artifacts
|
||||||
|
|||||||
@@ -25,15 +25,15 @@ docker buildx create --use
|
|||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # proceed with default installation
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # proceed with default installation
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
|
||||||
source ~/.bashrc
|
source ~/.bashrc
|
||||||
nvm install 20
|
nvm install 24
|
||||||
nvm use 20
|
nvm use 24
|
||||||
nvm alias default 20 # this prevents your machine from reverting back to another version
|
nvm alias default 24 # this prevents your machine from reverting back to another version
|
||||||
```
|
```
|
||||||
|
|
||||||
## Cloning the repository
|
## Cloning the repository
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone --recursive https://github.com/Start9Labs/start-os.git --branch next/minor
|
git clone --recursive https://github.com/Start9Labs/start-os.git --branch next/major
|
||||||
cd start-os
|
cd start-os
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
228
Makefile
228
Makefile
@@ -1,32 +1,45 @@
|
|||||||
|
ls-files = $(shell git ls-files --cached --others --exclude-standard $1)
|
||||||
|
PROFILE = release
|
||||||
|
|
||||||
PLATFORM_FILE := $(shell ./check-platform.sh)
|
PLATFORM_FILE := $(shell ./check-platform.sh)
|
||||||
ENVIRONMENT_FILE := $(shell ./check-environment.sh)
|
ENVIRONMENT_FILE := $(shell ./check-environment.sh)
|
||||||
GIT_HASH_FILE := $(shell ./check-git-hash.sh)
|
GIT_HASH_FILE := $(shell ./check-git-hash.sh)
|
||||||
VERSION_FILE := $(shell ./check-version.sh)
|
VERSION_FILE := $(shell ./check-version.sh)
|
||||||
BASENAME := $(shell ./basename.sh)
|
BASENAME := $(shell PROJECT=startos ./basename.sh)
|
||||||
PLATFORM := $(shell if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi)
|
PLATFORM := $(shell if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi)
|
||||||
ARCH := $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo aarch64; else echo $(PLATFORM) | sed 's/-nonfree$$//g'; fi)
|
ARCH := $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo aarch64; else echo $(PLATFORM) | sed 's/-nonfree$$//g'; fi)
|
||||||
|
RUST_ARCH := $(shell if [ "$(ARCH)" = "riscv64" ]; then echo riscv64gc; else echo $(ARCH); fi)
|
||||||
|
REGISTRY_BASENAME := $(shell PROJECT=start-registry PLATFORM=$(ARCH) ./basename.sh)
|
||||||
|
TUNNEL_BASENAME := $(shell PROJECT=start-tunnel PLATFORM=$(ARCH) ./basename.sh)
|
||||||
IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi)
|
IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi)
|
||||||
WEB_UIS := web/dist/raw/ui/index.html web/dist/raw/setup-wizard/index.html web/dist/raw/install-wizard/index.html
|
WEB_UIS := web/dist/raw/ui/index.html web/dist/raw/setup-wizard/index.html web/dist/raw/install-wizard/index.html
|
||||||
COMPRESSED_WEB_UIS := web/dist/static/ui/index.html web/dist/static/setup-wizard/index.html web/dist/static/install-wizard/index.html
|
COMPRESSED_WEB_UIS := web/dist/static/ui/index.html web/dist/static/setup-wizard/index.html web/dist/static/install-wizard/index.html
|
||||||
FIRMWARE_ROMS := ./firmware/$(PLATFORM) $(shell jq --raw-output '.[] | select(.platform[] | contains("$(PLATFORM)")) | "./firmware/$(PLATFORM)/" + .id + ".rom.gz"' build/lib/firmware.json)
|
FIRMWARE_ROMS := ./firmware/$(PLATFORM) $(shell jq --raw-output '.[] | select(.platform[] | contains("$(PLATFORM)")) | "./firmware/$(PLATFORM)/" + .id + ".rom.gz"' build/lib/firmware.json)
|
||||||
BUILD_SRC := $(shell git ls-files build) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS)
|
BUILD_SRC := $(call ls-files, build) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS)
|
||||||
DEBIAN_SRC := $(shell git ls-files debian/)
|
IMAGE_RECIPE_SRC := $(call ls-files, image-recipe/)
|
||||||
IMAGE_RECIPE_SRC := $(shell git ls-files image-recipe/)
|
|
||||||
STARTD_SRC := core/startos/startd.service $(BUILD_SRC)
|
STARTD_SRC := core/startos/startd.service $(BUILD_SRC)
|
||||||
COMPAT_SRC := $(shell git ls-files system-images/compat/)
|
CORE_SRC := $(call ls-files, core) $(shell git ls-files --recurse-submodules patch-db) $(GIT_HASH_FILE)
|
||||||
UTILS_SRC := $(shell git ls-files system-images/utils/)
|
WEB_SHARED_SRC := $(call ls-files, web/projects/shared) $(call ls-files, web/projects/marketplace) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist/index.js sdk/baseDist/package.json web/patchdb-ui-seed.json sdk/dist/package.json
|
||||||
BINFMT_SRC := $(shell git ls-files system-images/binfmt/)
|
WEB_UI_SRC := $(call ls-files, web/projects/ui)
|
||||||
CORE_SRC := $(shell git ls-files core) $(shell git ls-files --recurse-submodules patch-db) $(GIT_HASH_FILE)
|
WEB_SETUP_WIZARD_SRC := $(call ls-files, web/projects/setup-wizard)
|
||||||
WEB_SHARED_SRC := $(shell git ls-files web/projects/shared) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist/index.js sdk/baseDist/package.json web/patchdb-ui-seed.json sdk/dist/package.json
|
WEB_INSTALL_WIZARD_SRC := $(call ls-files, web/projects/install-wizard)
|
||||||
WEB_UI_SRC := $(shell git ls-files web/projects/ui)
|
WEB_START_TUNNEL_SRC := $(call ls-files, web/projects/start-tunnel)
|
||||||
WEB_SETUP_WIZARD_SRC := $(shell git ls-files web/projects/setup-wizard)
|
|
||||||
WEB_INSTALL_WIZARD_SRC := $(shell git ls-files web/projects/install-wizard)
|
|
||||||
PATCH_DB_CLIENT_SRC := $(shell git ls-files --recurse-submodules patch-db/client)
|
PATCH_DB_CLIENT_SRC := $(shell git ls-files --recurse-submodules patch-db/client)
|
||||||
GZIP_BIN := $(shell which pigz || which gzip)
|
GZIP_BIN := $(shell which pigz || which gzip)
|
||||||
TAR_BIN := $(shell which gtar || which tar)
|
TAR_BIN := $(shell which gtar || which tar)
|
||||||
COMPILED_TARGETS := core/target/$(ARCH)-unknown-linux-musl/release/startbox core/target/$(ARCH)-unknown-linux-musl/release/containerbox system-images/compat/docker-images/$(ARCH).tar system-images/utils/docker-images/$(ARCH).tar system-images/binfmt/docker-images/$(ARCH).tar container-runtime/rootfs.$(ARCH).squashfs
|
COMPILED_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox core/target/$(RUST_ARCH)-unknown-linux-musl/release/start-container container-runtime/rootfs.$(ARCH).squashfs
|
||||||
ALL_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) $(COMPILED_TARGETS) cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo cargo-deps/aarch64-unknown-linux-musl/release/pi-beep; fi) $(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then echo cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console; fi') $(PLATFORM_FILE)
|
STARTOS_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) $(COMPILED_TARGETS) cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs $(PLATFORM_FILE) \
|
||||||
REBUILD_TYPES = 1
|
$(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then \
|
||||||
|
echo cargo-deps/aarch64-unknown-linux-musl/release/pi-beep; \
|
||||||
|
fi) \
|
||||||
|
$(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then \
|
||||||
|
echo cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph; \
|
||||||
|
fi') \
|
||||||
|
$(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)console($$|-) ]]; then \
|
||||||
|
echo cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/tokio-console; \
|
||||||
|
fi')
|
||||||
|
REGISTRY_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox core/startos/start-registryd.service
|
||||||
|
TUNNEL_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox core/startos/start-tunneld.service
|
||||||
|
|
||||||
ifeq ($(REMOTE),)
|
ifeq ($(REMOTE),)
|
||||||
mkdir = mkdir -p $1
|
mkdir = mkdir -p $1
|
||||||
@@ -49,21 +62,16 @@ endif
|
|||||||
|
|
||||||
.DELETE_ON_ERROR:
|
.DELETE_ON_ERROR:
|
||||||
|
|
||||||
.PHONY: all metadata install clean format cli uis ui reflash deb $(IMAGE_TYPE) squashfs sudo wormhole wormhole-deb test test-core test-sdk test-container-runtime registry
|
.PHONY: all metadata install clean format install-cli cli uis ui reflash deb $(IMAGE_TYPE) squashfs wormhole wormhole-deb test test-core test-sdk test-container-runtime registry install-registry tunnel install-tunnel ts-bindings
|
||||||
|
|
||||||
all: $(ALL_TARGETS)
|
all: $(STARTOS_TARGETS)
|
||||||
|
|
||||||
touch:
|
touch:
|
||||||
touch $(ALL_TARGETS)
|
touch $(STARTOS_TARGETS)
|
||||||
|
|
||||||
metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
||||||
|
|
||||||
sudo:
|
|
||||||
sudo true
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f system-images/**/*.tar
|
|
||||||
rm -rf system-images/compat/target
|
|
||||||
rm -rf core/target
|
rm -rf core/target
|
||||||
rm -rf core/startos/bindings
|
rm -rf core/startos/bindings
|
||||||
rm -rf web/.angular
|
rm -rf web/.angular
|
||||||
@@ -98,25 +106,63 @@ test: | test-core test-sdk test-container-runtime
|
|||||||
test-core: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
test-core: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||||
./core/run-tests.sh
|
./core/run-tests.sh
|
||||||
|
|
||||||
test-sdk: $(shell git ls-files sdk) sdk/base/lib/osBindings/index.ts
|
test-sdk: $(call ls-files, sdk) sdk/base/lib/osBindings/index.ts
|
||||||
cd sdk && make test
|
cd sdk && make test
|
||||||
|
|
||||||
test-container-runtime: container-runtime/node_modules/.package-lock.json $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
test-container-runtime: container-runtime/node_modules/.package-lock.json $(call ls-files, container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
||||||
cd container-runtime && npm test
|
cd container-runtime && npm test
|
||||||
|
|
||||||
cli:
|
install-cli: $(GIT_HASH_FILE)
|
||||||
cd core && ./install-cli.sh
|
./core/build-cli.sh --install
|
||||||
|
|
||||||
registry:
|
cli: $(GIT_HASH_FILE)
|
||||||
cd core && ./build-registrybox.sh
|
./core/build-cli.sh
|
||||||
|
|
||||||
|
registry: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox
|
||||||
|
|
||||||
|
install-registry: $(REGISTRY_TARGETS)
|
||||||
|
$(call mkdir,$(DESTDIR)/usr/bin)
|
||||||
|
$(call cp,core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox,$(DESTDIR)/usr/bin/start-registrybox)
|
||||||
|
$(call ln,/usr/bin/start-registrybox,$(DESTDIR)/usr/bin/start-registryd)
|
||||||
|
$(call ln,/usr/bin/start-registrybox,$(DESTDIR)/usr/bin/start-registry)
|
||||||
|
|
||||||
|
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
||||||
|
$(call cp,core/startos/start-registryd.service,$(DESTDIR)/lib/systemd/system/start-registryd.service)
|
||||||
|
|
||||||
|
core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||||
|
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-registrybox.sh
|
||||||
|
|
||||||
|
tunnel: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox
|
||||||
|
|
||||||
|
install-tunnel: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox core/startos/start-tunneld.service
|
||||||
|
$(call mkdir,$(DESTDIR)/usr/bin)
|
||||||
|
$(call cp,core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox,$(DESTDIR)/usr/bin/start-tunnelbox)
|
||||||
|
$(call ln,/usr/bin/start-tunnelbox,$(DESTDIR)/usr/bin/start-tunneld)
|
||||||
|
$(call ln,/usr/bin/start-tunnelbox,$(DESTDIR)/usr/bin/start-tunnel)
|
||||||
|
|
||||||
|
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
||||||
|
$(call cp,core/startos/start-tunneld.service,$(DESTDIR)/lib/systemd/system/start-tunneld.service)
|
||||||
|
|
||||||
|
$(call mkdir,$(DESTDIR)/usr/lib/startos/scripts)
|
||||||
|
$(call cp,build/lib/scripts/forward-port,$(DESTDIR)/usr/lib/startos/scripts/forward-port)
|
||||||
|
|
||||||
|
core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox: $(CORE_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) web/dist/static/start-tunnel/index.html
|
||||||
|
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-tunnelbox.sh
|
||||||
|
|
||||||
deb: results/$(BASENAME).deb
|
deb: results/$(BASENAME).deb
|
||||||
|
|
||||||
debian/control: build/lib/depends build/lib/conflicts
|
results/$(BASENAME).deb: dpkg-build.sh $(call ls-files,debian/startos) $(STARTOS_TARGETS)
|
||||||
./debuild/control.sh
|
PLATFORM=$(PLATFORM) REQUIRES=debian ./build/os-compat/run-compat.sh ./dpkg-build.sh
|
||||||
|
|
||||||
results/$(BASENAME).deb: dpkg-build.sh $(DEBIAN_SRC) $(ALL_TARGETS)
|
registry-deb: results/$(REGISTRY_BASENAME).deb
|
||||||
PLATFORM=$(PLATFORM) ./dpkg-build.sh
|
|
||||||
|
results/$(REGISTRY_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-registry) $(REGISTRY_TARGETS)
|
||||||
|
PROJECT=start-registry PLATFORM=$(ARCH) REQUIRES=debian ./build/os-compat/run-compat.sh ./dpkg-build.sh
|
||||||
|
|
||||||
|
tunnel-deb: results/$(TUNNEL_BASENAME).deb
|
||||||
|
|
||||||
|
results/$(TUNNEL_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-tunnel) $(TUNNEL_TARGETS) build/lib/scripts/forward-port
|
||||||
|
PROJECT=start-tunnel PLATFORM=$(ARCH) REQUIRES=debian DEPENDS=wireguard-tools,iptables,conntrack ./build/os-compat/run-compat.sh ./dpkg-build.sh
|
||||||
|
|
||||||
$(IMAGE_TYPE): results/$(BASENAME).$(IMAGE_TYPE)
|
$(IMAGE_TYPE): results/$(BASENAME).$(IMAGE_TYPE)
|
||||||
|
|
||||||
@@ -126,16 +172,20 @@ results/$(BASENAME).$(IMAGE_TYPE) results/$(BASENAME).squashfs: $(IMAGE_RECIPE_S
|
|||||||
./image-recipe/run-local-build.sh "results/$(BASENAME).deb"
|
./image-recipe/run-local-build.sh "results/$(BASENAME).deb"
|
||||||
|
|
||||||
# For creating os images. DO NOT USE
|
# For creating os images. DO NOT USE
|
||||||
install: $(ALL_TARGETS)
|
install: $(STARTOS_TARGETS)
|
||||||
$(call mkdir,$(DESTDIR)/usr/bin)
|
$(call mkdir,$(DESTDIR)/usr/bin)
|
||||||
$(call mkdir,$(DESTDIR)/usr/sbin)
|
$(call mkdir,$(DESTDIR)/usr/sbin)
|
||||||
$(call cp,core/target/$(ARCH)-unknown-linux-musl/release/startbox,$(DESTDIR)/usr/bin/startbox)
|
$(call cp,core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox,$(DESTDIR)/usr/bin/startbox)
|
||||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/startd)
|
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/startd)
|
||||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-cli)
|
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-cli)
|
||||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-sdk)
|
|
||||||
if [ "$(PLATFORM)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-musl/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi
|
if [ "$(PLATFORM)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-musl/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi
|
||||||
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then $(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); fi
|
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then \
|
||||||
$(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs,$(DESTDIR)/usr/bin/startos-backup-fs)
|
$(call cp,cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph,$(DESTDIR)/usr/bin/flamegraph); \
|
||||||
|
fi
|
||||||
|
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)console($$|-) ]]'; then \
|
||||||
|
$(call cp,cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); \
|
||||||
|
fi
|
||||||
|
$(call cp,cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs,$(DESTDIR)/usr/bin/startos-backup-fs)
|
||||||
$(call ln,/usr/bin/startos-backup-fs,$(DESTDIR)/usr/sbin/mount.backup-fs)
|
$(call ln,/usr/bin/startos-backup-fs,$(DESTDIR)/usr/sbin/mount.backup-fs)
|
||||||
|
|
||||||
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
||||||
@@ -152,13 +202,9 @@ install: $(ALL_TARGETS)
|
|||||||
$(call cp,GIT_HASH.txt,$(DESTDIR)/usr/lib/startos/GIT_HASH.txt)
|
$(call cp,GIT_HASH.txt,$(DESTDIR)/usr/lib/startos/GIT_HASH.txt)
|
||||||
$(call cp,VERSION.txt,$(DESTDIR)/usr/lib/startos/VERSION.txt)
|
$(call cp,VERSION.txt,$(DESTDIR)/usr/lib/startos/VERSION.txt)
|
||||||
|
|
||||||
$(call mkdir,$(DESTDIR)/usr/lib/startos/system-images)
|
|
||||||
$(call cp,system-images/compat/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/compat.tar)
|
|
||||||
$(call cp,system-images/utils/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/utils.tar)
|
|
||||||
|
|
||||||
$(call cp,firmware/$(PLATFORM),$(DESTDIR)/usr/lib/startos/firmware)
|
$(call cp,firmware/$(PLATFORM),$(DESTDIR)/usr/lib/startos/firmware)
|
||||||
|
|
||||||
update-overlay: $(ALL_TARGETS)
|
update-overlay: $(STARTOS_TARGETS)
|
||||||
@echo "\033[33m!!! THIS WILL ONLY REFLASH YOUR DEVICE IN MEMORY !!!\033[0m"
|
@echo "\033[33m!!! THIS WILL ONLY REFLASH YOUR DEVICE IN MEMORY !!!\033[0m"
|
||||||
@echo "\033[33mALL CHANGES WILL BE REVERTED IF YOU RESTART THE DEVICE\033[0m"
|
@echo "\033[33mALL CHANGES WILL BE REVERTED IF YOU RESTART THE DEVICE\033[0m"
|
||||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||||
@@ -167,10 +213,10 @@ update-overlay: $(ALL_TARGETS)
|
|||||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) PLATFORM=$(PLATFORM)
|
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) PLATFORM=$(PLATFORM)
|
||||||
$(call ssh,"sudo systemctl start startd")
|
$(call ssh,"sudo systemctl start startd")
|
||||||
|
|
||||||
wormhole: core/target/$(ARCH)-unknown-linux-musl/release/startbox
|
wormhole: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox
|
||||||
@echo "Paste the following command into the shell of your StartOS server:"
|
@echo "Paste the following command into the shell of your StartOS server:"
|
||||||
@echo
|
@echo
|
||||||
@wormhole send core/target/$(ARCH)-unknown-linux-musl/release/startbox 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo /usr/lib/startos/scripts/chroot-and-upgrade \"cd /usr/bin && rm startbox && wormhole receive --accept-file %s && chmod +x startbox\"\n", $$3 }'
|
@wormhole send core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo /usr/lib/startos/scripts/chroot-and-upgrade \"cd /usr/bin && rm startbox && wormhole receive --accept-file %s && chmod +x startbox\"\n", $$3 }'
|
||||||
|
|
||||||
wormhole-deb: results/$(BASENAME).deb
|
wormhole-deb: results/$(BASENAME).deb
|
||||||
@echo "Paste the following command into the shell of your StartOS server:"
|
@echo "Paste the following command into the shell of your StartOS server:"
|
||||||
@@ -182,18 +228,18 @@ wormhole-squashfs: results/$(BASENAME).squashfs
|
|||||||
$(eval SQFS_SIZE := $(shell du -s --bytes results/$(BASENAME).squashfs | awk '{print $$1}'))
|
$(eval SQFS_SIZE := $(shell du -s --bytes results/$(BASENAME).squashfs | awk '{print $$1}'))
|
||||||
@echo "Paste the following command into the shell of your StartOS server:"
|
@echo "Paste the following command into the shell of your StartOS server:"
|
||||||
@echo
|
@echo
|
||||||
@wormhole send results/$(BASENAME).squashfs 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo sh -c '"'"'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE) && cd /media/startos/images && wormhole receive --accept-file %s && mv $(BASENAME).squashfs $(SQFS_SUM).rootfs && ln -rsf ./$(SQFS_SUM).rootfs ../config/current.rootfs && sync && reboot'"'"'\n", $$3 }'
|
@wormhole send results/$(BASENAME).squashfs 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo sh -c '"'"'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE) && /usr/lib/startos/scripts/prune-boot && cd /media/startos/images && wormhole receive --accept-file %s && CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/upgrade ./$(BASENAME).squashfs'"'"'\n", $$3 }'
|
||||||
|
|
||||||
update: $(ALL_TARGETS)
|
update: $(STARTOS_TARGETS)
|
||||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||||
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
||||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/startos/next PLATFORM=$(PLATFORM)
|
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/startos/next PLATFORM=$(PLATFORM)
|
||||||
$(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync "apt-get install -y $(shell cat ./build/lib/depends)"')
|
$(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync "apt-get install -y $(shell cat ./build/lib/depends)"')
|
||||||
|
|
||||||
update-startbox: core/target/$(ARCH)-unknown-linux-musl/release/startbox # only update binary (faster than full update)
|
update-startbox: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox # only update binary (faster than full update)
|
||||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||||
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
||||||
$(call cp,core/target/$(ARCH)-unknown-linux-musl/release/startbox,/media/startos/next/usr/bin/startbox)
|
$(call cp,core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox,/media/startos/next/usr/bin/startbox)
|
||||||
$(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync true')
|
$(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync true')
|
||||||
|
|
||||||
update-deb: results/$(BASENAME).deb # better than update, but only available from debian
|
update-deb: results/$(BASENAME).deb # better than update, but only available from debian
|
||||||
@@ -208,11 +254,11 @@ update-squashfs: results/$(BASENAME).squashfs
|
|||||||
$(eval SQFS_SUM := $(shell b3sum results/$(BASENAME).squashfs))
|
$(eval SQFS_SUM := $(shell b3sum results/$(BASENAME).squashfs))
|
||||||
$(eval SQFS_SIZE := $(shell du -s --bytes results/$(BASENAME).squashfs | awk '{print $$1}'))
|
$(eval SQFS_SIZE := $(shell du -s --bytes results/$(BASENAME).squashfs | awk '{print $$1}'))
|
||||||
$(call ssh,'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE)')
|
$(call ssh,'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE)')
|
||||||
$(call cp,results/$(BASENAME).squashfs,/media/startos/images/$(SQFS_SUM).rootfs)
|
$(call ssh,'/usr/lib/startos/scripts/prune-boot')
|
||||||
$(call ssh,'sudo ln -rsf /media/startos/images/$(SQFS_SUM).rootfs /media/startos/config/current.rootfs')
|
$(call cp,results/$(BASENAME).squashfs,/media/startos/images/next.rootfs)
|
||||||
$(call ssh,'sudo reboot')
|
$(call ssh,'sudo CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/upgrade /media/startos/images/next.rootfs')
|
||||||
|
|
||||||
emulate-reflash: $(ALL_TARGETS)
|
emulate-reflash: $(STARTOS_TARGETS)
|
||||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||||
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
||||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/startos/next PLATFORM=$(PLATFORM)
|
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/startos/next PLATFORM=$(PLATFORM)
|
||||||
@@ -222,65 +268,63 @@ emulate-reflash: $(ALL_TARGETS)
|
|||||||
upload-ota: results/$(BASENAME).squashfs
|
upload-ota: results/$(BASENAME).squashfs
|
||||||
TARGET=$(TARGET) KEY=$(KEY) ./upload-ota.sh
|
TARGET=$(TARGET) KEY=$(KEY) ./upload-ota.sh
|
||||||
|
|
||||||
container-runtime/debian.$(ARCH).squashfs:
|
container-runtime/debian.$(ARCH).squashfs: ./container-runtime/download-base-image.sh
|
||||||
ARCH=$(ARCH) ./container-runtime/download-base-image.sh
|
ARCH=$(ARCH) ./container-runtime/download-base-image.sh
|
||||||
|
|
||||||
container-runtime/node_modules/.package-lock.json: container-runtime/package.json container-runtime/package-lock.json sdk/dist/package.json
|
container-runtime/package-lock.json: sdk/dist/package.json
|
||||||
|
npm --prefix container-runtime i
|
||||||
|
touch container-runtime/package-lock.json
|
||||||
|
|
||||||
|
container-runtime/node_modules/.package-lock.json: container-runtime/package-lock.json
|
||||||
npm --prefix container-runtime ci
|
npm --prefix container-runtime ci
|
||||||
touch container-runtime/node_modules/.package-lock.json
|
touch container-runtime/node_modules/.package-lock.json
|
||||||
|
|
||||||
sdk/base/lib/osBindings/index.ts: $(shell if [ "$(REBUILD_TYPES)" -ne 0 ]; then echo core/startos/bindings/index.ts; fi)
|
ts-bindings: core/startos/bindings/index.ts
|
||||||
mkdir -p sdk/base/lib/osBindings
|
mkdir -p sdk/base/lib/osBindings
|
||||||
rsync -ac --delete core/startos/bindings/ sdk/base/lib/osBindings/
|
rsync -ac --delete core/startos/bindings/ sdk/base/lib/osBindings/
|
||||||
touch sdk/base/lib/osBindings/index.ts
|
|
||||||
|
|
||||||
core/startos/bindings/index.ts: $(shell git ls-files core) $(ENVIRONMENT_FILE)
|
core/startos/bindings/index.ts: $(call ls-files, core) $(ENVIRONMENT_FILE)
|
||||||
rm -rf core/startos/bindings
|
rm -rf core/startos/bindings
|
||||||
./core/build-ts.sh
|
./core/build-ts.sh
|
||||||
ls core/startos/bindings/*.ts | sed 's/core\/startos\/bindings\/\([^.]*\)\.ts/export { \1 } from ".\/\1";/g' | grep -v '"./index"' | tee core/startos/bindings/index.ts
|
ls core/startos/bindings/*.ts | sed 's/core\/startos\/bindings\/\([^.]*\)\.ts/export { \1 } from ".\/\1";/g' | grep -v '"./index"' | tee core/startos/bindings/index.ts
|
||||||
npm --prefix sdk exec -- prettier --config ./sdk/base/package.json -w ./core/startos/bindings/*.ts
|
npm --prefix sdk exec -- prettier --config ./sdk/base/package.json -w ./core/startos/bindings/*.ts
|
||||||
touch core/startos/bindings/index.ts
|
touch core/startos/bindings/index.ts
|
||||||
|
|
||||||
sdk/dist/package.json sdk/baseDist/package.json: $(shell git ls-files sdk) sdk/base/lib/osBindings/index.ts
|
sdk/dist/package.json sdk/baseDist/package.json: $(call ls-files, sdk) sdk/base/lib/osBindings/index.ts
|
||||||
(cd sdk && make bundle)
|
(cd sdk && make bundle)
|
||||||
touch sdk/dist/package.json
|
touch sdk/dist/package.json
|
||||||
touch sdk/baseDist/package.json
|
touch sdk/baseDist/package.json
|
||||||
|
|
||||||
# TODO: make container-runtime its own makefile?
|
# TODO: make container-runtime its own makefile?
|
||||||
container-runtime/dist/index.js: container-runtime/node_modules/.package-lock.json $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
container-runtime/dist/index.js: container-runtime/node_modules/.package-lock.json $(call ls-files, container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
||||||
npm --prefix container-runtime run build
|
npm --prefix container-runtime run build
|
||||||
|
|
||||||
container-runtime/dist/node_modules/.package-lock.json container-runtime/dist/package.json container-runtime/dist/package-lock.json: container-runtime/package.json container-runtime/package-lock.json sdk/dist/package.json container-runtime/install-dist-deps.sh
|
container-runtime/dist/node_modules/.package-lock.json container-runtime/dist/package.json container-runtime/dist/package-lock.json: container-runtime/package.json container-runtime/package-lock.json sdk/dist/package.json container-runtime/install-dist-deps.sh
|
||||||
./container-runtime/install-dist-deps.sh
|
./container-runtime/install-dist-deps.sh
|
||||||
touch container-runtime/dist/node_modules/.package-lock.json
|
touch container-runtime/dist/node_modules/.package-lock.json
|
||||||
|
|
||||||
container-runtime/rootfs.$(ARCH).squashfs: container-runtime/debian.$(ARCH).squashfs container-runtime/container-runtime.service container-runtime/update-image.sh container-runtime/deb-install.sh container-runtime/dist/index.js container-runtime/dist/node_modules/.package-lock.json core/target/$(ARCH)-unknown-linux-musl/release/containerbox | sudo
|
container-runtime/rootfs.$(ARCH).squashfs: container-runtime/debian.$(ARCH).squashfs container-runtime/container-runtime.service container-runtime/update-image.sh container-runtime/deb-install.sh container-runtime/dist/index.js container-runtime/dist/node_modules/.package-lock.json core/target/$(RUST_ARCH)-unknown-linux-musl/release/start-container
|
||||||
ARCH=$(ARCH) ./container-runtime/update-image.sh
|
ARCH=$(ARCH) REQUIRES=qemu ./build/os-compat/run-compat.sh ./container-runtime/update-image.sh
|
||||||
|
|
||||||
build/lib/depends build/lib/conflicts: build/dpkg-deps/*
|
build/lib/depends build/lib/conflicts: $(ENVIRONMENT_FILE) $(PLATFORM_FILE) $(shell ls build/dpkg-deps/*)
|
||||||
build/dpkg-deps/generate.sh
|
PLATFORM=$(PLATFORM) ARCH=$(ARCH) build/dpkg-deps/generate.sh
|
||||||
|
|
||||||
$(FIRMWARE_ROMS): build/lib/firmware.json download-firmware.sh $(PLATFORM_FILE)
|
$(FIRMWARE_ROMS): build/lib/firmware.json download-firmware.sh $(PLATFORM_FILE)
|
||||||
./download-firmware.sh $(PLATFORM)
|
./download-firmware.sh $(PLATFORM)
|
||||||
|
|
||||||
system-images/compat/docker-images/$(ARCH).tar: $(COMPAT_SRC)
|
core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox: $(CORE_SRC) $(COMPRESSED_WEB_UIS) web/patchdb-ui-seed.json $(ENVIRONMENT_FILE)
|
||||||
cd system-images/compat && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-startbox.sh
|
||||||
|
touch core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox
|
||||||
|
|
||||||
system-images/utils/docker-images/$(ARCH).tar: $(UTILS_SRC)
|
core/target/$(RUST_ARCH)-unknown-linux-musl/release/start-container: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||||
cd system-images/utils && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
ARCH=$(ARCH) ./core/build-start-container.sh
|
||||||
|
touch core/target/$(RUST_ARCH)-unknown-linux-musl/release/start-container
|
||||||
|
|
||||||
system-images/binfmt/docker-images/$(ARCH).tar: $(BINFMT_SRC)
|
web/package-lock.json: web/package.json sdk/baseDist/package.json
|
||||||
cd system-images/binfmt && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
npm --prefix web i
|
||||||
|
touch web/package-lock.json
|
||||||
|
|
||||||
core/target/$(ARCH)-unknown-linux-musl/release/startbox: $(CORE_SRC) $(COMPRESSED_WEB_UIS) web/patchdb-ui-seed.json $(ENVIRONMENT_FILE)
|
web/node_modules/.package-lock.json: web/package-lock.json
|
||||||
ARCH=$(ARCH) ./core/build-startbox.sh
|
|
||||||
touch core/target/$(ARCH)-unknown-linux-musl/release/startbox
|
|
||||||
|
|
||||||
core/target/$(ARCH)-unknown-linux-musl/release/containerbox: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
|
||||||
ARCH=$(ARCH) ./core/build-containerbox.sh
|
|
||||||
touch core/target/$(ARCH)-unknown-linux-musl/release/containerbox
|
|
||||||
|
|
||||||
web/node_modules/.package-lock.json: web/package.json sdk/baseDist/package.json
|
|
||||||
npm --prefix web ci
|
npm --prefix web ci
|
||||||
touch web/node_modules/.package-lock.json
|
touch web/node_modules/.package-lock.json
|
||||||
|
|
||||||
@@ -298,11 +342,15 @@ web/dist/raw/setup-wizard/index.html: $(WEB_SETUP_WIZARD_SRC) $(WEB_SHARED_SRC)
|
|||||||
touch web/dist/raw/setup-wizard/index.html
|
touch web/dist/raw/setup-wizard/index.html
|
||||||
|
|
||||||
web/dist/raw/install-wizard/index.html: $(WEB_INSTALL_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular/.updated
|
web/dist/raw/install-wizard/index.html: $(WEB_INSTALL_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular/.updated
|
||||||
npm --prefix web run build:install-wiz
|
npm --prefix web run build:install
|
||||||
touch web/dist/raw/install-wizard/index.html
|
touch web/dist/raw/install-wizard/index.html
|
||||||
|
|
||||||
$(COMPRESSED_WEB_UIS): $(WEB_UIS) $(ENVIRONMENT_FILE)
|
web/dist/raw/start-tunnel/index.html: $(WEB_START_TUNNEL_SRC) $(WEB_SHARED_SRC) web/.angular/.updated
|
||||||
./compress-uis.sh
|
npm --prefix web run build:tunnel
|
||||||
|
touch web/dist/raw/start-tunnel/index.html
|
||||||
|
|
||||||
|
web/dist/static/%/index.html: web/dist/raw/%/index.html
|
||||||
|
./compress-uis.sh $*
|
||||||
|
|
||||||
web/config.json: $(GIT_HASH_FILE) web/config-sample.json
|
web/config.json: $(GIT_HASH_FILE) web/config-sample.json
|
||||||
jq '.useMocks = false' web/config-sample.json | jq '.gitHash = "$(shell cat GIT_HASH.txt)"' > web/config.json
|
jq '.useMocks = false' web/config-sample.json | jq '.gitHash = "$(shell cat GIT_HASH.txt)"' > web/config.json
|
||||||
@@ -326,11 +374,17 @@ uis: $(WEB_UIS)
|
|||||||
# this is a convenience step to build the UI
|
# this is a convenience step to build the UI
|
||||||
ui: web/dist/raw/ui
|
ui: web/dist/raw/ui
|
||||||
|
|
||||||
cargo-deps/aarch64-unknown-linux-musl/release/pi-beep:
|
cargo-deps/aarch64-unknown-linux-musl/release/pi-beep: ./build-cargo-dep.sh
|
||||||
ARCH=aarch64 ./build-cargo-dep.sh pi-beep
|
ARCH=aarch64 ./build-cargo-dep.sh pi-beep
|
||||||
|
|
||||||
cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console:
|
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/tokio-console: ./build-cargo-dep.sh
|
||||||
ARCH=$(ARCH) PREINSTALL="apk add musl-dev pkgconfig" ./build-cargo-dep.sh tokio-console
|
ARCH=$(ARCH) ./build-cargo-dep.sh tokio-console
|
||||||
|
touch $@
|
||||||
|
|
||||||
cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs:
|
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs: ./build-cargo-dep.sh
|
||||||
ARCH=$(ARCH) PREINSTALL="apk add fuse3 fuse3-dev fuse3-static musl-dev pkgconfig" ./build-cargo-dep.sh --git https://github.com/Start9Labs/start-fs.git startos-backup-fs
|
ARCH=$(ARCH) ./build-cargo-dep.sh --git https://github.com/Start9Labs/start-fs.git startos-backup-fs
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph: ./build-cargo-dep.sh
|
||||||
|
ARCH=$(ARCH) ./build-cargo-dep.sh flamegraph
|
||||||
|
touch $@
|
||||||
|
|||||||
@@ -13,9 +13,6 @@
|
|||||||
<a href="https://twitter.com/start9labs">
|
<a href="https://twitter.com/start9labs">
|
||||||
<img alt="X (formerly Twitter) Follow" src="https://img.shields.io/twitter/follow/start9labs">
|
<img alt="X (formerly Twitter) Follow" src="https://img.shields.io/twitter/follow/start9labs">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://mastodon.start9labs.com">
|
|
||||||
<img src="https://img.shields.io/mastodon/follow/000000001?domain=https%3A%2F%2Fmastodon.start9labs.com&label=Follow&style=social">
|
|
||||||
</a>
|
|
||||||
<a href="https://matrix.to/#/#community:matrix.start9labs.com">
|
<a href="https://matrix.to/#/#community:matrix.start9labs.com">
|
||||||
<img alt="Static Badge" src="https://img.shields.io/badge/community-matrix-yellow?logo=matrix">
|
<img alt="Static Badge" src="https://img.shields.io/badge/community-matrix-yellow?logo=matrix">
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
95
START-TUNNEL.md
Normal file
95
START-TUNNEL.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# StartTunnel
|
||||||
|
|
||||||
|
A self-hosted WireGuard VPN optimized for creating VLANs and reverse tunneling to personal servers.
|
||||||
|
|
||||||
|
You can think of StartTunnel as "virtual router in the cloud".
|
||||||
|
|
||||||
|
Use it for private remote access to self-hosted services running on a personal server, or to expose self-hosted services to the public Internet without revealing the host server's IP address.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Create Subnets**: Each subnet creates a private, virtual local area network (VLAN), similar to the LAN created by a home router.
|
||||||
|
|
||||||
|
- **Add Devices**: When you add a device (server, phone, laptop) to a subnet, it receives a LAN IP address on that subnet as well as a unique WireGuard config that must be copied, downloaded, or scanned into the device.
|
||||||
|
|
||||||
|
- **Forward Ports**: Forwarding a port creates a "reverse tunnel", exposing a specific port on a specific device to the public Internet.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Rent a low cost VPS. For most use cases, the cheapest option should be enough.
|
||||||
|
|
||||||
|
- It must have a dedicated public IP address.
|
||||||
|
- For compute (CPU), memory (RAM), and storage (disk), choose the minimum spec.
|
||||||
|
- For transfer (bandwidth), it depends on (1) your use case and (2) your home Internet's _upload_ speed. Even if you intend to serve large files or stream content from your server, there is no reason to pay for speeds that exceed your home Internet's upload speed.
|
||||||
|
|
||||||
|
1. Provision the VPS with the latest version of Debian.
|
||||||
|
|
||||||
|
1. Access the VPS via SSH.
|
||||||
|
|
||||||
|
1. Run the StartTunnel install script:
|
||||||
|
|
||||||
|
curl -fsSL https://start9labs.github.io/start-tunnel | sh
|
||||||
|
|
||||||
|
1. [Initialize the web interface](#web-interface) (recommended)
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
Simply re-run the install command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -fsSL https://start9labs.github.io/start-tunnel | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## CLI
|
||||||
|
|
||||||
|
By default, StartTunnel is managed via the `start-tunnel` command line interface, which is self-documented.
|
||||||
|
|
||||||
|
```
|
||||||
|
start-tunnel --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Web Interface
|
||||||
|
|
||||||
|
Enable the web interface (recommended in most cases) to access your StartTunnel from the browser or via API.
|
||||||
|
|
||||||
|
1. Initialize the web interface.
|
||||||
|
|
||||||
|
start-tunnel web init
|
||||||
|
|
||||||
|
1. If your VPS has multiple public IP addresses, you will be prompted to select the IP address at which to host the web interface.
|
||||||
|
|
||||||
|
1. When prompted, enter the port at which to host the web interface. The default is 8443, and we recommend using it. If you change the default, choose an uncommon port to avoid future conflicts.
|
||||||
|
|
||||||
|
1. To access your StartTunnel web interface securely over HTTPS, you need an SSL certificate. When prompted, select whether to autogenerate a certificate or provide your own. _This is only for accessing your StartTunnel web interface_.
|
||||||
|
|
||||||
|
1. You will receive a success message with 3 pieces of information:
|
||||||
|
|
||||||
|
- **<https://IP:port>**: the URL where you can reach your personal web interface.
|
||||||
|
- **Password**: an autogenerated password for your interface. If you lose/forget it, you can reset it using the start-tunnel CLI.
|
||||||
|
- **Root Certificate Authority**: the Root CA of your StartTunnel instance.
|
||||||
|
|
||||||
|
1. If you autogenerated your SSL certificate, visiting the `https://IP:port` URL in the browser will warn you that the website is insecure. This is expected. You have two options for getting past this warning:
|
||||||
|
- option 1 (recommended): [Trust your StartTunnel Root CA on your connecting device](#trusting-your-starttunnel-root-ca).
|
||||||
|
- Option 2: bypass the warning in the browser, creating a one-time security exception.
|
||||||
|
|
||||||
|
### Trusting your StartTunnel Root CA
|
||||||
|
|
||||||
|
1. Copy the contents of your Root CA (starting with -----BEGIN CERTIFICATE----- and ending with -----END CERTIFICATE-----).
|
||||||
|
|
||||||
|
2. Open a text editor:
|
||||||
|
|
||||||
|
- Linux: gedit, nano, or any editor
|
||||||
|
- Mac: TextEdit
|
||||||
|
- Windows: Notepad
|
||||||
|
|
||||||
|
3. Paste the contents of your Root CA.
|
||||||
|
|
||||||
|
4. Save the file with a `.crt` extension (e.g. `start-tunnel.crt`) (make sure it saves as plain text, not rich text).
|
||||||
|
|
||||||
|
5. Trust the Root CA on your client device(s):
|
||||||
|
|
||||||
|
- [Linux](https://staging.docs.start9.com/device-guides/linux/ca.html)
|
||||||
|
- [Mac](https://staging.docs.start9.com/device-guides/mac/ca.html)
|
||||||
|
- [Windows](https://staging.docs.start9.com/device-guides/windows/ca.html)
|
||||||
|
- [Android/Graphene](https://staging.docs.start9.com/device-guides/android/ca.html)
|
||||||
|
- [iOS](https://staging.docs.start9.com/device-guides/ios/ca.html)
|
||||||
201
agents/VERSION_BUMP.md
Normal file
201
agents/VERSION_BUMP.md
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
# StartOS Version Bump Guide
|
||||||
|
|
||||||
|
This document explains how to bump the StartOS version across the entire codebase.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
When bumping from version `X.Y.Z-alpha.N` to `X.Y.Z-alpha.N+1`, you need to update files in multiple locations across the repository. The `// VERSION_BUMP` comment markers indicate where changes are needed.
|
||||||
|
|
||||||
|
## Files to Update
|
||||||
|
|
||||||
|
### 1. Core Rust Crate Version
|
||||||
|
|
||||||
|
**File: `core/startos/Cargo.toml`**
|
||||||
|
|
||||||
|
Update the version string (line ~18):
|
||||||
|
|
||||||
|
```toml
|
||||||
|
version = "0.4.0-alpha.15" # VERSION_BUMP
|
||||||
|
```
|
||||||
|
|
||||||
|
**File: `core/Cargo.lock`**
|
||||||
|
|
||||||
|
This file is auto-generated. After updating `Cargo.toml`, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd core
|
||||||
|
cargo check
|
||||||
|
```
|
||||||
|
|
||||||
|
This will update the version in `Cargo.lock` automatically.
|
||||||
|
|
||||||
|
### 2. Create New Version Migration Module
|
||||||
|
|
||||||
|
**File: `core/startos/src/version/vX_Y_Z_alpha_N+1.rs`**
|
||||||
|
|
||||||
|
Create a new version file by copying the previous version and updating:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use exver::{PreReleaseSegment, VersionRange};
|
||||||
|
|
||||||
|
use super::v0_3_5::V0_3_0_COMPAT;
|
||||||
|
use super::{VersionT, v0_4_0_alpha_14}; // Update to previous version
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref V0_4_0_alpha_15: exver::Version = exver::Version::new(
|
||||||
|
[0, 4, 0],
|
||||||
|
[PreReleaseSegment::String("alpha".into()), 15.into()] // Update number
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Version;
|
||||||
|
|
||||||
|
impl VersionT for Version {
|
||||||
|
type Previous = v0_4_0_alpha_14::Version; // Update to previous version
|
||||||
|
type PreUpRes = ();
|
||||||
|
|
||||||
|
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn semver(self) -> exver::Version {
|
||||||
|
V0_4_0_alpha_15.clone() // Update version name
|
||||||
|
}
|
||||||
|
fn compat(self) -> &'static VersionRange {
|
||||||
|
&V0_3_0_COMPAT
|
||||||
|
}
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||||
|
// Add migration logic here if needed
|
||||||
|
Ok(Value::Null)
|
||||||
|
}
|
||||||
|
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||||
|
// Add rollback logic here if needed
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Update Version Module Registry
|
||||||
|
|
||||||
|
**File: `core/startos/src/version/mod.rs`**
|
||||||
|
|
||||||
|
Make changes in **5 locations**:
|
||||||
|
|
||||||
|
#### Location 1: Module Declaration (~line 57)
|
||||||
|
|
||||||
|
Add the new module after the previous version:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
mod v0_4_0_alpha_14;
|
||||||
|
mod v0_4_0_alpha_15; // Add this
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Location 2: Current Type Alias (~line 59)
|
||||||
|
|
||||||
|
Update the `Current` type and move the `// VERSION_BUMP` comment:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub type Current = v0_4_0_alpha_15::Version; // VERSION_BUMP
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Location 3: Version Enum (~line 175)
|
||||||
|
|
||||||
|
Remove `// VERSION_BUMP` from the previous version, add new variant, add comment:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
V0_4_0_alpha_14(Wrapper<v0_4_0_alpha_14::Version>),
|
||||||
|
V0_4_0_alpha_15(Wrapper<v0_4_0_alpha_15::Version>), // VERSION_BUMP
|
||||||
|
Other(exver::Version),
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Location 4: as_version_t() Match (~line 233)
|
||||||
|
|
||||||
|
Remove `// VERSION_BUMP`, add new match arm, add comment:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
Self::V0_4_0_alpha_14(v) => DynVersion(Box::new(v.0)),
|
||||||
|
Self::V0_4_0_alpha_15(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||||
|
Self::Other(v) => {
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Location 5: as_exver() Match (~line 284, inside #[cfg(test)])
|
||||||
|
|
||||||
|
Remove `// VERSION_BUMP`, add new match arm, add comment:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
Version::V0_4_0_alpha_14(Wrapper(x)) => x.semver(),
|
||||||
|
Version::V0_4_0_alpha_15(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||||
|
Version::Other(x) => x.clone(),
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. SDK TypeScript Version
|
||||||
|
|
||||||
|
**File: `sdk/package/lib/StartSdk.ts`**
|
||||||
|
|
||||||
|
Update the OSVersion constant (~line 64):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const OSVersion = testTypeVersion("0.4.0-alpha.15");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Web UI Package Version
|
||||||
|
|
||||||
|
**File: `web/package.json`**
|
||||||
|
|
||||||
|
Update the version field:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "startos-ui",
|
||||||
|
"version": "0.4.0-alpha.15",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**File: `web/package-lock.json`**
|
||||||
|
|
||||||
|
This file is auto-generated, but it's faster to update manually. Find all instances of "startos-ui" and update the version field.
|
||||||
|
|
||||||
|
## Verification Step
|
||||||
|
|
||||||
|
```
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
## VERSION_BUMP Comment Pattern
|
||||||
|
|
||||||
|
The `// VERSION_BUMP` comment serves as a marker for where to make changes next time:
|
||||||
|
|
||||||
|
- Always **remove** it from the old location
|
||||||
|
- **Add** the new version entry
|
||||||
|
- **Move** the comment to mark the new location
|
||||||
|
|
||||||
|
This pattern helps you quickly find all the places that need updating in the next version bump.
|
||||||
|
|
||||||
|
## Summary Checklist
|
||||||
|
|
||||||
|
- [ ] Update `core/startos/Cargo.toml` version
|
||||||
|
- [ ] Create new `core/startos/src/version/vX_Y_Z_alpha_N+1.rs` file
|
||||||
|
- [ ] Update `core/startos/src/version/mod.rs` in 5 locations
|
||||||
|
- [ ] Run `cargo check` to update `core/Cargo.lock`
|
||||||
|
- [ ] Update `sdk/package/lib/StartSdk.ts` OSVersion
|
||||||
|
- [ ] Update `web/package.json` and `web/package-lock.json` version
|
||||||
|
- [ ] Verify all changes compile/build successfully
|
||||||
|
|
||||||
|
## Migration Logic
|
||||||
|
|
||||||
|
The `up()` and `down()` methods in the version file handle database migrations:
|
||||||
|
|
||||||
|
- **up()**: Migrates the database from the previous version to this version
|
||||||
|
- **down()**: Rolls back from this version to the previous version
|
||||||
|
- **pre_up()**: Runs before migration, useful for pre-migration checks or data gathering
|
||||||
|
|
||||||
|
If no migration is needed, return `Ok(Value::Null)` for `up()` and `Ok(())` for `down()`.
|
||||||
|
|
||||||
|
For complex migrations, you may need to:
|
||||||
|
|
||||||
|
1. Update `type PreUpRes` to pass data between `pre_up()` and `up()`
|
||||||
|
2. Implement database transformations in the `up()` method
|
||||||
|
3. Implement reverse transformations in `down()` for rollback support
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
PROJECT=${PROJECT:-"startos"}
|
||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
PLATFORM="$(if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi)"
|
PLATFORM="$(if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi)"
|
||||||
@@ -16,4 +18,4 @@ if [ -n "$STARTOS_ENV" ]; then
|
|||||||
VERSION_FULL="$VERSION_FULL~${STARTOS_ENV}"
|
VERSION_FULL="$VERSION_FULL~${STARTOS_ENV}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -n "startos-${VERSION_FULL}_${PLATFORM}"
|
echo -n "${PROJECT}-${VERSION_FULL}_${PLATFORM}"
|
||||||
@@ -8,27 +8,22 @@ if [ "$0" != "./build-cargo-dep.sh" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
USE_TTY=
|
|
||||||
if tty -s; then
|
|
||||||
USE_TTY="-it"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DOCKER_PLATFORM="linux/${ARCH}"
|
RUST_ARCH="$ARCH"
|
||||||
if [ "$ARCH" = aarch64 ] || [ "$ARCH" = arm64 ]; then
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
DOCKER_PLATFORM="linux/arm64"
|
RUST_ARCH="riscv64gc"
|
||||||
elif [ "$ARCH" = x86_64 ]; then
|
|
||||||
DOCKER_PLATFORM="linux/amd64"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p cargo-deps
|
mkdir -p cargo-deps
|
||||||
alias 'rust-musl-builder'='docker run $USE_TTY --platform=${DOCKER_PLATFORM} --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)"/cargo-deps:/home/rust/src -w /home/rust/src -P rust:alpine'
|
|
||||||
|
|
||||||
PREINSTALL=${PREINSTALL:-true}
|
source core/builder-alias.sh
|
||||||
|
|
||||||
rust-musl-builder sh -c "$PREINSTALL && cargo install $* --target-dir /home/rust/src --target=$ARCH-unknown-linux-musl"
|
RUSTFLAGS="-C target-feature=+crt-static"
|
||||||
sudo chown -R $USER cargo-deps
|
|
||||||
sudo chown -R $USER ~/.cargo
|
rust-zig-builder cargo-zigbuild install $* --target-dir /workdir/cargo-deps/ --target=$RUST_ARCH-unknown-linux-musl
|
||||||
|
if [ "$(ls -nd "cargo-deps/$RUST_ARCH-unknown-linux-musl/release/${!#}" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
|
rust-zig-builder sh -c "chown -R $UID:$UID cargo-deps && chown -R $UID:$UID /usr/local/cargo"
|
||||||
|
fi
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ bmon
|
|||||||
btrfs-progs
|
btrfs-progs
|
||||||
ca-certificates
|
ca-certificates
|
||||||
cifs-utils
|
cifs-utils
|
||||||
|
conntrack
|
||||||
cryptsetup
|
cryptsetup
|
||||||
curl
|
curl
|
||||||
dnsutils
|
|
||||||
dmidecode
|
dmidecode
|
||||||
dnsutils
|
dnsutils
|
||||||
dosfstools
|
dosfstools
|
||||||
@@ -19,6 +19,7 @@ exfatprogs
|
|||||||
flashrom
|
flashrom
|
||||||
fuse3
|
fuse3
|
||||||
grub-common
|
grub-common
|
||||||
|
grub-efi
|
||||||
htop
|
htop
|
||||||
httpdirfs
|
httpdirfs
|
||||||
iotop
|
iotop
|
||||||
@@ -36,13 +37,14 @@ man-db
|
|||||||
ncdu
|
ncdu
|
||||||
net-tools
|
net-tools
|
||||||
network-manager
|
network-manager
|
||||||
|
nfs-common
|
||||||
nvme-cli
|
nvme-cli
|
||||||
nyx
|
nyx
|
||||||
openssh-server
|
openssh-server
|
||||||
podman
|
podman
|
||||||
postgresql
|
|
||||||
psmisc
|
psmisc
|
||||||
qemu-guest-agent
|
qemu-guest-agent
|
||||||
|
rfkill
|
||||||
rsync
|
rsync
|
||||||
samba-common-bin
|
samba-common-bin
|
||||||
smartmontools
|
smartmontools
|
||||||
|
|||||||
@@ -5,11 +5,15 @@ set -e
|
|||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
IFS="-" read -ra FEATURES <<< "$ENVIRONMENT"
|
IFS="-" read -ra FEATURES <<< "$ENVIRONMENT"
|
||||||
|
FEATURES+=("${ARCH}")
|
||||||
|
if [ "$ARCH" != "$PLATFORM" ]; then
|
||||||
|
FEATURES+=("${PLATFORM}")
|
||||||
|
fi
|
||||||
|
|
||||||
feature_file_checker='
|
feature_file_checker='
|
||||||
/^#/ { next }
|
/^#/ { next }
|
||||||
/^\+ [a-z0-9]+$/ { next }
|
/^\+ [a-z0-9.-]+$/ { next }
|
||||||
/^- [a-z0-9]+$/ { next }
|
/^- [a-z0-9.-]+$/ { next }
|
||||||
{ exit 1 }
|
{ exit 1 }
|
||||||
'
|
'
|
||||||
|
|
||||||
@@ -30,8 +34,8 @@ for type in conflicts depends; do
|
|||||||
for feature in ${FEATURES[@]}; do
|
for feature in ${FEATURES[@]}; do
|
||||||
file="$feature.$type"
|
file="$feature.$type"
|
||||||
if [ -f $file ]; then
|
if [ -f $file ]; then
|
||||||
if grep "^- $pkg$" $file; then
|
if grep "^- $pkg$" $file > /dev/null; then
|
||||||
SKIP=1
|
SKIP=yes
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
10
build/dpkg-deps/raspberrypi.depends
Normal file
10
build/dpkg-deps/raspberrypi.depends
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
- grub-efi
|
||||||
|
+ parted
|
||||||
|
+ raspberrypi-net-mods
|
||||||
|
+ raspberrypi-sys-mods
|
||||||
|
+ raspi-config
|
||||||
|
+ raspi-firmware
|
||||||
|
+ raspi-utils
|
||||||
|
+ rpi-eeprom
|
||||||
|
+ rpi-update
|
||||||
|
+ rpi.gpio-common
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
+ gdb
|
+ gdb
|
||||||
+ heaptrack
|
+ heaptrack
|
||||||
|
+ linux-perf
|
||||||
1
build/dpkg-deps/x86_64.depends
Normal file
1
build/dpkg-deps/x86_64.depends
Normal file
@@ -0,0 +1 @@
|
|||||||
|
+ grub-pc-bin
|
||||||
147
build/lib/motd
147
build/lib/motd
@@ -1,34 +1,123 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
printf "\n"
|
|
||||||
printf "Welcome to\n"
|
|
||||||
cat << "ASCII"
|
|
||||||
|
|
||||||
███████
|
parse_essential_db_info() {
|
||||||
█ █ █
|
DB_DUMP="/tmp/startos_db.json"
|
||||||
█ █ █ █
|
|
||||||
█ █ █ █
|
|
||||||
█ █ █ █
|
|
||||||
█ █ █ █
|
|
||||||
█ █
|
|
||||||
███████
|
|
||||||
|
|
||||||
_____ __ ___ __ __
|
if command -v start-cli >/dev/null 2>&1; then
|
||||||
(_ | /\ |__) | / \(_
|
start-cli db dump > "$DB_DUMP" 2>/dev/null || return 1
|
||||||
__) | / \| \ | \__/__)
|
else
|
||||||
ASCII
|
return 1
|
||||||
printf " v$(cat /usr/lib/startos/VERSION.txt)\n\n"
|
fi
|
||||||
printf " %s (%s %s)\n" "$(uname -o)" "$(uname -r)" "$(uname -m)"
|
|
||||||
printf " Git Hash: $(cat /usr/lib/startos/GIT_HASH.txt)"
|
if command -v jq >/dev/null 2>&1 && [ -f "$DB_DUMP" ]; then
|
||||||
if [ -n "$(cat /usr/lib/startos/ENVIRONMENT.txt)" ]; then
|
HOSTNAME=$(jq -r '.value.serverInfo.hostname // "unknown"' "$DB_DUMP" 2>/dev/null)
|
||||||
printf " ~ $(cat /usr/lib/startos/ENVIRONMENT.txt)\n"
|
VERSION=$(jq -r '.value.serverInfo.version // "unknown"' "$DB_DUMP" 2>/dev/null)
|
||||||
else
|
RAM_BYTES=$(jq -r '.value.serverInfo.ram // 0' "$DB_DUMP" 2>/dev/null)
|
||||||
printf "\n"
|
WAN_IP=$(jq -r '.value.serverInfo.network.gateways[].ipInfo.wanIp // "unknown"' "$DB_DUMP" 2>/dev/null | head -1)
|
||||||
|
NTP_SYNCED=$(jq -r '.value.serverInfo.ntpSynced // false' "$DB_DUMP" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ "$RAM_BYTES" != "0" ] && [ "$RAM_BYTES" != "null" ]; then
|
||||||
|
RAM_GB=$(echo "scale=1; $RAM_BYTES / 1073741824" | bc 2>/dev/null || echo "unknown")
|
||||||
|
else
|
||||||
|
RAM_GB="unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
RUNNING_SERVICES=$(jq -r '[.value.packageData[] | select(.statusInfo.started != null)] | length' "$DB_DUMP" 2>/dev/null)
|
||||||
|
TOTAL_SERVICES=$(jq -r '.value.packageData | length' "$DB_DUMP" 2>/dev/null)
|
||||||
|
|
||||||
|
rm -f "$DB_DUMP"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
rm -f "$DB_DUMP" 2>/dev/null
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
DB_INFO_AVAILABLE=0
|
||||||
|
if parse_essential_db_info; then
|
||||||
|
DB_INFO_AVAILABLE=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf "\n"
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ] && [ "$VERSION" != "unknown" ]; then
|
||||||
printf " * Documentation: https://docs.start9.com\n"
|
version_display="v$VERSION"
|
||||||
printf " * Management: https://%s.local\n" "$(hostname)"
|
else
|
||||||
printf " * Support: https://start9.com/contact\n"
|
version_display="v$(cat /usr/lib/startos/VERSION.txt 2>/dev/null || echo 'unknown')"
|
||||||
printf " * Source Code: https://github.com/Start9Labs/start-os\n"
|
fi
|
||||||
printf " * License: MIT\n"
|
|
||||||
printf "\n"
|
printf "\n\033[1;37m ▄▄▀▀▀▀▀▄▄\033[0m\n"
|
||||||
|
printf "\033[1;37m ▄▀ ▄ ▀▄ ▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄ ▄▄▄▄▄ ▄▄▄▄▄▄▄ \033[1;31m▄██████▄ ▄██████\033[0m\n"
|
||||||
|
printf "\033[1;37m █ █ █ █ █ █ █ █ █ ▀▄ █ \033[1;31m██ ██ ██ \033[0m\n"
|
||||||
|
printf "\033[1;37m█ █ █ █ ▀▄▄▄▄ █ █ █ █ ▄▄▄▀ █ \033[1;31m██ ██ ▀█████▄\033[0m\n"
|
||||||
|
printf "\033[1;37m█ █ █ █ █ █ █ █ █ ▀▄ █ \033[1;31m██ ██ ██\033[0m\n"
|
||||||
|
printf "\033[1;37m █ █ █ █ ▄▄▄▄▄▀ █ █ █ █ ▀▄ █ \033[1;31m▀██████▀ ██████▀\033[0m\n"
|
||||||
|
printf "\033[1;37m █ █\033[0m\n"
|
||||||
|
printf "\033[1;37m ▀▀▄▄▄▀▀ $version_display\033[0m\n\n"
|
||||||
|
|
||||||
|
uptime_str=$(uptime | awk -F'up ' '{print $2}' | awk -F',' '{print $1}' | sed 's/^ *//')
|
||||||
|
|
||||||
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ] && [ "$RAM_GB" != "unknown" ]; then
|
||||||
|
memory_used=$(free -m | awk 'NR==2{printf "%.0fMB", $3}')
|
||||||
|
memory_display="$memory_used / ${RAM_GB}GB"
|
||||||
|
else
|
||||||
|
memory_display=$(free -m | awk 'NR==2{printf "%.0fMB / %.0fMB", $3, $2}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
root_usage=$(df -h / | awk 'NR==2{printf "%s (%s free)", $5, $4}')
|
||||||
|
|
||||||
|
if [ -d "/media/startos/data/package-data" ]; then
|
||||||
|
data_usage=$(df -h /media/startos/data/package-data | awk 'NR==2{printf "%s (%s free)", $5, $4}')
|
||||||
|
else
|
||||||
|
data_usage="N/A"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ]; then
|
||||||
|
services_text="$RUNNING_SERVICES/$TOTAL_SERVICES running"
|
||||||
|
else
|
||||||
|
services_text="Unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local_ip=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1)
|
||||||
|
if [ -z "$local_ip" ]; then local_ip="N/A"; fi
|
||||||
|
|
||||||
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ] && [ "$WAN_IP" != "unknown" ]; then
|
||||||
|
wan_ip="$WAN_IP"
|
||||||
|
else
|
||||||
|
wan_ip="N/A"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf " \033[1;37m┌─ SYSTEM STATUS ───────────────────────────────────────────────────┐\033[0m\n"
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;33m%-22s\033[0m %-8s \033[0;33m%-23s\033[0m \033[1;37m│\033[0m\n" "Uptime:" "$uptime_str" "Memory:" "$memory_display"
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;33m%-22s\033[0m %-8s \033[0;33m%-23s\033[0m \033[1;37m│\033[0m\n" "Root:" "$root_usage" "Data:" "$data_usage"
|
||||||
|
|
||||||
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ]; then
|
||||||
|
if [ "$RUNNING_SERVICES" -eq "$TOTAL_SERVICES" ] && [ "$TOTAL_SERVICES" -gt 0 ]; then
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;32m%-22s\033[0m %-8s \033[0;33m%-23s\033[0m \033[1;37m│\033[0m\n" "Services:" "$services_text" "WAN:" "$wan_ip"
|
||||||
|
elif [ "$RUNNING_SERVICES" -gt 0 ]; then
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;33m%-22s\033[0m %-8s \033[0;33m%-23s\033[0m \033[1;37m│\033[0m\n" "Services:" "$services_text" "WAN:" "$wan_ip"
|
||||||
|
else
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;31m%-22s\033[0m %-8s \033[0;33m%-23s\033[0m \033[1;37m│\033[0m\n" "Services:" "$services_text" "WAN:" "$wan_ip"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;37m%-22s\033[0m %-8s \033[0;33m%-23s\033[0m \033[1;37m│\033[0m\n" "Services:" "$services_text" "WAN:" "$wan_ip"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ] && [ "$NTP_SYNCED" = "true" ]; then
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;33m%-22s\033[0m %-8s \033[0;32m%-23s\033[0m \033[1;37m│\033[0m\n" "Local:" "$local_ip" "NTP:" "Synced"
|
||||||
|
elif [ "$DB_INFO_AVAILABLE" -eq 1 ] && [ "$NTP_SYNCED" = "false" ]; then
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;33m%-22s\033[0m %-8s \033[0;31m%-23s\033[0m \033[1;37m│\033[0m\n" "Local:" "$local_ip" "NTP:" "Not Synced"
|
||||||
|
else
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;33m%-22s\033[0m %-8s \033[0;37m%-23s\033[0m \033[1;37m│\033[0m\n" "Local:" "$local_ip" "NTP:" "Unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf " \033[1;37m└───────────────────────────────────────────────────────────────────┘\033[0m"
|
||||||
|
|
||||||
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ] && [ "$HOSTNAME" != "unknown" ]; then
|
||||||
|
web_url="https://$HOSTNAME.local"
|
||||||
|
else
|
||||||
|
web_url="https://$(hostname).local"
|
||||||
|
fi
|
||||||
|
printf "\n \033[1;37m┌──────────────────────────────────────────────────── QUICK ACCESS ─┐\033[0m\n"
|
||||||
|
printf " \033[1;37m│\033[0m Web Interface: \033[0;36m%-50s\033[0m \033[1;37m│\033[0m\n" "$web_url"
|
||||||
|
printf " \033[1;37m│\033[0m Documentation: \033[0;36m%-50s\033[0m \033[1;37m│\033[0m\n" "https://staging.docs.start9.com"
|
||||||
|
printf " \033[1;37m│\033[0m Support: \033[0;36m%-50s\033[0m \033[1;37m│\033[0m\n" "https://start9.com/contact"
|
||||||
|
printf " \033[1;37m└───────────────────────────────────────────────────────────────────┘\033[0m\n\n"
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
|
|
||||||
if cat /sys/class/drm/*/status | grep -qw connected; then
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SOURCE_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
SOURCE_DIR="$(dirname $(realpath "${BASH_SOURCE[0]}"))"
|
||||||
|
|
||||||
if [ "$UID" -ne 0 ]; then
|
if [ "$UID" -ne 0 ]; then
|
||||||
>&2 echo 'Must be run as root'
|
>&2 echo 'Must be run as root'
|
||||||
@@ -10,24 +10,24 @@ fi
|
|||||||
POSITIONAL_ARGS=()
|
POSITIONAL_ARGS=()
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
--no-sync)
|
--no-sync)
|
||||||
NO_SYNC=1
|
NO_SYNC=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--create)
|
--create)
|
||||||
ONLY_CREATE=1
|
ONLY_CREATE=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-*|--*)
|
-*|--*)
|
||||||
echo "Unknown option $1"
|
echo "Unknown option $1"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
POSITIONAL_ARGS+=("$1") # save positional arg
|
POSITIONAL_ARGS+=("$1") # save positional arg
|
||||||
shift # past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
|
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
|
||||||
@@ -35,7 +35,7 @@ set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
|
|||||||
if [ -z "$NO_SYNC" ]; then
|
if [ -z "$NO_SYNC" ]; then
|
||||||
echo 'Syncing...'
|
echo 'Syncing...'
|
||||||
umount -R /media/startos/next 2> /dev/null
|
umount -R /media/startos/next 2> /dev/null
|
||||||
umount -R /media/startos/upper 2> /dev/null
|
umount /media/startos/upper 2> /dev/null
|
||||||
rm -rf /media/startos/upper /media/startos/next
|
rm -rf /media/startos/upper /media/startos/next
|
||||||
mkdir /media/startos/upper
|
mkdir /media/startos/upper
|
||||||
mount -t tmpfs tmpfs /media/startos/upper
|
mount -t tmpfs tmpfs /media/startos/upper
|
||||||
@@ -43,8 +43,6 @@ if [ -z "$NO_SYNC" ]; then
|
|||||||
mount -t overlay \
|
mount -t overlay \
|
||||||
-olowerdir=/media/startos/current,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \
|
-olowerdir=/media/startos/current,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \
|
||||||
overlay /media/startos/next
|
overlay /media/startos/next
|
||||||
mkdir -p /media/startos/next/media/startos/root
|
|
||||||
mount --bind /media/startos/root /media/startos/next/media/startos/root
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$ONLY_CREATE" ]; then
|
if [ -n "$ONLY_CREATE" ]; then
|
||||||
@@ -56,12 +54,18 @@ mkdir -p /media/startos/next/dev
|
|||||||
mkdir -p /media/startos/next/sys
|
mkdir -p /media/startos/next/sys
|
||||||
mkdir -p /media/startos/next/proc
|
mkdir -p /media/startos/next/proc
|
||||||
mkdir -p /media/startos/next/boot
|
mkdir -p /media/startos/next/boot
|
||||||
|
mkdir -p /media/startos/next/media/startos/root
|
||||||
mount --bind /run /media/startos/next/run
|
mount --bind /run /media/startos/next/run
|
||||||
mount --bind /tmp /media/startos/next/tmp
|
mount --bind /tmp /media/startos/next/tmp
|
||||||
mount --bind /dev /media/startos/next/dev
|
mount --bind /dev /media/startos/next/dev
|
||||||
mount --bind /sys /media/startos/next/sys
|
mount --bind /sys /media/startos/next/sys
|
||||||
mount --bind /proc /media/startos/next/proc
|
mount --bind /proc /media/startos/next/proc
|
||||||
mount --bind /boot /media/startos/next/boot
|
mount --bind /boot /media/startos/next/boot
|
||||||
|
mount --bind /media/startos/root /media/startos/next/media/startos/root
|
||||||
|
|
||||||
|
if mountpoint /sys/firmware/efi/efivars 2> /dev/null; then
|
||||||
|
mount --bind /sys/firmware/efi/efivars /media/startos/next/sys/firmware/efi/efivars
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$*" ]; then
|
if [ -z "$*" ]; then
|
||||||
chroot /media/startos/next
|
chroot /media/startos/next
|
||||||
@@ -71,6 +75,10 @@ else
|
|||||||
CHROOT_RES=$?
|
CHROOT_RES=$?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if mountpoint /media/startos/next/sys/firmware/efi/efivars 2> /dev/null; then
|
||||||
|
umount /media/startos/next/sys/firmware/efi/efivars
|
||||||
|
fi
|
||||||
|
|
||||||
umount /media/startos/next/run
|
umount /media/startos/next/run
|
||||||
umount /media/startos/next/tmp
|
umount /media/startos/next/tmp
|
||||||
umount /media/startos/next/dev
|
umount /media/startos/next/dev
|
||||||
@@ -87,11 +95,12 @@ if [ "$CHROOT_RES" -eq 0 ]; then
|
|||||||
|
|
||||||
echo 'Upgrading...'
|
echo 'Upgrading...'
|
||||||
|
|
||||||
|
rm -f /media/startos/images/next.squashfs
|
||||||
if ! time mksquashfs /media/startos/next /media/startos/images/next.squashfs -b 4096 -comp gzip; then
|
if ! time mksquashfs /media/startos/next /media/startos/images/next.squashfs -b 4096 -comp gzip; then
|
||||||
umount -R /media/startos/next
|
umount -l /media/startos/next
|
||||||
umount -R /media/startos/upper
|
umount -l /media/startos/upper
|
||||||
rm -rf /media/startos/upper /media/startos/next
|
rm -rf /media/startos/upper /media/startos/next
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
hash=$(b3sum /media/startos/images/next.squashfs | head -c 32)
|
hash=$(b3sum /media/startos/images/next.squashfs | head -c 32)
|
||||||
mv /media/startos/images/next.squashfs /media/startos/images/${hash}.rootfs
|
mv /media/startos/images/next.squashfs /media/startos/images/${hash}.rootfs
|
||||||
@@ -103,5 +112,5 @@ if [ "$CHROOT_RES" -eq 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
umount -R /media/startos/next
|
umount -R /media/startos/next
|
||||||
umount -R /media/startos/upper
|
umount /media/startos/upper
|
||||||
rm -rf /media/startos/upper /media/startos/next
|
rm -rf /media/startos/upper /media/startos/next
|
||||||
@@ -27,7 +27,6 @@ user_pref("browser.crashReports.unsubmittedCheck.autoSubmit2", false);
|
|||||||
user_pref("browser.newtabpage.activity-stream.feeds.asrouterfeed", false);
|
user_pref("browser.newtabpage.activity-stream.feeds.asrouterfeed", false);
|
||||||
user_pref("browser.newtabpage.activity-stream.feeds.topsites", false);
|
user_pref("browser.newtabpage.activity-stream.feeds.topsites", false);
|
||||||
user_pref("browser.newtabpage.activity-stream.showSponsoredTopSites", false);
|
user_pref("browser.newtabpage.activity-stream.showSponsoredTopSites", false);
|
||||||
user_pref("browser.onboarding.enabled", false);
|
|
||||||
user_pref("browser.ping-centre.telemetry", false);
|
user_pref("browser.ping-centre.telemetry", false);
|
||||||
user_pref("browser.pocket.enabled", false);
|
user_pref("browser.pocket.enabled", false);
|
||||||
user_pref("browser.safebrowsing.blockedURIs.enabled", false);
|
user_pref("browser.safebrowsing.blockedURIs.enabled", false);
|
||||||
@@ -43,7 +42,7 @@ user_pref("browser.startup.homepage_override.mstone", "ignore");
|
|||||||
user_pref("browser.theme.content-theme", 0);
|
user_pref("browser.theme.content-theme", 0);
|
||||||
user_pref("browser.theme.toolbar-theme", 0);
|
user_pref("browser.theme.toolbar-theme", 0);
|
||||||
user_pref("browser.urlbar.groupLabels.enabled", false);
|
user_pref("browser.urlbar.groupLabels.enabled", false);
|
||||||
user_pref("browser.urlbar.suggest.searches" false);
|
user_pref("browser.urlbar.suggest.searches", false);
|
||||||
user_pref("datareporting.policy.firstRunURL", "");
|
user_pref("datareporting.policy.firstRunURL", "");
|
||||||
user_pref("datareporting.healthreport.service.enabled", false);
|
user_pref("datareporting.healthreport.service.enabled", false);
|
||||||
user_pref("datareporting.healthreport.uploadEnabled", false);
|
user_pref("datareporting.healthreport.uploadEnabled", false);
|
||||||
@@ -52,10 +51,9 @@ user_pref("dom.securecontext.allowlist_onions", true);
|
|||||||
user_pref("dom.securecontext.whitelist_onions", true);
|
user_pref("dom.securecontext.whitelist_onions", true);
|
||||||
user_pref("experiments.enabled", false);
|
user_pref("experiments.enabled", false);
|
||||||
user_pref("experiments.activeExperiment", false);
|
user_pref("experiments.activeExperiment", false);
|
||||||
user_pref("experiments.supported", false);
|
|
||||||
user_pref("extensions.activeThemeID", "firefox-compact-dark@mozilla.org");
|
user_pref("extensions.activeThemeID", "firefox-compact-dark@mozilla.org");
|
||||||
user_pref("extensions.blocklist.enabled", false);
|
user_pref("extensions.blocklist.enabled", false);
|
||||||
user_pref("extensions.getAddons.cache.enabled", false);
|
user_pref("extensions.htmlaboutaddons.recommendations.enabled", false);
|
||||||
user_pref("extensions.pocket.enabled", false);
|
user_pref("extensions.pocket.enabled", false);
|
||||||
user_pref("extensions.update.enabled", false);
|
user_pref("extensions.update.enabled", false);
|
||||||
user_pref("extensions.shield-recipe-client.enabled", false);
|
user_pref("extensions.shield-recipe-client.enabled", false);
|
||||||
@@ -66,9 +64,15 @@ user_pref("messaging-system.rsexperimentloader.enabled", false);
|
|||||||
user_pref("network.allow-experiments", false);
|
user_pref("network.allow-experiments", false);
|
||||||
user_pref("network.captive-portal-service.enabled", false);
|
user_pref("network.captive-portal-service.enabled", false);
|
||||||
user_pref("network.connectivity-service.enabled", false);
|
user_pref("network.connectivity-service.enabled", false);
|
||||||
user_pref("network.proxy.autoconfig_url", "file:///usr/lib/startos/proxy.pac");
|
user_pref("network.proxy.socks", "10.0.3.1");
|
||||||
|
user_pref("network.proxy.socks_port", 9050);
|
||||||
|
user_pref("network.proxy.socks_version", 5);
|
||||||
user_pref("network.proxy.socks_remote_dns", true);
|
user_pref("network.proxy.socks_remote_dns", true);
|
||||||
user_pref("network.proxy.type", 2);
|
user_pref("network.proxy.type", 1);
|
||||||
|
user_pref("privacy.resistFingerprinting", true);
|
||||||
|
//Enable letterboxing if we want the window size sent to the server to snap to common resolutions:
|
||||||
|
//user_pref("privacy.resistFingerprinting.letterboxing", true);
|
||||||
|
user_pref("privacy.trackingprotection.enabled", true);
|
||||||
user_pref("signon.rememberSignons", false);
|
user_pref("signon.rememberSignons", false);
|
||||||
user_pref("toolkit.telemetry.archive.enabled", false);
|
user_pref("toolkit.telemetry.archive.enabled", false);
|
||||||
user_pref("toolkit.telemetry.bhrPing.enabled", false);
|
user_pref("toolkit.telemetry.bhrPing.enabled", false);
|
||||||
@@ -81,6 +85,17 @@ user_pref("toolkit.telemetry.shutdownPingSender.enabled", false);
|
|||||||
user_pref("toolkit.telemetry.unified", false);
|
user_pref("toolkit.telemetry.unified", false);
|
||||||
user_pref("toolkit.telemetry.updatePing.enabled", false);
|
user_pref("toolkit.telemetry.updatePing.enabled", false);
|
||||||
user_pref("toolkit.telemetry.cachedClientID", "");
|
user_pref("toolkit.telemetry.cachedClientID", "");
|
||||||
|
//Blocking automatic Mozilla CDN server requests
|
||||||
|
user_pref("extensions.getAddons.showPane", false);
|
||||||
|
user_pref("extensions.getAddons.cache.enabled", false);
|
||||||
|
//user_pref("services.settings.server", ""); // Remote settings server (HSTS preload updates and Cerfiticate Revocation Lists are fetched)
|
||||||
|
user_pref("browser.aboutHomeSnippets.updateUrl", "");
|
||||||
|
user_pref("browser.newtabpage.activity-stream.feeds.snippets", false);
|
||||||
|
user_pref("browser.newtabpage.activity-stream.feeds.section.topstories", false);
|
||||||
|
user_pref("browser.newtabpage.activity-stream.feeds.system.topstories", false);
|
||||||
|
user_pref("browser.newtabpage.activity-stream.feeds.discoverystreamfeed", false);
|
||||||
|
user_pref("browser.safebrowsing.provider.mozilla.updateURL", "");
|
||||||
|
user_pref("browser.safebrowsing.provider.mozilla.gethashURL", "");
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
ln -sf /usr/lib/$(uname -m)-linux-gnu/pkcs11/p11-kit-trust.so /usr/lib/firefox-esr/libnssckbi.so
|
ln -sf /usr/lib/$(uname -m)-linux-gnu/pkcs11/p11-kit-trust.so /usr/lib/firefox-esr/libnssckbi.so
|
||||||
@@ -91,15 +106,6 @@ cat > /home/kiosk/kiosk.sh << 'EOF'
|
|||||||
while ! curl "http://localhost" > /dev/null; do
|
while ! curl "http://localhost" > /dev/null; do
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
while ! /usr/lib/startos/scripts/check-monitor; do
|
|
||||||
sleep 15
|
|
||||||
done
|
|
||||||
(
|
|
||||||
while /usr/lib/startos/scripts/check-monitor; do
|
|
||||||
sleep 15
|
|
||||||
done
|
|
||||||
killall firefox-esr
|
|
||||||
) &
|
|
||||||
matchbox-window-manager -use_titlebar no &
|
matchbox-window-manager -use_titlebar no &
|
||||||
cp -r /home/kiosk/fx-profile /home/kiosk/fx-profile-tmp
|
cp -r /home/kiosk/fx-profile /home/kiosk/fx-profile-tmp
|
||||||
firefox-esr http://localhost --profile /home/kiosk/fx-profile-tmp
|
firefox-esr http://localhost --profile /home/kiosk/fx-profile-tmp
|
||||||
|
|||||||
51
build/lib/scripts/forward-port
Executable file
51
build/lib/scripts/forward-port
Executable file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ -z "$sip" ] || [ -z "$dip" ] || [ -z "$dprefix" ] || [ -z "$sport" ] || [ -z "$dport" ]; then
|
||||||
|
>&2 echo 'missing required env var'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
NAME="F$(echo "$sip:$sport -> $dip/$dprefix:$dport" | sha256sum | head -c 15)"
|
||||||
|
|
||||||
|
for kind in INPUT FORWARD ACCEPT; do
|
||||||
|
if ! iptables -C $kind -j "${NAME}_${kind}" 2> /dev/null; then
|
||||||
|
iptables -N "${NAME}_${kind}" 2> /dev/null
|
||||||
|
iptables -A $kind -j "${NAME}_${kind}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for kind in PREROUTING INPUT OUTPUT POSTROUTING; do
|
||||||
|
if ! iptables -t nat -C $kind -j "${NAME}_${kind}" 2> /dev/null; then
|
||||||
|
iptables -t nat -N "${NAME}_${kind}" 2> /dev/null
|
||||||
|
iptables -t nat -A $kind -j "${NAME}_${kind}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
err=0
|
||||||
|
trap 'err=1' ERR
|
||||||
|
|
||||||
|
for kind in INPUT FORWARD ACCEPT; do
|
||||||
|
iptables -F "${NAME}_${kind}" 2> /dev/null
|
||||||
|
done
|
||||||
|
for kind in PREROUTING INPUT OUTPUT POSTROUTING; do
|
||||||
|
iptables -t nat -F "${NAME}_${kind}" 2> /dev/null
|
||||||
|
done
|
||||||
|
if [ "$UNDO" = 1 ]; then
|
||||||
|
conntrack -D -p tcp -d $sip --dport $sport || true # conntrack returns exit 1 if no connections are active
|
||||||
|
conntrack -D -p udp -d $sip --dport $sport || true # conntrack returns exit 1 if no connections are active
|
||||||
|
exit $err
|
||||||
|
fi
|
||||||
|
|
||||||
|
iptables -t nat -A ${NAME}_PREROUTING -d "$sip" -p tcp --dport "$sport" -j DNAT --to-destination "$dip:$dport"
|
||||||
|
iptables -t nat -A ${NAME}_PREROUTING -d "$sip" -p udp --dport "$sport" -j DNAT --to-destination "$dip:$dport"
|
||||||
|
iptables -t nat -A ${NAME}_OUTPUT -d "$sip" -p tcp --dport "$sport" -j DNAT --to-destination "$dip:$dport"
|
||||||
|
iptables -t nat -A ${NAME}_OUTPUT -d "$sip" -p udp --dport "$sport" -j DNAT --to-destination "$dip:$dport"
|
||||||
|
|
||||||
|
iptables -t nat -A ${NAME}_PREROUTING -s "$dip/$dprefix" -d "$sip" -p tcp --dport "$sport" -j DNAT --to-destination "$dip:$dport"
|
||||||
|
iptables -t nat -A ${NAME}_PREROUTING -s "$dip/$dprefix" -d "$sip" -p udp --dport "$sport" -j DNAT --to-destination "$dip:$dport"
|
||||||
|
iptables -t nat -A ${NAME}_POSTROUTING -s "$dip/$dprefix" -d "$dip" -p tcp --dport "$dport" -j MASQUERADE
|
||||||
|
iptables -t nat -A ${NAME}_POSTROUTING -s "$dip/$dprefix" -d "$dip" -p udp --dport "$dport" -j MASQUERADE
|
||||||
|
|
||||||
|
iptables -A ${NAME}_FORWARD -d $dip -p tcp --dport $dport -m state --state NEW -j ACCEPT
|
||||||
|
iptables -A ${NAME}_FORWARD -d $dip -p udp --dport $dport -m state --state NEW -j ACCEPT
|
||||||
|
|
||||||
|
exit $err
|
||||||
35
build/lib/scripts/prune-boot
Executable file
35
build/lib/scripts/prune-boot
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$UID" -ne 0 ]; then
|
||||||
|
>&2 echo 'Must be run as root'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the current kernel version
|
||||||
|
current_kernel=$(uname -r)
|
||||||
|
|
||||||
|
echo "Current kernel: $current_kernel"
|
||||||
|
echo "Searching for old kernel files in /boot..."
|
||||||
|
|
||||||
|
# Extract base kernel version (without possible suffixes)
|
||||||
|
current_base=$(echo "$current_kernel" | sed 's/-.*//')
|
||||||
|
|
||||||
|
cd /boot || { echo "/boot directory not found!"; exit 1; }
|
||||||
|
|
||||||
|
for file in vmlinuz-* initrd.img-* System.map-* config-*; do
|
||||||
|
# Extract version from filename
|
||||||
|
version=$(echo "$file" | sed -E 's/^[^0-9]*([0-9][^ ]*).*/\1/')
|
||||||
|
# Skip if file matches current kernel version
|
||||||
|
if [[ "$file" == *"$current_kernel"* ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# Compare versions, delete if less than current
|
||||||
|
if dpkg --compare-versions "$version" lt "$current_kernel"; then
|
||||||
|
echo "Deleting $file (version $version is older than $current_kernel)"
|
||||||
|
sudo rm -f "$file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Old kernel files deleted."
|
||||||
@@ -83,6 +83,7 @@ local_mount_root()
|
|||||||
if [ -d "$image" ]; then
|
if [ -d "$image" ]; then
|
||||||
mount -r --bind $image /lower
|
mount -r --bind $image /lower
|
||||||
elif [ -f "$image" ]; then
|
elif [ -f "$image" ]; then
|
||||||
|
modprobe loop
|
||||||
modprobe squashfs
|
modprobe squashfs
|
||||||
mount -r $image /lower
|
mount -r $image /lower
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,36 +1,64 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
fail=$(printf " [\033[31m fail \033[0m]")
|
# --- Config ---
|
||||||
pass=$(printf " [\033[32m pass \033[0m]")
|
# Colors (using printf to ensure compatibility)
|
||||||
|
GRAY=$(printf '\033[90m')
|
||||||
|
GREEN=$(printf '\033[32m')
|
||||||
|
RED=$(printf '\033[31m')
|
||||||
|
NC=$(printf '\033[0m') # No Color
|
||||||
|
|
||||||
|
# Proxies to test
|
||||||
|
proxies=(
|
||||||
|
"Host Tor|127.0.1.1:9050"
|
||||||
|
"Startd Tor|10.0.3.1:9050"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Default URLs
|
||||||
onion_list=(
|
onion_list=(
|
||||||
|
"The Tor Project|http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion"
|
||||||
"Start9|http://privacy34kn4ez3y3nijweec6w4g54i3g54sdv7r5mr6soma3w4begyd.onion"
|
"Start9|http://privacy34kn4ez3y3nijweec6w4g54i3g54sdv7r5mr6soma3w4begyd.onion"
|
||||||
"Mempool|http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion"
|
"Mempool|http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion"
|
||||||
"DuckDuckGo|https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion"
|
"DuckDuckGo|https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion"
|
||||||
"Brave Search|https://search.brave4u7jddbv7cyviptqjc7jusxh72uik7zt6adtckl5f4nwy2v72qd.onion"
|
"Brave Search|https://search.brave4u7jddbv7cyviptqjc7jusxh72uik7zt6adtckl5f4nwy2v72qd.onion"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if ~/.startos/tor-check.list exists and read its contents if available
|
# Load custom list
|
||||||
if [ -f ~/.startos/tor-check.list ]; then
|
[ -f ~/.startos/tor-check.list ] && readarray -t custom_list < <(grep -v '^#' ~/.startos/tor-check.list) && onion_list+=("${custom_list[@]}")
|
||||||
while IFS= read -r line; do
|
|
||||||
# Check if the line starts with a #
|
# --- Functions ---
|
||||||
if [[ ! "$line" =~ ^# ]]; then
|
print_line() { printf "${GRAY}────────────────────────────────────────${NC}\n"; }
|
||||||
onion_list+=("$line")
|
|
||||||
|
# --- Main ---
|
||||||
|
echo "Testing Onion Connections..."
|
||||||
|
|
||||||
|
for proxy_info in "${proxies[@]}"; do
|
||||||
|
proxy_name="${proxy_info%%|*}"
|
||||||
|
proxy_addr="${proxy_info#*|}"
|
||||||
|
|
||||||
|
print_line
|
||||||
|
printf "${GRAY}Proxy: %s (%s)${NC}\n" "$proxy_name" "$proxy_addr"
|
||||||
|
|
||||||
|
for data in "${onion_list[@]}"; do
|
||||||
|
name="${data%%|*}"
|
||||||
|
url="${data#*|}"
|
||||||
|
|
||||||
|
# Capture verbose output + http code.
|
||||||
|
# --no-progress-meter: Suppresses the "0 0 0" stats but keeps -v output
|
||||||
|
output=$(curl -v --no-progress-meter --max-time 15 --socks5-hostname "$proxy_addr" "$url" 2>&1)
|
||||||
|
exit_code=$?
|
||||||
|
|
||||||
|
if [ $exit_code -eq 0 ]; then
|
||||||
|
printf " ${GREEN}[pass]${NC} %s (%s)\n" "$name" "$url"
|
||||||
|
else
|
||||||
|
printf " ${RED}[fail]${NC} %s (%s)\n" "$name" "$url"
|
||||||
|
printf " ${RED}↳ Curl Error %s${NC}\n" "$exit_code"
|
||||||
|
|
||||||
|
# Print the last 4 lines of verbose log to show the specific handshake error
|
||||||
|
# We look for lines starting with '*' or '>' or '<' to filter out junk if any remains
|
||||||
|
echo "$output" | tail -n 4 | sed "s/^/ ${GRAY}/"
|
||||||
fi
|
fi
|
||||||
done < ~/.startos/tor-check.list
|
done
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Testing connection to Onion Pages ..."
|
|
||||||
|
|
||||||
for data in "${onion_list[@]}"; do
|
|
||||||
name="${data%%|*}"
|
|
||||||
url="${data#*|}"
|
|
||||||
if curl --socks5-hostname localhost:9050 "$url" > /dev/null 2>&1; then
|
|
||||||
echo " ${pass}: $name ($url) "
|
|
||||||
else
|
|
||||||
echo " ${fail}: $name ($url) "
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
print_line
|
||||||
echo
|
# Reset color just in case
|
||||||
echo "Done."
|
printf "${NC}"
|
||||||
|
|||||||
82
build/lib/scripts/upgrade
Executable file
82
build/lib/scripts/upgrade
Executable file
@@ -0,0 +1,82 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SOURCE_DIR="$(dirname $(realpath "${BASH_SOURCE[0]}"))"
|
||||||
|
|
||||||
|
if [ "$UID" -ne 0 ]; then
|
||||||
|
>&2 echo 'Must be run as root'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -f "$1" ]; then
|
||||||
|
>&2 echo "usage: $0 <SQUASHFS>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 'Upgrading...'
|
||||||
|
|
||||||
|
hash=$(b3sum $1 | head -c 32)
|
||||||
|
if [ -n "$2" ] && [ "$hash" != "$CHECKSUM" ]; then
|
||||||
|
>&2 echo 'Checksum mismatch'
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
unsquashfs -f -d / $1 boot
|
||||||
|
|
||||||
|
umount -R /media/startos/next 2> /dev/null || true
|
||||||
|
umount /media/startos/upper 2> /dev/null || true
|
||||||
|
umount /media/startos/lower 2> /dev/null || true
|
||||||
|
|
||||||
|
mkdir -p /media/startos/upper
|
||||||
|
mount -t tmpfs tmpfs /media/startos/upper
|
||||||
|
mkdir -p /media/startos/lower /media/startos/upper/data /media/startos/upper/work /media/startos/next
|
||||||
|
mount $1 /media/startos/lower
|
||||||
|
mount -t overlay \
|
||||||
|
-olowerdir=/media/startos/lower,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \
|
||||||
|
overlay /media/startos/next
|
||||||
|
|
||||||
|
mkdir -p /media/startos/next/run
|
||||||
|
mkdir -p /media/startos/next/dev
|
||||||
|
mkdir -p /media/startos/next/sys
|
||||||
|
mkdir -p /media/startos/next/proc
|
||||||
|
mkdir -p /media/startos/next/boot
|
||||||
|
mkdir -p /media/startos/next/media/startos/root
|
||||||
|
mount --bind /run /media/startos/next/run
|
||||||
|
mount --bind /tmp /media/startos/next/tmp
|
||||||
|
mount --bind /dev /media/startos/next/dev
|
||||||
|
mount --bind /sys /media/startos/next/sys
|
||||||
|
mount --bind /proc /media/startos/next/proc
|
||||||
|
mount --bind /boot /media/startos/next/boot
|
||||||
|
mount --bind /media/startos/root /media/startos/next/media/startos/root
|
||||||
|
|
||||||
|
if mountpoint /boot/efi 2> /dev/null; then
|
||||||
|
mkdir -p /media/startos/next/boot/efi
|
||||||
|
mount --bind /boot/efi /media/startos/next/boot/efi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if mountpoint /sys/firmware/efi/efivars 2> /dev/null; then
|
||||||
|
mount --bind /sys/firmware/efi/efivars /media/startos/next/sys/firmware/efi/efivars
|
||||||
|
fi
|
||||||
|
|
||||||
|
chroot /media/startos/next bash -e << "EOF"
|
||||||
|
|
||||||
|
if [ -f /boot/grub/grub.cfg ]; then
|
||||||
|
grub-install /dev/$(eval $(lsblk -o MOUNTPOINT,PKNAME -P | grep 'MOUNTPOINT="/media/startos/root"') && echo $PKNAME)
|
||||||
|
update-grub
|
||||||
|
fi
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sync
|
||||||
|
|
||||||
|
umount -Rl /media/startos/next
|
||||||
|
umount /media/startos/upper
|
||||||
|
umount /media/startos/lower
|
||||||
|
|
||||||
|
mv $1 /media/startos/images/${hash}.rootfs
|
||||||
|
ln -rsf /media/startos/images/${hash}.rootfs /media/startos/config/current.rootfs
|
||||||
|
|
||||||
|
sync
|
||||||
|
|
||||||
|
echo 'System upgrade complete. Reboot to apply changes...'
|
||||||
46
build/os-compat/buildenv.Dockerfile
Normal file
46
build/os-compat/buildenv.Dockerfile
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
FROM debian:forky
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
gpg \
|
||||||
|
build-essential \
|
||||||
|
sed \
|
||||||
|
grep \
|
||||||
|
gawk \
|
||||||
|
jq \
|
||||||
|
gzip \
|
||||||
|
brotli \
|
||||||
|
qemu-user-static \
|
||||||
|
binfmt-support \
|
||||||
|
squashfs-tools \
|
||||||
|
git \
|
||||||
|
debspawn \
|
||||||
|
rsync \
|
||||||
|
b3sum \
|
||||||
|
fuse-overlayfs \
|
||||||
|
sudo \
|
||||||
|
systemd \
|
||||||
|
systemd-container \
|
||||||
|
systemd-sysv \
|
||||||
|
dbus \
|
||||||
|
dbus-user-session \
|
||||||
|
nodejs
|
||||||
|
|
||||||
|
RUN systemctl mask \
|
||||||
|
systemd-firstboot.service \
|
||||||
|
systemd-udevd.service \
|
||||||
|
getty@tty1.service \
|
||||||
|
console-getty.service
|
||||||
|
|
||||||
|
RUN git config --global --add safe.directory /root/start-os
|
||||||
|
|
||||||
|
RUN mkdir -p /etc/debspawn && \
|
||||||
|
echo "AllowUnsafePermissions=true" > /etc/debspawn/global.toml
|
||||||
|
|
||||||
|
RUN mkdir -p /root/start-os
|
||||||
|
WORKDIR /root/start-os
|
||||||
|
|
||||||
|
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
ENTRYPOINT [ "/docker-entrypoint.sh" ]
|
||||||
3
build/os-compat/docker-entrypoint.sh
Executable file
3
build/os-compat/docker-entrypoint.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
exec /lib/systemd/systemd --unit=multi-user.target --show-status=false --log-target=journal
|
||||||
27
build/os-compat/run-compat.sh
Executable file
27
build/os-compat/run-compat.sh
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ "$FORCE_COMPAT" = 1 ] || ( [ "$REQUIRES" = "linux" ] && [ "$(uname -s)" != "Linux" ] ) || ( [ "$REQUIRES" = "debian" ] && ! which dpkg > /dev/null ) || ( [ "$REQUIRES" = "qemu" ] && ! which qemu-$ARCH > /dev/null ); then
|
||||||
|
project_pwd="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)/"
|
||||||
|
pwd="$(pwd)/"
|
||||||
|
if ! [[ "$pwd" = "$project_pwd"* ]]; then
|
||||||
|
>&2 echo "Must be run from start-os project dir"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rel_pwd="${pwd#"$project_pwd"}"
|
||||||
|
|
||||||
|
SYSTEMD_TTY="-P"
|
||||||
|
USE_TTY=
|
||||||
|
if tty -s; then
|
||||||
|
USE_TTY="-it"
|
||||||
|
SYSTEMD_TTY="-t"
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker run -d --rm --name os-compat --privileged --security-opt apparmor=unconfined -v "${project_pwd}:/root/start-os" -v /lib/modules:/lib/modules:ro start9/build-env
|
||||||
|
while ! docker exec os-compat systemctl is-active --quiet multi-user.target 2> /dev/null; do sleep .5; done
|
||||||
|
docker exec -eARCH -eENVIRONMENT -ePLATFORM -eGIT_BRANCH_AS_HASH -ePROJECT -eDEPENDS -eCONFLICTS $USE_TTY -w "/root/start-os${rel_pwd}" os-compat $@
|
||||||
|
code=$?
|
||||||
|
docker stop os-compat > /dev/null
|
||||||
|
exit $code
|
||||||
|
else
|
||||||
|
exec $@
|
||||||
|
fi
|
||||||
@@ -7,6 +7,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if ! [ -f ./GIT_HASH.txt ] || [ "$(cat ./GIT_HASH.txt)" != "$GIT_HASH" ]; then
|
if ! [ -f ./GIT_HASH.txt ] || [ "$(cat ./GIT_HASH.txt)" != "$GIT_HASH" ]; then
|
||||||
|
>&2 echo Git hash changed from "$([ -f ./GIT_HASH.txt ] && cat ./GIT_HASH.txt)" to "$GIT_HASH"
|
||||||
echo -n "$GIT_HASH" > ./GIT_HASH.txt
|
echo -n "$GIT_HASH" > ./GIT_HASH.txt
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
rm -rf web/dist/static
|
STATIC_DIR=web/dist/static/$1
|
||||||
|
RAW_DIR=web/dist/raw/$1
|
||||||
|
|
||||||
|
mkdir -p $STATIC_DIR
|
||||||
|
rm -rf $STATIC_DIR
|
||||||
|
|
||||||
if ! [[ "$ENVIRONMENT" =~ (^|-)dev($|-) ]]; then
|
if ! [[ "$ENVIRONMENT" =~ (^|-)dev($|-) ]]; then
|
||||||
find web/dist/raw -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 gzip -kf
|
find $RAW_DIR -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 gzip -kf
|
||||||
find web/dist/raw -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 brotli -kf
|
find $RAW_DIR -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 brotli -kf
|
||||||
|
|
||||||
for file in $(find web/dist/raw -type f -not -name '*.gz' -and -not -name '*.br'); do
|
for file in $(find $RAW_DIR -type f -not -name '*.gz' -and -not -name '*.br'); do
|
||||||
raw_size=$(du $file | awk '{print $1 * 512}')
|
raw_size=$(du $file | awk '{print $1 * 512}')
|
||||||
gz_size=$(du $file.gz | awk '{print $1 * 512}')
|
gz_size=$(du $file.gz | awk '{print $1 * 512}')
|
||||||
br_size=$(du $file.br | awk '{print $1 * 512}')
|
br_size=$(du $file.br | awk '{print $1 * 512}')
|
||||||
@@ -23,4 +27,5 @@ if ! [[ "$ENVIRONMENT" =~ (^|-)dev($|-) ]]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cp -r web/dist/raw web/dist/static
|
|
||||||
|
cp -r $RAW_DIR $STATIC_DIR
|
||||||
6
container-runtime/container-runtime-failure.service
Normal file
6
container-runtime/container-runtime-failure.service
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=StartOS Container Runtime Failure Handler
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/bin/start-container rebuild
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=StartOS Container Runtime
|
Description=StartOS Container Runtime
|
||||||
|
OnFailure=container-runtime-failure.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart=/usr/bin/node --experimental-detect-module --unhandled-rejections=warn /usr/lib/startos/init/index.js
|
Environment=RUST_LOG=startos=debug
|
||||||
Restart=always
|
ExecStart=/usr/bin/node --experimental-detect-module --trace-warnings --unhandled-rejections=warn /usr/lib/startos/init/index.js
|
||||||
RestartSec=3
|
Restart=no
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
@@ -6,13 +6,9 @@ mkdir -p /run/systemd/resolve
|
|||||||
echo "nameserver 8.8.8.8" > /run/systemd/resolve/stub-resolv.conf
|
echo "nameserver 8.8.8.8" > /run/systemd/resolve/stub-resolv.conf
|
||||||
|
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y curl rsync qemu-user-static
|
apt-get install -y curl rsync qemu-user-static nodejs
|
||||||
|
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
|
|
||||||
source ~/.bashrc
|
|
||||||
nvm install 20
|
|
||||||
ln -s $(which node) /usr/bin/node
|
|
||||||
|
|
||||||
|
sed -i '/\(^\|#\)DNSStubListener=/c\DNSStubListener=no' /etc/systemd/resolved.conf
|
||||||
sed -i '/\(^\|#\)Storage=/c\Storage=persistent' /etc/systemd/journald.conf
|
sed -i '/\(^\|#\)Storage=/c\Storage=persistent' /etc/systemd/journald.conf
|
||||||
sed -i '/\(^\|#\)Compress=/c\Compress=yes' /etc/systemd/journald.conf
|
sed -i '/\(^\|#\)Compress=/c\Compress=yes' /etc/systemd/journald.conf
|
||||||
sed -i '/\(^\|#\)SystemMaxUse=/c\SystemMaxUse=1G' /etc/systemd/journald.conf
|
sed -i '/\(^\|#\)SystemMaxUse=/c\SystemMaxUse=1G' /etc/systemd/journald.conf
|
||||||
@@ -20,4 +16,7 @@ sed -i '/\(^\|#\)ForwardToSyslog=/c\ForwardToSyslog=no' /etc/systemd/journald.co
|
|||||||
|
|
||||||
systemctl enable container-runtime.service
|
systemctl enable container-runtime.service
|
||||||
|
|
||||||
rm -rf /run/systemd
|
rm -rf /run/systemd
|
||||||
|
|
||||||
|
rm -f /etc/resolv.conf
|
||||||
|
echo "nameserver 10.0.3.1" > /etc/resolv.conf
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DISTRO=debian
|
DISTRO=debian
|
||||||
VERSION=bookworm
|
VERSION=trixie
|
||||||
ARCH=${ARCH:-$(uname -m)}
|
ARCH=${ARCH:-$(uname -m)}
|
||||||
FLAVOR=default
|
FLAVOR=default
|
||||||
|
|
||||||
@@ -16,8 +14,9 @@ elif [ "$_ARCH" = "aarch64" ]; then
|
|||||||
_ARCH=arm64
|
_ARCH=arm64
|
||||||
fi
|
fi
|
||||||
|
|
||||||
URL="https://images.linuxcontainers.org/$(curl -fsSL https://images.linuxcontainers.org/meta/1.0/index-system | grep "^$DISTRO;$VERSION;$_ARCH;$FLAVOR;" | head -n1 | sed 's/^.*;//g')/rootfs.squashfs"
|
BASE_URL="https://images.linuxcontainers.org$(curl -fsSL https://images.linuxcontainers.org/meta/1.0/index-system | grep "^$DISTRO;$VERSION;$_ARCH;$FLAVOR;" | head -n1 | sed 's/^.*;//g')"
|
||||||
|
OUTPUT_FILE="debian.${ARCH}.squashfs"
|
||||||
|
|
||||||
echo "Downloading $URL to debian.${ARCH}.squashfs"
|
echo "Downloading ${BASE_URL}/rootfs.squashfs to $OUTPUT_FILE"
|
||||||
|
curl -fsSL "${BASE_URL}/rootfs.squashfs" > "$OUTPUT_FILE"
|
||||||
curl -fsSL "$URL" > debian.${ARCH}.squashfs
|
curl -fsSL "$BASE_URL/SHA256SUMS" | grep 'rootfs\.squashfs' | awk '{print $1" '"$OUTPUT_FILE"'"}' | shasum -a 256 -c
|
||||||
6091
container-runtime/package-lock.json
generated
6091
container-runtime/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -26,8 +26,9 @@
|
|||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"jsonpath": "^1.1.1",
|
"jsonpath": "^1.1.1",
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
|
"mime": "^4.0.7",
|
||||||
"node-fetch": "^3.1.0",
|
"node-fetch": "^3.1.0",
|
||||||
"ts-matches": "^5.5.1",
|
"ts-matches": "^6.3.2",
|
||||||
"tslib": "^2.5.3",
|
"tslib": "^2.5.3",
|
||||||
"typescript": "^5.1.3",
|
"typescript": "^5.1.3",
|
||||||
"yaml": "^2.3.1"
|
"yaml": "^2.3.1"
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { types as T, utils } from "@start9labs/start-sdk"
|
import {
|
||||||
|
ExtendedVersion,
|
||||||
|
types as T,
|
||||||
|
utils,
|
||||||
|
VersionRange,
|
||||||
|
} from "@start9labs/start-sdk"
|
||||||
import * as net from "net"
|
import * as net from "net"
|
||||||
import { object, string, number, literals, some, unknown } from "ts-matches"
|
import { object, string, number, literals, some, unknown } from "ts-matches"
|
||||||
import { Effects } from "../Models/Effects"
|
import { Effects } from "../Models/Effects"
|
||||||
@@ -6,23 +11,19 @@ import { Effects } from "../Models/Effects"
|
|||||||
import { CallbackHolder } from "../Models/CallbackHolder"
|
import { CallbackHolder } from "../Models/CallbackHolder"
|
||||||
import { asError } from "@start9labs/start-sdk/base/lib/util"
|
import { asError } from "@start9labs/start-sdk/base/lib/util"
|
||||||
const matchRpcError = object({
|
const matchRpcError = object({
|
||||||
error: object(
|
error: object({
|
||||||
{
|
code: number,
|
||||||
code: number,
|
message: string,
|
||||||
message: string,
|
data: some(
|
||||||
data: some(
|
string,
|
||||||
string,
|
object({
|
||||||
object(
|
details: string,
|
||||||
{
|
debug: string.nullable().optional(),
|
||||||
details: string,
|
}),
|
||||||
debug: string,
|
)
|
||||||
},
|
.nullable()
|
||||||
["debug"],
|
.optional(),
|
||||||
),
|
}),
|
||||||
),
|
|
||||||
},
|
|
||||||
["data"],
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
const testRpcError = matchRpcError.test
|
const testRpcError = matchRpcError.test
|
||||||
const testRpcResult = object({
|
const testRpcResult = object({
|
||||||
@@ -34,13 +35,13 @@ const SOCKET_PATH = "/media/startos/rpc/host.sock"
|
|||||||
let hostSystemId = 0
|
let hostSystemId = 0
|
||||||
|
|
||||||
export type EffectContext = {
|
export type EffectContext = {
|
||||||
procedureId: string | null
|
eventId: string | null
|
||||||
callbacks?: CallbackHolder
|
callbacks?: CallbackHolder
|
||||||
constRetry?: () => void
|
constRetry?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const rpcRoundFor =
|
const rpcRoundFor =
|
||||||
(procedureId: string | null) =>
|
(eventId: string | null) =>
|
||||||
<K extends T.EffectMethod | "clearCallbacks">(
|
<K extends T.EffectMethod | "clearCallbacks">(
|
||||||
method: K,
|
method: K,
|
||||||
params: Record<string, unknown>,
|
params: Record<string, unknown>,
|
||||||
@@ -51,7 +52,7 @@ const rpcRoundFor =
|
|||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
id,
|
id,
|
||||||
method,
|
method,
|
||||||
params: { ...params, procedureId: procedureId || undefined },
|
params: { ...params, eventId: eventId ?? undefined },
|
||||||
}) + "\n",
|
}) + "\n",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -102,9 +103,21 @@ const rpcRoundFor =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function makeEffects(context: EffectContext): Effects {
|
export function makeEffects(context: EffectContext): Effects {
|
||||||
const rpcRound = rpcRoundFor(context.procedureId)
|
const rpcRound = rpcRoundFor(context.eventId)
|
||||||
const self: Effects = {
|
const self: Effects = {
|
||||||
|
eventId: context.eventId,
|
||||||
|
child: (name) =>
|
||||||
|
makeEffects({ ...context, callbacks: context.callbacks?.child(name) }),
|
||||||
constRetry: context.constRetry,
|
constRetry: context.constRetry,
|
||||||
|
isInContext: !!context.callbacks,
|
||||||
|
onLeaveContext:
|
||||||
|
context.callbacks?.onLeaveContext?.bind(context.callbacks) ||
|
||||||
|
(() => {
|
||||||
|
console.warn(
|
||||||
|
"no context for this effects object",
|
||||||
|
new Error().stack?.replace(/^Error/, ""),
|
||||||
|
)
|
||||||
|
}),
|
||||||
clearCallbacks(...[options]: Parameters<T.Effects["clearCallbacks"]>) {
|
clearCallbacks(...[options]: Parameters<T.Effects["clearCallbacks"]>) {
|
||||||
return rpcRound("clear-callbacks", {
|
return rpcRound("clear-callbacks", {
|
||||||
...options,
|
...options,
|
||||||
@@ -126,22 +139,20 @@ export function makeEffects(context: EffectContext): Effects {
|
|||||||
...options,
|
...options,
|
||||||
}) as ReturnType<T.Effects["action"]["getInput"]>
|
}) as ReturnType<T.Effects["action"]["getInput"]>
|
||||||
},
|
},
|
||||||
request(...[options]: Parameters<T.Effects["action"]["request"]>) {
|
createTask(...[options]: Parameters<T.Effects["action"]["createTask"]>) {
|
||||||
return rpcRound("action.request", {
|
return rpcRound("action.create-task", {
|
||||||
...options,
|
...options,
|
||||||
}) as ReturnType<T.Effects["action"]["request"]>
|
}) as ReturnType<T.Effects["action"]["createTask"]>
|
||||||
},
|
},
|
||||||
run(...[options]: Parameters<T.Effects["action"]["run"]>) {
|
run(...[options]: Parameters<T.Effects["action"]["run"]>) {
|
||||||
return rpcRound("action.run", {
|
return rpcRound("action.run", {
|
||||||
...options,
|
...options,
|
||||||
}) as ReturnType<T.Effects["action"]["run"]>
|
}) as ReturnType<T.Effects["action"]["run"]>
|
||||||
},
|
},
|
||||||
clearRequests(
|
clearTasks(...[options]: Parameters<T.Effects["action"]["clearTasks"]>) {
|
||||||
...[options]: Parameters<T.Effects["action"]["clearRequests"]>
|
return rpcRound("action.clear-tasks", {
|
||||||
) {
|
|
||||||
return rpcRound("action.clear-requests", {
|
|
||||||
...options,
|
...options,
|
||||||
}) as ReturnType<T.Effects["action"]["clearRequests"]>
|
}) as ReturnType<T.Effects["action"]["clearTasks"]>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
bind(...[options]: Parameters<T.Effects["bind"]>) {
|
bind(...[options]: Parameters<T.Effects["bind"]>) {
|
||||||
@@ -186,13 +197,6 @@ export function makeEffects(context: EffectContext): Effects {
|
|||||||
T.Effects["exportServiceInterface"]
|
T.Effects["exportServiceInterface"]
|
||||||
>
|
>
|
||||||
}) as Effects["exportServiceInterface"],
|
}) as Effects["exportServiceInterface"],
|
||||||
exposeForDependents(
|
|
||||||
...[options]: Parameters<T.Effects["exposeForDependents"]>
|
|
||||||
) {
|
|
||||||
return rpcRound("expose-for-dependents", options) as ReturnType<
|
|
||||||
T.Effects["exposeForDependents"]
|
|
||||||
>
|
|
||||||
},
|
|
||||||
getContainerIp(...[options]: Parameters<T.Effects["getContainerIp"]>) {
|
getContainerIp(...[options]: Parameters<T.Effects["getContainerIp"]>) {
|
||||||
return rpcRound("get-container-ip", options) as ReturnType<
|
return rpcRound("get-container-ip", options) as ReturnType<
|
||||||
T.Effects["getContainerIp"]
|
T.Effects["getContainerIp"]
|
||||||
@@ -254,6 +258,7 @@ export function makeEffects(context: EffectContext): Effects {
|
|||||||
return rpcRound("mount", options) as ReturnType<T.Effects["mount"]>
|
return rpcRound("mount", options) as ReturnType<T.Effects["mount"]>
|
||||||
},
|
},
|
||||||
restart(...[]: Parameters<T.Effects["restart"]>) {
|
restart(...[]: Parameters<T.Effects["restart"]>) {
|
||||||
|
console.log("Restarting service...")
|
||||||
return rpcRound("restart", {}) as ReturnType<T.Effects["restart"]>
|
return rpcRound("restart", {}) as ReturnType<T.Effects["restart"]>
|
||||||
},
|
},
|
||||||
setDependencies(
|
setDependencies(
|
||||||
@@ -284,6 +289,7 @@ export function makeEffects(context: EffectContext): Effects {
|
|||||||
getStatus(...[o]: Parameters<T.Effects["getStatus"]>) {
|
getStatus(...[o]: Parameters<T.Effects["getStatus"]>) {
|
||||||
return rpcRound("get-status", o) as ReturnType<T.Effects["getStatus"]>
|
return rpcRound("get-status", o) as ReturnType<T.Effects["getStatus"]>
|
||||||
},
|
},
|
||||||
|
/// DEPRECATED
|
||||||
setMainStatus(o: { status: "running" | "stopped" }): Promise<null> {
|
setMainStatus(o: { status: "running" | "stopped" }): Promise<null> {
|
||||||
return rpcRound("set-main-status", o) as ReturnType<
|
return rpcRound("set-main-status", o) as ReturnType<
|
||||||
T.Effects["setHealth"]
|
T.Effects["setHealth"]
|
||||||
@@ -293,15 +299,6 @@ export function makeEffects(context: EffectContext): Effects {
|
|||||||
shutdown(...[]: Parameters<T.Effects["shutdown"]>) {
|
shutdown(...[]: Parameters<T.Effects["shutdown"]>) {
|
||||||
return rpcRound("shutdown", {}) as ReturnType<T.Effects["shutdown"]>
|
return rpcRound("shutdown", {}) as ReturnType<T.Effects["shutdown"]>
|
||||||
},
|
},
|
||||||
store: {
|
|
||||||
get: async (options: any) =>
|
|
||||||
rpcRound("store.get", {
|
|
||||||
...options,
|
|
||||||
callback: context.callbacks?.addCallback(options.callback) || null,
|
|
||||||
}) as any,
|
|
||||||
set: async (options: any) =>
|
|
||||||
rpcRound("store.set", options) as ReturnType<T.Effects["store"]["set"]>,
|
|
||||||
} as T.Effects["store"],
|
|
||||||
getDataVersion() {
|
getDataVersion() {
|
||||||
return rpcRound("get-data-version", {}) as ReturnType<
|
return rpcRound("get-data-version", {}) as ReturnType<
|
||||||
T.Effects["getDataVersion"]
|
T.Effects["getDataVersion"]
|
||||||
@@ -313,5 +310,15 @@ export function makeEffects(context: EffectContext): Effects {
|
|||||||
>
|
>
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if (context.callbacks?.onLeaveContext)
|
||||||
|
self.onLeaveContext(() => {
|
||||||
|
self.isInContext = false
|
||||||
|
self.onLeaveContext = () => {
|
||||||
|
console.warn(
|
||||||
|
"this effects object is already out of context",
|
||||||
|
new Error().stack?.replace(/^Error/, ""),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,15 @@ import {
|
|||||||
any,
|
any,
|
||||||
shape,
|
shape,
|
||||||
anyOf,
|
anyOf,
|
||||||
|
literals,
|
||||||
} from "ts-matches"
|
} from "ts-matches"
|
||||||
|
|
||||||
import { types as T, utils } from "@start9labs/start-sdk"
|
import {
|
||||||
|
ExtendedVersion,
|
||||||
|
types as T,
|
||||||
|
utils,
|
||||||
|
VersionRange,
|
||||||
|
} from "@start9labs/start-sdk"
|
||||||
import * as fs from "fs"
|
import * as fs from "fs"
|
||||||
|
|
||||||
import { CallbackHolder } from "../Models/CallbackHolder"
|
import { CallbackHolder } from "../Models/CallbackHolder"
|
||||||
@@ -26,20 +32,16 @@ type MaybePromise<T> = T | Promise<T>
|
|||||||
export const matchRpcResult = anyOf(
|
export const matchRpcResult = anyOf(
|
||||||
object({ result: any }),
|
object({ result: any }),
|
||||||
object({
|
object({
|
||||||
error: object(
|
error: object({
|
||||||
{
|
code: number,
|
||||||
code: number,
|
message: string,
|
||||||
message: string,
|
data: object({
|
||||||
data: object(
|
details: string.optional(),
|
||||||
{
|
debug: any.optional(),
|
||||||
details: string,
|
})
|
||||||
debug: any,
|
.nullable()
|
||||||
},
|
.optional(),
|
||||||
["details", "debug"],
|
}),
|
||||||
),
|
|
||||||
},
|
|
||||||
["data"],
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,38 +56,26 @@ const isResult = object({ result: any }).test
|
|||||||
|
|
||||||
const idType = some(string, number, literal(null))
|
const idType = some(string, number, literal(null))
|
||||||
type IdType = null | string | number | undefined
|
type IdType = null | string | number | undefined
|
||||||
const runType = object(
|
const runType = object({
|
||||||
{
|
id: idType.optional(),
|
||||||
id: idType,
|
method: literal("execute"),
|
||||||
method: literal("execute"),
|
params: object({
|
||||||
params: object(
|
id: string,
|
||||||
{
|
procedure: string,
|
||||||
id: string,
|
input: any,
|
||||||
procedure: string,
|
timeout: number.nullable().optional(),
|
||||||
input: any,
|
}),
|
||||||
timeout: number,
|
})
|
||||||
},
|
const sandboxRunType = object({
|
||||||
["timeout"],
|
id: idType.optional(),
|
||||||
),
|
method: literal("sandbox"),
|
||||||
},
|
params: object({
|
||||||
["id"],
|
id: string,
|
||||||
)
|
procedure: string,
|
||||||
const sandboxRunType = object(
|
input: any,
|
||||||
{
|
timeout: number.nullable().optional(),
|
||||||
id: idType,
|
}),
|
||||||
method: literal("sandbox"),
|
})
|
||||||
params: object(
|
|
||||||
{
|
|
||||||
id: string,
|
|
||||||
procedure: string,
|
|
||||||
input: any,
|
|
||||||
timeout: number,
|
|
||||||
},
|
|
||||||
["timeout"],
|
|
||||||
),
|
|
||||||
},
|
|
||||||
["id"],
|
|
||||||
)
|
|
||||||
const callbackType = object({
|
const callbackType = object({
|
||||||
method: literal("callback"),
|
method: literal("callback"),
|
||||||
params: object({
|
params: object({
|
||||||
@@ -93,44 +83,37 @@ const callbackType = object({
|
|||||||
args: array,
|
args: array,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const initType = object(
|
const initType = object({
|
||||||
{
|
id: idType.optional(),
|
||||||
id: idType,
|
method: literal("init"),
|
||||||
method: literal("init"),
|
params: object({
|
||||||
},
|
id: string,
|
||||||
["id"],
|
kind: literals("install", "update", "restore").nullable(),
|
||||||
)
|
}),
|
||||||
const startType = object(
|
})
|
||||||
{
|
const startType = object({
|
||||||
id: idType,
|
id: idType.optional(),
|
||||||
method: literal("start"),
|
method: literal("start"),
|
||||||
},
|
})
|
||||||
["id"],
|
const stopType = object({
|
||||||
)
|
id: idType.optional(),
|
||||||
const stopType = object(
|
method: literal("stop"),
|
||||||
{
|
})
|
||||||
id: idType,
|
const exitType = object({
|
||||||
method: literal("stop"),
|
id: idType.optional(),
|
||||||
},
|
method: literal("exit"),
|
||||||
["id"],
|
params: object({
|
||||||
)
|
id: string,
|
||||||
const exitType = object(
|
target: string.nullable(),
|
||||||
{
|
}),
|
||||||
id: idType,
|
})
|
||||||
method: literal("exit"),
|
const evalType = object({
|
||||||
},
|
id: idType.optional(),
|
||||||
["id"],
|
method: literal("eval"),
|
||||||
)
|
params: object({
|
||||||
const evalType = object(
|
script: string,
|
||||||
{
|
}),
|
||||||
id: idType,
|
})
|
||||||
method: literal("eval"),
|
|
||||||
params: object({
|
|
||||||
script: string,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
["id"],
|
|
||||||
)
|
|
||||||
|
|
||||||
const jsonParse = (x: string) => JSON.parse(x)
|
const jsonParse = (x: string) => JSON.parse(x)
|
||||||
|
|
||||||
@@ -163,6 +146,7 @@ const handleRpc = (id: IdType, result: Promise<RpcResult>) =>
|
|||||||
|
|
||||||
const hasId = object({ id: idType }).test
|
const hasId = object({ id: idType }).test
|
||||||
export class RpcListener {
|
export class RpcListener {
|
||||||
|
shouldExit = false
|
||||||
unixSocketServer = net.createServer(async (server) => {})
|
unixSocketServer = net.createServer(async (server) => {})
|
||||||
private _system: System | undefined
|
private _system: System | undefined
|
||||||
private callbacks: CallbackHolder | undefined
|
private callbacks: CallbackHolder | undefined
|
||||||
@@ -171,8 +155,12 @@ export class RpcListener {
|
|||||||
if (!fs.existsSync(SOCKET_PARENT)) {
|
if (!fs.existsSync(SOCKET_PARENT)) {
|
||||||
fs.mkdirSync(SOCKET_PARENT, { recursive: true })
|
fs.mkdirSync(SOCKET_PARENT, { recursive: true })
|
||||||
}
|
}
|
||||||
|
if (fs.existsSync(SOCKET_PATH)) fs.rmSync(SOCKET_PATH, { force: true })
|
||||||
|
|
||||||
this.unixSocketServer.listen(SOCKET_PATH)
|
this.unixSocketServer.listen(SOCKET_PATH)
|
||||||
|
|
||||||
|
console.log("Listening on %s", SOCKET_PATH)
|
||||||
|
|
||||||
this.unixSocketServer.on("connection", (s) => {
|
this.unixSocketServer.on("connection", (s) => {
|
||||||
let id: IdType = null
|
let id: IdType = null
|
||||||
const captureId = <X>(x: X) => {
|
const captureId = <X>(x: X) => {
|
||||||
@@ -223,6 +211,11 @@ export class RpcListener {
|
|||||||
.catch(mapError)
|
.catch(mapError)
|
||||||
.then(logData("response"))
|
.then(logData("response"))
|
||||||
.then(writeDataToSocket)
|
.then(writeDataToSocket)
|
||||||
|
.then((_) => {
|
||||||
|
if (this.shouldExit) {
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(`Major error in socket handling: ${e}`)
|
console.error(`Major error in socket handling: ${e}`)
|
||||||
console.debug(`Data in: ${a.toString()}`)
|
console.debug(`Data in: ${a.toString()}`)
|
||||||
@@ -238,21 +231,6 @@ export class RpcListener {
|
|||||||
return this._system
|
return this._system
|
||||||
}
|
}
|
||||||
|
|
||||||
private callbackHolders: Map<string, CallbackHolder> = new Map()
|
|
||||||
private removeCallbackHolderFor(procedure: string) {
|
|
||||||
const prev = this.callbackHolders.get(procedure)
|
|
||||||
if (prev) {
|
|
||||||
this.callbackHolders.delete(procedure)
|
|
||||||
this.callbacks?.removeChild(prev)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private callbackHolderFor(procedure: string): CallbackHolder {
|
|
||||||
this.removeCallbackHolderFor(procedure)
|
|
||||||
const callbackHolder = this.callbacks!.child()
|
|
||||||
this.callbackHolders.set(procedure, callbackHolder)
|
|
||||||
return callbackHolder
|
|
||||||
}
|
|
||||||
|
|
||||||
callCallback(callback: number, args: any[]): void {
|
callCallback(callback: number, args: any[]): void {
|
||||||
if (this.callbacks) {
|
if (this.callbacks) {
|
||||||
this.callbacks
|
this.callbacks
|
||||||
@@ -272,11 +250,11 @@ export class RpcListener {
|
|||||||
.when(runType, async ({ id, params }) => {
|
.when(runType, async ({ id, params }) => {
|
||||||
const system = this.system
|
const system = this.system
|
||||||
const procedure = jsonPath.unsafeCast(params.procedure)
|
const procedure = jsonPath.unsafeCast(params.procedure)
|
||||||
const { input, timeout, id: procedureId } = params
|
const { input, timeout, id: eventId } = params
|
||||||
const result = this.getResult(
|
const result = this.getResult(
|
||||||
procedure,
|
procedure,
|
||||||
system,
|
system,
|
||||||
procedureId,
|
eventId,
|
||||||
timeout,
|
timeout,
|
||||||
input,
|
input,
|
||||||
)
|
)
|
||||||
@@ -286,11 +264,11 @@ export class RpcListener {
|
|||||||
.when(sandboxRunType, async ({ id, params }) => {
|
.when(sandboxRunType, async ({ id, params }) => {
|
||||||
const system = this.system
|
const system = this.system
|
||||||
const procedure = jsonPath.unsafeCast(params.procedure)
|
const procedure = jsonPath.unsafeCast(params.procedure)
|
||||||
const { input, timeout, id: procedureId } = params
|
const { input, timeout, id: eventId } = params
|
||||||
const result = this.getResult(
|
const result = this.getResult(
|
||||||
procedure,
|
procedure,
|
||||||
system,
|
system,
|
||||||
procedureId,
|
eventId,
|
||||||
timeout,
|
timeout,
|
||||||
input,
|
input,
|
||||||
)
|
)
|
||||||
@@ -302,9 +280,10 @@ export class RpcListener {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
.when(startType, async ({ id }) => {
|
.when(startType, async ({ id }) => {
|
||||||
const callbacks = this.callbackHolderFor("main")
|
const callbacks =
|
||||||
|
this.callbacks?.getChild("main") || this.callbacks?.child("main")
|
||||||
const effects = makeEffects({
|
const effects = makeEffects({
|
||||||
procedureId: null,
|
eventId: null,
|
||||||
callbacks,
|
callbacks,
|
||||||
})
|
})
|
||||||
return handleRpc(
|
return handleRpc(
|
||||||
@@ -313,21 +292,39 @@ export class RpcListener {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(stopType, async ({ id }) => {
|
.when(stopType, async ({ id }) => {
|
||||||
this.removeCallbackHolderFor("main")
|
|
||||||
return handleRpc(
|
return handleRpc(
|
||||||
id,
|
id,
|
||||||
this.system.stop().then((result) => ({ result })),
|
this.system.stop().then((result) => {
|
||||||
|
this.callbacks?.removeChild("main")
|
||||||
|
|
||||||
|
return { result }
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(exitType, async ({ id }) => {
|
.when(exitType, async ({ id, params }) => {
|
||||||
return handleRpc(
|
return handleRpc(
|
||||||
id,
|
id,
|
||||||
(async () => {
|
(async () => {
|
||||||
if (this._system) await this._system.exit()
|
if (this._system) {
|
||||||
|
let target = null
|
||||||
|
if (params.target)
|
||||||
|
try {
|
||||||
|
target = ExtendedVersion.parse(params.target)
|
||||||
|
} catch (_) {
|
||||||
|
target = VersionRange.parse(params.target).normalize()
|
||||||
|
}
|
||||||
|
await this._system.exit(
|
||||||
|
makeEffects({
|
||||||
|
eventId: params.id,
|
||||||
|
}),
|
||||||
|
target,
|
||||||
|
)
|
||||||
|
this.shouldExit = true
|
||||||
|
}
|
||||||
})().then((result) => ({ result })),
|
})().then((result) => ({ result })),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(initType, async ({ id }) => {
|
.when(initType, async ({ id, params }) => {
|
||||||
return handleRpc(
|
return handleRpc(
|
||||||
id,
|
id,
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -335,16 +332,19 @@ export class RpcListener {
|
|||||||
const system = await this.getDependencies.system()
|
const system = await this.getDependencies.system()
|
||||||
this.callbacks = new CallbackHolder(
|
this.callbacks = new CallbackHolder(
|
||||||
makeEffects({
|
makeEffects({
|
||||||
procedureId: null,
|
eventId: params.id,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const callbacks = this.callbackHolderFor("containerInit")
|
const callbacks = this.callbacks.child("init")
|
||||||
await system.containerInit(
|
console.error("Initializing...")
|
||||||
|
await system.init(
|
||||||
makeEffects({
|
makeEffects({
|
||||||
procedureId: null,
|
eventId: params.id,
|
||||||
callbacks,
|
callbacks,
|
||||||
}),
|
}),
|
||||||
|
params.kind,
|
||||||
)
|
)
|
||||||
|
console.error("Initialization complete.")
|
||||||
this._system = system
|
this._system = system
|
||||||
}
|
}
|
||||||
})().then((result) => ({ result })),
|
})().then((result) => ({ result })),
|
||||||
@@ -377,7 +377,7 @@ export class RpcListener {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(
|
.when(
|
||||||
shape({ id: idType, method: string }, ["id"]),
|
shape({ id: idType.optional(), method: string }),
|
||||||
({ id, method }) => ({
|
({ id, method }) => ({
|
||||||
jsonrpc,
|
jsonrpc,
|
||||||
id,
|
id,
|
||||||
@@ -411,8 +411,8 @@ export class RpcListener {
|
|||||||
private getResult(
|
private getResult(
|
||||||
procedure: typeof jsonPath._TYPE,
|
procedure: typeof jsonPath._TYPE,
|
||||||
system: System,
|
system: System,
|
||||||
procedureId: string,
|
eventId: string,
|
||||||
timeout: number | undefined,
|
timeout: number | null | undefined,
|
||||||
input: any,
|
input: any,
|
||||||
) {
|
) {
|
||||||
const ensureResultTypeShape = (
|
const ensureResultTypeShape = (
|
||||||
@@ -420,9 +420,9 @@ export class RpcListener {
|
|||||||
): { result: any } => {
|
): { result: any } => {
|
||||||
return { result }
|
return { result }
|
||||||
}
|
}
|
||||||
const callbacks = this.callbackHolderFor(procedure)
|
const callbacks = this.callbacks?.child(procedure)
|
||||||
const effects = makeEffects({
|
const effects = makeEffects({
|
||||||
procedureId,
|
eventId,
|
||||||
callbacks,
|
callbacks,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -430,16 +430,6 @@ export class RpcListener {
|
|||||||
switch (procedure) {
|
switch (procedure) {
|
||||||
case "/backup/create":
|
case "/backup/create":
|
||||||
return system.createBackup(effects, timeout || null)
|
return system.createBackup(effects, timeout || null)
|
||||||
case "/backup/restore":
|
|
||||||
return system.restoreBackup(effects, timeout || null)
|
|
||||||
case "/packageInit":
|
|
||||||
return system.packageInit(effects, timeout || null)
|
|
||||||
case "/packageUninit":
|
|
||||||
return system.packageUninit(
|
|
||||||
effects,
|
|
||||||
string.optional().unsafeCast(input),
|
|
||||||
timeout || null,
|
|
||||||
)
|
|
||||||
default:
|
default:
|
||||||
const procedures = unNestPath(procedure)
|
const procedures = unNestPath(procedure)
|
||||||
switch (true) {
|
switch (true) {
|
||||||
@@ -461,14 +451,10 @@ export class RpcListener {
|
|||||||
})().then(ensureResultTypeShape, (error) =>
|
})().then(ensureResultTypeShape, (error) =>
|
||||||
matches(error)
|
matches(error)
|
||||||
.when(
|
.when(
|
||||||
object(
|
object({
|
||||||
{
|
error: string,
|
||||||
error: string,
|
code: number.defaultTo(0),
|
||||||
code: number,
|
}),
|
||||||
},
|
|
||||||
["code"],
|
|
||||||
{ code: 0 },
|
|
||||||
),
|
|
||||||
(error) => ({
|
(error) => ({
|
||||||
error: {
|
error: {
|
||||||
code: error.code,
|
code: error.code,
|
||||||
|
|||||||
@@ -7,13 +7,22 @@ import { Volume } from "./matchVolume"
|
|||||||
import {
|
import {
|
||||||
CommandOptions,
|
CommandOptions,
|
||||||
ExecOptions,
|
ExecOptions,
|
||||||
ExecSpawnable,
|
SubContainerOwned,
|
||||||
} from "@start9labs/start-sdk/package/lib/util/SubContainer"
|
} from "@start9labs/start-sdk/package/lib/util/SubContainer"
|
||||||
|
import { Mounts } from "@start9labs/start-sdk/package/lib/mainFn/Mounts"
|
||||||
|
import { Manifest } from "@start9labs/start-sdk/base/lib/osBindings"
|
||||||
|
import { BackupEffects } from "@start9labs/start-sdk/package/lib/backup/Backups"
|
||||||
|
import { Drop } from "@start9labs/start-sdk/package/lib/util"
|
||||||
|
import { SDKManifest } from "@start9labs/start-sdk/base/lib/types"
|
||||||
export const exec = promisify(cp.exec)
|
export const exec = promisify(cp.exec)
|
||||||
export const execFile = promisify(cp.execFile)
|
export const execFile = promisify(cp.execFile)
|
||||||
|
|
||||||
export class DockerProcedureContainer {
|
export class DockerProcedureContainer extends Drop {
|
||||||
private constructor(private readonly subcontainer: ExecSpawnable) {}
|
private constructor(
|
||||||
|
private readonly subcontainer: SubContainer<SDKManifest>,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
static async of(
|
static async of(
|
||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
@@ -21,7 +30,7 @@ export class DockerProcedureContainer {
|
|||||||
data: DockerProcedure,
|
data: DockerProcedure,
|
||||||
volumes: { [id: VolumeId]: Volume },
|
volumes: { [id: VolumeId]: Volume },
|
||||||
name: string,
|
name: string,
|
||||||
options: { subcontainer?: ExecSpawnable } = {},
|
options: { subcontainer?: SubContainer<SDKManifest> } = {},
|
||||||
) {
|
) {
|
||||||
const subcontainer =
|
const subcontainer =
|
||||||
options?.subcontainer ??
|
options?.subcontainer ??
|
||||||
@@ -41,9 +50,10 @@ export class DockerProcedureContainer {
|
|||||||
volumes: { [id: VolumeId]: Volume },
|
volumes: { [id: VolumeId]: Volume },
|
||||||
name: string,
|
name: string,
|
||||||
) {
|
) {
|
||||||
const subcontainer = await SubContainer.of(
|
const subcontainer = await SubContainerOwned.of(
|
||||||
effects,
|
effects as BackupEffects,
|
||||||
{ imageId: data.image },
|
{ imageId: data.image },
|
||||||
|
null,
|
||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,13 +67,19 @@ export class DockerProcedureContainer {
|
|||||||
const volumeMount = volumes[mount]
|
const volumeMount = volumes[mount]
|
||||||
if (volumeMount.type === "data") {
|
if (volumeMount.type === "data") {
|
||||||
await subcontainer.mount(
|
await subcontainer.mount(
|
||||||
{ type: "volume", id: mount, subpath: null, readonly: false },
|
Mounts.of().mountVolume({
|
||||||
mounts[mount],
|
volumeId: mount,
|
||||||
|
subpath: null,
|
||||||
|
mountpoint: mounts[mount],
|
||||||
|
readonly: false,
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
} else if (volumeMount.type === "assets") {
|
} else if (volumeMount.type === "assets") {
|
||||||
await subcontainer.mount(
|
await subcontainer.mount(
|
||||||
{ type: "assets", subpath: mount },
|
Mounts.of().mountAssets({
|
||||||
mounts[mount],
|
subpath: mount,
|
||||||
|
mountpoint: mounts[mount],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
} else if (volumeMount.type === "certificate") {
|
} else if (volumeMount.type === "certificate") {
|
||||||
const hostnames = [
|
const hostnames = [
|
||||||
@@ -95,21 +111,22 @@ export class DockerProcedureContainer {
|
|||||||
key,
|
key,
|
||||||
)
|
)
|
||||||
} else if (volumeMount.type === "pointer") {
|
} else if (volumeMount.type === "pointer") {
|
||||||
await effects
|
await effects.mount({
|
||||||
.mount({
|
location: path,
|
||||||
location: path,
|
target: {
|
||||||
target: {
|
packageId: volumeMount["package-id"],
|
||||||
packageId: volumeMount["package-id"],
|
subpath: volumeMount.path,
|
||||||
subpath: volumeMount.path,
|
readonly: volumeMount.readonly,
|
||||||
readonly: volumeMount.readonly,
|
volumeId: volumeMount["volume-id"],
|
||||||
volumeId: volumeMount["volume-id"],
|
idmap: [],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.catch(console.warn)
|
|
||||||
} else if (volumeMount.type === "backup") {
|
} else if (volumeMount.type === "backup") {
|
||||||
await subcontainer.mount(
|
await subcontainer.mount(
|
||||||
{ type: "backup", subpath: null },
|
Mounts.of().mountBackups({
|
||||||
mounts[mount],
|
subpath: null,
|
||||||
|
mountpoint: mounts[mount],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,7 +168,11 @@ export class DockerProcedureContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async spawn(commands: string[]): Promise<cp.ChildProcess> {
|
// async spawn(commands: string[]): Promise<cp.ChildProcess> {
|
||||||
return await this.subcontainer.spawn(commands)
|
// return await this.subcontainer.spawn(commands)
|
||||||
|
// }
|
||||||
|
|
||||||
|
onDrop(): void {
|
||||||
|
this.subcontainer.destroy?.()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { Daemon } from "@start9labs/start-sdk/package/lib/mainFn/Daemon"
|
|||||||
import { Effects } from "../../../Models/Effects"
|
import { Effects } from "../../../Models/Effects"
|
||||||
import { off } from "node:process"
|
import { off } from "node:process"
|
||||||
import { CommandController } from "@start9labs/start-sdk/package/lib/mainFn/CommandController"
|
import { CommandController } from "@start9labs/start-sdk/package/lib/mainFn/CommandController"
|
||||||
|
import { SDKManifest } from "@start9labs/start-sdk/base/lib/types"
|
||||||
|
import { SubContainerRc } from "@start9labs/start-sdk/package/lib/util/SubContainer"
|
||||||
|
|
||||||
const EMBASSY_HEALTH_INTERVAL = 15 * 1000
|
const EMBASSY_HEALTH_INTERVAL = 15 * 1000
|
||||||
const EMBASSY_PROPERTIES_LOOP = 30 * 1000
|
const EMBASSY_PROPERTIES_LOOP = 30 * 1000
|
||||||
@@ -15,8 +17,13 @@ const EMBASSY_PROPERTIES_LOOP = 30 * 1000
|
|||||||
* Also, this has an ability to clean itself up too if need be.
|
* Also, this has an ability to clean itself up too if need be.
|
||||||
*/
|
*/
|
||||||
export class MainLoop {
|
export class MainLoop {
|
||||||
|
private subcontainerRc?: SubContainerRc<SDKManifest>
|
||||||
get mainSubContainerHandle() {
|
get mainSubContainerHandle() {
|
||||||
return this.mainEvent?.daemon?.subContainerHandle
|
this.subcontainerRc =
|
||||||
|
this.subcontainerRc ??
|
||||||
|
this.mainEvent?.daemon?.subcontainerRc() ??
|
||||||
|
undefined
|
||||||
|
return this.subcontainerRc
|
||||||
}
|
}
|
||||||
private healthLoops?: {
|
private healthLoops?: {
|
||||||
name: string
|
name: string
|
||||||
@@ -24,7 +31,7 @@ export class MainLoop {
|
|||||||
}[]
|
}[]
|
||||||
|
|
||||||
private mainEvent?: {
|
private mainEvent?: {
|
||||||
daemon: Daemon
|
daemon: Daemon<SDKManifest>
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
@@ -55,28 +62,20 @@ export class MainLoop {
|
|||||||
if (jsMain) {
|
if (jsMain) {
|
||||||
throw new Error("Unreachable")
|
throw new Error("Unreachable")
|
||||||
}
|
}
|
||||||
const daemon = new Daemon(async () => {
|
const subcontainer = await DockerProcedureContainer.createSubContainer(
|
||||||
const subcontainer = await DockerProcedureContainer.createSubContainer(
|
effects,
|
||||||
effects,
|
this.system.manifest.id,
|
||||||
this.system.manifest.id,
|
this.system.manifest.main,
|
||||||
this.system.manifest.main,
|
this.system.manifest.volumes,
|
||||||
this.system.manifest.volumes,
|
`Main - ${currentCommand.join(" ")}`,
|
||||||
`Main - ${currentCommand.join(" ")}`,
|
)
|
||||||
)
|
const daemon = await Daemon.of()(this.effects, subcontainer, {
|
||||||
return CommandController.of()(
|
command: currentCommand,
|
||||||
this.effects,
|
runAsInit: true,
|
||||||
subcontainer,
|
env: {
|
||||||
currentCommand,
|
TINI_SUBREAPER: "true",
|
||||||
{
|
},
|
||||||
runAsInit: true,
|
sigtermTimeout: utils.inMs(this.system.manifest.main["sigterm-timeout"]),
|
||||||
env: {
|
|
||||||
TINI_SUBREAPER: "true",
|
|
||||||
},
|
|
||||||
sigtermTimeout: utils.inMs(
|
|
||||||
this.system.manifest.main["sigterm-timeout"],
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
daemon.start()
|
daemon.start()
|
||||||
@@ -121,6 +120,7 @@ export class MainLoop {
|
|||||||
? {
|
? {
|
||||||
preferredExternalPort: lanConf.external,
|
preferredExternalPort: lanConf.external,
|
||||||
alpn: { specified: ["http/1.1"] },
|
alpn: { specified: ["http/1.1"] },
|
||||||
|
addXForwardedHeaders: false,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
})
|
})
|
||||||
@@ -134,7 +134,7 @@ export class MainLoop {
|
|||||||
delete this.mainEvent
|
delete this.mainEvent
|
||||||
delete this.healthLoops
|
delete this.healthLoops
|
||||||
await main?.daemon
|
await main?.daemon
|
||||||
.stop()
|
.term()
|
||||||
.catch((e: unknown) => console.error(`Main loop error`, utils.asError(e)))
|
.catch((e: unknown) => console.error(`Main loop error`, utils.asError(e)))
|
||||||
this.effects.setMainStatus({ status: "stopped" })
|
this.effects.setMainStatus({ status: "stopped" })
|
||||||
if (healthLoops) healthLoops.forEach((x) => clearInterval(x.interval))
|
if (healthLoops) healthLoops.forEach((x) => clearInterval(x.interval))
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
export default {
|
||||||
|
nodes: {
|
||||||
|
type: "list",
|
||||||
|
subtype: "union",
|
||||||
|
name: "Lightning Nodes",
|
||||||
|
description: "List of Lightning Network node instances to manage",
|
||||||
|
range: "[1,*)",
|
||||||
|
default: ["lnd"],
|
||||||
|
spec: {
|
||||||
|
type: "string",
|
||||||
|
"display-as": "{{name}}",
|
||||||
|
"unique-by": "name",
|
||||||
|
name: "Node Implementation",
|
||||||
|
tag: {
|
||||||
|
id: "type",
|
||||||
|
name: "Type",
|
||||||
|
description:
|
||||||
|
"- LND: Lightning Network Daemon from Lightning Labs\n- CLN: Core Lightning from Blockstream\n",
|
||||||
|
"variant-names": {
|
||||||
|
lnd: "Lightning Network Daemon (LND)",
|
||||||
|
"c-lightning": "Core Lightning (CLN)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: "lnd",
|
||||||
|
variants: {
|
||||||
|
lnd: {
|
||||||
|
name: {
|
||||||
|
type: "string",
|
||||||
|
name: "Node Name",
|
||||||
|
description: "Name of this node in the list",
|
||||||
|
default: "StartOS LND",
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
|
"connection-settings": {
|
||||||
|
type: "union",
|
||||||
|
name: "Connection Settings",
|
||||||
|
description: "The Lightning Network Daemon node to connect to.",
|
||||||
|
tag: {
|
||||||
|
id: "type",
|
||||||
|
name: "Type",
|
||||||
|
description:
|
||||||
|
"- Internal: The Lightning Network Daemon service installed to your StartOS server.\n- External: A Lightning Network Daemon instance running on a remote device (advanced).\n",
|
||||||
|
"variant-names": {
|
||||||
|
internal: "Internal",
|
||||||
|
external: "External",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: "internal",
|
||||||
|
variants: {
|
||||||
|
internal: {},
|
||||||
|
external: {
|
||||||
|
address: {
|
||||||
|
type: "string",
|
||||||
|
name: "Public Address",
|
||||||
|
description:
|
||||||
|
"The public address of your LND REST server\nNOTE: RTL does not support a .onion URL here\n",
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
|
"rest-port": {
|
||||||
|
type: "number",
|
||||||
|
name: "REST Port",
|
||||||
|
description:
|
||||||
|
"The port that your Lightning Network Daemon REST server is bound to",
|
||||||
|
nullable: false,
|
||||||
|
range: "[0,65535]",
|
||||||
|
integral: true,
|
||||||
|
default: 8080,
|
||||||
|
},
|
||||||
|
macaroon: {
|
||||||
|
type: "string",
|
||||||
|
name: "Macaroon",
|
||||||
|
description:
|
||||||
|
'Your admin.macaroon file, Base64URL encoded. This is the same as the value after "macaroon=" in your lndconnect URL.',
|
||||||
|
nullable: false,
|
||||||
|
masked: true,
|
||||||
|
pattern: "[=A-Za-z0-9_-]+",
|
||||||
|
"pattern-description":
|
||||||
|
"Macaroon must be encoded in Base64URL format (only A-Z, a-z, 0-9, _, - and = allowed)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"c-lightning": {
|
||||||
|
name: {
|
||||||
|
type: "string",
|
||||||
|
name: "Node Name",
|
||||||
|
description: "Name of this node in the list",
|
||||||
|
default: "StartOS CLN",
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
|
"connection-settings": {
|
||||||
|
type: "union",
|
||||||
|
name: "Connection Settings",
|
||||||
|
description: "The Core Lightning (CLN) node to connect to.",
|
||||||
|
tag: {
|
||||||
|
id: "type",
|
||||||
|
name: "Type",
|
||||||
|
description:
|
||||||
|
"- Internal: The Core Lightning (CLN) service installed to your StartOS server.\n- External: A Core Lightning (CLN) instance running on a remote device (advanced).\n",
|
||||||
|
"variant-names": {
|
||||||
|
internal: "Internal",
|
||||||
|
external: "External",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: "internal",
|
||||||
|
variants: {
|
||||||
|
internal: {},
|
||||||
|
external: {
|
||||||
|
address: {
|
||||||
|
type: "string",
|
||||||
|
name: "Public Address",
|
||||||
|
description:
|
||||||
|
"The public address of your CLNRest server\nNOTE: RTL does not support a .onion URL here\n",
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
|
"rest-port": {
|
||||||
|
type: "number",
|
||||||
|
name: "CLNRest Port",
|
||||||
|
description: "The port that your CLNRest server is bound to",
|
||||||
|
nullable: false,
|
||||||
|
range: "[0,65535]",
|
||||||
|
integral: true,
|
||||||
|
default: 3010,
|
||||||
|
},
|
||||||
|
macaroon: {
|
||||||
|
type: "string",
|
||||||
|
name: "Rune",
|
||||||
|
description:
|
||||||
|
"Your CLNRest unrestricted Rune, Base64URL encoded.",
|
||||||
|
nullable: false,
|
||||||
|
masked: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: "string",
|
||||||
|
name: "Password",
|
||||||
|
description: "The password for your Ride the Lightning dashboard",
|
||||||
|
nullable: false,
|
||||||
|
copyable: true,
|
||||||
|
masked: true,
|
||||||
|
default: {
|
||||||
|
charset: "a-z,A-Z,0-9",
|
||||||
|
len: 22,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,5 +1,30 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`transformConfigSpec transformConfigSpec(RTL) 1`] = `
|
||||||
|
{
|
||||||
|
"password": {
|
||||||
|
"default": {
|
||||||
|
"charset": "a-z,A-Z,0-9",
|
||||||
|
"len": 22,
|
||||||
|
},
|
||||||
|
"description": "The password for your Ride the Lightning dashboard",
|
||||||
|
"disabled": false,
|
||||||
|
"generate": null,
|
||||||
|
"immutable": false,
|
||||||
|
"inputmode": "text",
|
||||||
|
"masked": true,
|
||||||
|
"maxLength": null,
|
||||||
|
"minLength": null,
|
||||||
|
"name": "Password",
|
||||||
|
"patterns": [],
|
||||||
|
"placeholder": null,
|
||||||
|
"required": true,
|
||||||
|
"type": "text",
|
||||||
|
"warning": null,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`transformConfigSpec transformConfigSpec(bitcoind) 1`] = `
|
exports[`transformConfigSpec transformConfigSpec(bitcoind) 1`] = `
|
||||||
{
|
{
|
||||||
"advanced": {
|
"advanced": {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
ExtendedVersion,
|
ExtendedVersion,
|
||||||
|
FileHelper,
|
||||||
|
getDataVersion,
|
||||||
|
overlaps,
|
||||||
types as T,
|
types as T,
|
||||||
utils,
|
utils,
|
||||||
VersionRange,
|
VersionRange,
|
||||||
@@ -55,8 +58,21 @@ function todo(): never {
|
|||||||
|
|
||||||
const MANIFEST_LOCATION = "/usr/lib/startos/package/embassyManifest.json"
|
const MANIFEST_LOCATION = "/usr/lib/startos/package/embassyManifest.json"
|
||||||
export const EMBASSY_JS_LOCATION = "/usr/lib/startos/package/embassy.js"
|
export const EMBASSY_JS_LOCATION = "/usr/lib/startos/package/embassy.js"
|
||||||
const EMBASSY_POINTER_PATH_PREFIX = "/embassyConfig" as utils.StorePath
|
|
||||||
const EMBASSY_DEPENDS_ON_PATH_PREFIX = "/embassyDependsOn" as utils.StorePath
|
const configFile = FileHelper.json(
|
||||||
|
{
|
||||||
|
volumeId: "embassy",
|
||||||
|
subpath: "config.json",
|
||||||
|
},
|
||||||
|
matches.any,
|
||||||
|
)
|
||||||
|
const dependsOnFile = FileHelper.json(
|
||||||
|
{
|
||||||
|
volumeId: "embassy",
|
||||||
|
subpath: "dependsOn.json",
|
||||||
|
},
|
||||||
|
dictionary([string, array(string)]),
|
||||||
|
)
|
||||||
|
|
||||||
const matchResult = object({
|
const matchResult = object({
|
||||||
result: any,
|
result: any,
|
||||||
@@ -94,47 +110,48 @@ const fromReturnType = <A>(a: U.ResultType<A>): A => {
|
|||||||
return assertNever(a)
|
return assertNever(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchSetResult = object(
|
const matchSetResult = object({
|
||||||
{
|
"depends-on": dictionary([string, array(string)])
|
||||||
"depends-on": dictionary([string, array(string)]),
|
.nullable()
|
||||||
dependsOn: dictionary([string, array(string)]),
|
.optional(),
|
||||||
signal: literals(
|
dependsOn: dictionary([string, array(string)])
|
||||||
"SIGTERM",
|
.nullable()
|
||||||
"SIGHUP",
|
.optional(),
|
||||||
"SIGINT",
|
signal: literals(
|
||||||
"SIGQUIT",
|
"SIGTERM",
|
||||||
"SIGILL",
|
"SIGHUP",
|
||||||
"SIGTRAP",
|
"SIGINT",
|
||||||
"SIGABRT",
|
"SIGQUIT",
|
||||||
"SIGBUS",
|
"SIGILL",
|
||||||
"SIGFPE",
|
"SIGTRAP",
|
||||||
"SIGKILL",
|
"SIGABRT",
|
||||||
"SIGUSR1",
|
"SIGBUS",
|
||||||
"SIGSEGV",
|
"SIGFPE",
|
||||||
"SIGUSR2",
|
"SIGKILL",
|
||||||
"SIGPIPE",
|
"SIGUSR1",
|
||||||
"SIGALRM",
|
"SIGSEGV",
|
||||||
"SIGSTKFLT",
|
"SIGUSR2",
|
||||||
"SIGCHLD",
|
"SIGPIPE",
|
||||||
"SIGCONT",
|
"SIGALRM",
|
||||||
"SIGSTOP",
|
"SIGSTKFLT",
|
||||||
"SIGTSTP",
|
"SIGCHLD",
|
||||||
"SIGTTIN",
|
"SIGCONT",
|
||||||
"SIGTTOU",
|
"SIGSTOP",
|
||||||
"SIGURG",
|
"SIGTSTP",
|
||||||
"SIGXCPU",
|
"SIGTTIN",
|
||||||
"SIGXFSZ",
|
"SIGTTOU",
|
||||||
"SIGVTALRM",
|
"SIGURG",
|
||||||
"SIGPROF",
|
"SIGXCPU",
|
||||||
"SIGWINCH",
|
"SIGXFSZ",
|
||||||
"SIGIO",
|
"SIGVTALRM",
|
||||||
"SIGPWR",
|
"SIGPROF",
|
||||||
"SIGSYS",
|
"SIGWINCH",
|
||||||
"SIGINFO",
|
"SIGIO",
|
||||||
),
|
"SIGPWR",
|
||||||
},
|
"SIGSYS",
|
||||||
["depends-on", "dependsOn"],
|
"SIGINFO",
|
||||||
)
|
),
|
||||||
|
})
|
||||||
|
|
||||||
type OldGetConfigRes = {
|
type OldGetConfigRes = {
|
||||||
config?: null | Record<string, unknown>
|
config?: null | Record<string, unknown>
|
||||||
@@ -174,14 +191,14 @@ export type PackagePropertiesV2 = {
|
|||||||
}
|
}
|
||||||
export type PackagePropertyString = {
|
export type PackagePropertyString = {
|
||||||
type: "string"
|
type: "string"
|
||||||
description?: string
|
description?: string | null
|
||||||
value: string
|
value: string
|
||||||
/** Let's the ui make this copyable button */
|
/** Let's the ui make this copyable button */
|
||||||
copyable?: boolean
|
copyable?: boolean | null
|
||||||
/** Let the ui create a qr for this field */
|
/** Let the ui create a qr for this field */
|
||||||
qr?: boolean
|
qr?: boolean | null
|
||||||
/** Hiding the value unless toggled off for field */
|
/** Hiding the value unless toggled off for field */
|
||||||
masked?: boolean
|
masked?: boolean | null
|
||||||
}
|
}
|
||||||
export type PackagePropertyObject = {
|
export type PackagePropertyObject = {
|
||||||
value: PackagePropertiesV2
|
value: PackagePropertiesV2
|
||||||
@@ -225,17 +242,14 @@ const matchPackagePropertyObject: Parser<unknown, PackagePropertyObject> =
|
|||||||
})
|
})
|
||||||
|
|
||||||
const matchPackagePropertyString: Parser<unknown, PackagePropertyString> =
|
const matchPackagePropertyString: Parser<unknown, PackagePropertyString> =
|
||||||
object(
|
object({
|
||||||
{
|
type: literal("string"),
|
||||||
type: literal("string"),
|
description: string.nullable().optional(),
|
||||||
description: string,
|
value: string,
|
||||||
value: string,
|
copyable: boolean.nullable().optional(),
|
||||||
copyable: boolean,
|
qr: boolean.nullable().optional(),
|
||||||
qr: boolean,
|
masked: boolean.nullable().optional(),
|
||||||
masked: boolean,
|
})
|
||||||
},
|
|
||||||
["copyable", "description", "qr", "masked"],
|
|
||||||
)
|
|
||||||
setMatchPackageProperties(
|
setMatchPackageProperties(
|
||||||
dictionary([
|
dictionary([
|
||||||
string,
|
string,
|
||||||
@@ -275,6 +289,7 @@ function convertProperties(
|
|||||||
|
|
||||||
const DEFAULT_REGISTRY = "https://registry.start9.com"
|
const DEFAULT_REGISTRY = "https://registry.start9.com"
|
||||||
export class SystemForEmbassy implements System {
|
export class SystemForEmbassy implements System {
|
||||||
|
private version: ExtendedVersion
|
||||||
currentRunning: MainLoop | undefined
|
currentRunning: MainLoop | undefined
|
||||||
static async of(manifestLocation: string = MANIFEST_LOCATION) {
|
static async of(manifestLocation: string = MANIFEST_LOCATION) {
|
||||||
const moduleCode = await import(EMBASSY_JS_LOCATION)
|
const moduleCode = await import(EMBASSY_JS_LOCATION)
|
||||||
@@ -296,11 +311,37 @@ export class SystemForEmbassy implements System {
|
|||||||
constructor(
|
constructor(
|
||||||
readonly manifest: Manifest,
|
readonly manifest: Manifest,
|
||||||
readonly moduleCode: Partial<U.ExpectedExports>,
|
readonly moduleCode: Partial<U.ExpectedExports>,
|
||||||
) {}
|
) {
|
||||||
|
this.version = ExtendedVersion.parseEmver(manifest.version)
|
||||||
|
if (
|
||||||
|
this.manifest.id === "bitcoind" &&
|
||||||
|
this.manifest.title.toLowerCase().includes("knots")
|
||||||
|
)
|
||||||
|
this.version.flavor = "knots"
|
||||||
|
|
||||||
async containerInit(effects: Effects): Promise<void> {
|
if (
|
||||||
|
this.manifest.id === "lnd" ||
|
||||||
|
this.manifest.id === "ride-the-lightning" ||
|
||||||
|
this.manifest.id === "datum"
|
||||||
|
) {
|
||||||
|
this.version.upstream.prerelease = ["beta"]
|
||||||
|
} else if (
|
||||||
|
this.manifest.id === "lightning-terminal" ||
|
||||||
|
this.manifest.id === "robosats"
|
||||||
|
) {
|
||||||
|
this.version.upstream.prerelease = ["alpha"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(
|
||||||
|
effects: Effects,
|
||||||
|
kind: "install" | "update" | "restore" | null,
|
||||||
|
): Promise<void> {
|
||||||
|
if (kind === "restore") {
|
||||||
|
await this.restoreBackup(effects, null)
|
||||||
|
}
|
||||||
for (let depId in this.manifest.dependencies) {
|
for (let depId in this.manifest.dependencies) {
|
||||||
if (this.manifest.dependencies[depId].config) {
|
if (this.manifest.dependencies[depId]?.config) {
|
||||||
await this.dependenciesAutoconfig(effects, depId, null)
|
await this.dependenciesAutoconfig(effects, depId, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,6 +349,9 @@ export class SystemForEmbassy implements System {
|
|||||||
await this.exportActions(effects)
|
await this.exportActions(effects)
|
||||||
await this.exportNetwork(effects)
|
await this.exportNetwork(effects)
|
||||||
await this.containerSetDependencies(effects)
|
await this.containerSetDependencies(effects)
|
||||||
|
if (kind === "install" || kind === "update") {
|
||||||
|
await this.packageInit(effects, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async containerSetDependencies(effects: T.Effects) {
|
async containerSetDependencies(effects: T.Effects) {
|
||||||
const oldDeps: Record<string, string[]> = Object.fromEntries(
|
const oldDeps: Record<string, string[]> = Object.fromEntries(
|
||||||
@@ -347,16 +391,23 @@ export class SystemForEmbassy implements System {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async packageInit(effects: Effects, timeoutMs: number | null): Promise<void> {
|
async packageInit(effects: Effects, timeoutMs: number | null): Promise<void> {
|
||||||
const previousVersion = await effects.getDataVersion()
|
const previousVersion = await getDataVersion(effects)
|
||||||
if (previousVersion) {
|
if (previousVersion) {
|
||||||
if (
|
const migrationRes = await this.migration(
|
||||||
(await this.migration(effects, { from: previousVersion }, timeoutMs))
|
effects,
|
||||||
.configured
|
{ from: previousVersion },
|
||||||
) {
|
timeoutMs,
|
||||||
await effects.action.clearRequests({ only: ["needs-config"] })
|
)
|
||||||
|
if (migrationRes) {
|
||||||
|
if (migrationRes.configured)
|
||||||
|
await effects.action.clearTasks({ only: ["needs-config"] })
|
||||||
|
await configFile.write(
|
||||||
|
effects,
|
||||||
|
await this.getConfig(effects, timeoutMs),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else if (this.manifest.config) {
|
} else if (this.manifest.config) {
|
||||||
await effects.action.request({
|
await effects.action.createTask({
|
||||||
packageId: this.manifest.id,
|
packageId: this.manifest.id,
|
||||||
actionId: "config",
|
actionId: "config",
|
||||||
severity: "critical",
|
severity: "critical",
|
||||||
@@ -364,9 +415,11 @@ export class SystemForEmbassy implements System {
|
|||||||
reason: "This service must be configured before it can be run",
|
reason: "This service must be configured before it can be run",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await effects.setDataVersion({
|
await effects.setDataVersion({
|
||||||
version: ExtendedVersion.parseEmver(this.manifest.version).toString(),
|
version: this.version.toString(),
|
||||||
})
|
})
|
||||||
|
// @FullMetal: package hacks go here
|
||||||
}
|
}
|
||||||
async exportNetwork(effects: Effects) {
|
async exportNetwork(effects: Effects) {
|
||||||
for (const [id, interfaceValue] of Object.entries(
|
for (const [id, interfaceValue] of Object.entries(
|
||||||
@@ -403,6 +456,7 @@ export class SystemForEmbassy implements System {
|
|||||||
addSsl = {
|
addSsl = {
|
||||||
preferredExternalPort: lanPortNum,
|
preferredExternalPort: lanPortNum,
|
||||||
alpn: { specified: [] },
|
alpn: { specified: [] },
|
||||||
|
addXForwardedHeaders: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
@@ -441,7 +495,7 @@ export class SystemForEmbassy implements System {
|
|||||||
masked: false,
|
masked: false,
|
||||||
path: "",
|
path: "",
|
||||||
schemeOverride: null,
|
schemeOverride: null,
|
||||||
search: {},
|
query: {},
|
||||||
username: null,
|
username: null,
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
@@ -456,13 +510,18 @@ export class SystemForEmbassy implements System {
|
|||||||
): Promise<T.ActionInput | null> {
|
): Promise<T.ActionInput | null> {
|
||||||
if (actionId === "config") {
|
if (actionId === "config") {
|
||||||
const config = await this.getConfig(effects, timeoutMs)
|
const config = await this.getConfig(effects, timeoutMs)
|
||||||
return { spec: config.spec, value: config.config }
|
return {
|
||||||
|
eventId: effects.eventId!,
|
||||||
|
spec: config.spec,
|
||||||
|
value: config.config,
|
||||||
|
}
|
||||||
} else if (actionId === "properties") {
|
} else if (actionId === "properties") {
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
const oldSpec = this.manifest.actions?.[actionId]?.["input-spec"]
|
const oldSpec = this.manifest.actions?.[actionId]?.["input-spec"]
|
||||||
if (!oldSpec) return null
|
if (!oldSpec) return null
|
||||||
return {
|
return {
|
||||||
|
eventId: effects.eventId!,
|
||||||
spec: transformConfigSpec(oldSpec as OldConfigSpec),
|
spec: transformConfigSpec(oldSpec as OldConfigSpec),
|
||||||
value: null,
|
value: null,
|
||||||
}
|
}
|
||||||
@@ -543,14 +602,14 @@ export class SystemForEmbassy implements System {
|
|||||||
}
|
}
|
||||||
await effects.action.clear({ except: Object.keys(actions) })
|
await effects.action.clear({ except: Object.keys(actions) })
|
||||||
}
|
}
|
||||||
async packageUninit(
|
async uninit(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
nextVersion: Optional<string>,
|
target: ExtendedVersion | VersionRange | null,
|
||||||
timeoutMs: number | null,
|
timeoutMs?: number | null,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.currentRunning?.clean({ timeout: timeoutMs ?? undefined })
|
await this.currentRunning?.clean({ timeout: timeoutMs ?? undefined })
|
||||||
if (nextVersion) {
|
if (target) {
|
||||||
await this.migration(effects, { to: nextVersion }, timeoutMs)
|
await this.migration(effects, { to: target }, timeoutMs ?? null)
|
||||||
}
|
}
|
||||||
await effects.setMainStatus({ status: "stopped" })
|
await effects.setMainStatus({ status: "stopped" })
|
||||||
}
|
}
|
||||||
@@ -577,11 +636,21 @@ export class SystemForEmbassy implements System {
|
|||||||
const moduleCode = await this.moduleCode
|
const moduleCode = await this.moduleCode
|
||||||
await moduleCode.createBackup?.(polyfillEffects(effects, this.manifest))
|
await moduleCode.createBackup?.(polyfillEffects(effects, this.manifest))
|
||||||
}
|
}
|
||||||
|
const dataVersion = await effects.getDataVersion()
|
||||||
|
if (dataVersion)
|
||||||
|
await fs.writeFile("/media/startos/backup/dataVersion.txt", dataVersion, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
async restoreBackup(
|
async restoreBackup(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const store = await fs
|
||||||
|
.readFile("/media/startos/backup/store.json", {
|
||||||
|
encoding: "utf-8",
|
||||||
|
})
|
||||||
|
.catch((_) => null)
|
||||||
const restoreBackup = this.manifest.backup.restore
|
const restoreBackup = this.manifest.backup.restore
|
||||||
if (restoreBackup.type === "docker") {
|
if (restoreBackup.type === "docker") {
|
||||||
const commands = [restoreBackup.entrypoint, ...restoreBackup.args]
|
const commands = [restoreBackup.entrypoint, ...restoreBackup.args]
|
||||||
@@ -600,6 +669,13 @@ export class SystemForEmbassy implements System {
|
|||||||
const moduleCode = await this.moduleCode
|
const moduleCode = await this.moduleCode
|
||||||
await moduleCode.restoreBackup?.(polyfillEffects(effects, this.manifest))
|
await moduleCode.restoreBackup?.(polyfillEffects(effects, this.manifest))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dataVersion = await fs
|
||||||
|
.readFile("/media/startos/backup/dataVersion.txt", {
|
||||||
|
encoding: "utf-8",
|
||||||
|
})
|
||||||
|
.catch((_) => null)
|
||||||
|
if (dataVersion) await effects.setDataVersion({ version: dataVersion })
|
||||||
}
|
}
|
||||||
async getConfig(effects: Effects, timeoutMs: number | null) {
|
async getConfig(effects: Effects, timeoutMs: number | null) {
|
||||||
return this.getConfigUncleaned(effects, timeoutMs).then(convertToNewConfig)
|
return this.getConfigUncleaned(effects, timeoutMs).then(convertToNewConfig)
|
||||||
@@ -649,10 +725,7 @@ export class SystemForEmbassy implements System {
|
|||||||
structuredClone(newConfigWithoutPointers as Record<string, unknown>),
|
structuredClone(newConfigWithoutPointers as Record<string, unknown>),
|
||||||
)
|
)
|
||||||
await updateConfig(effects, this.manifest, spec, newConfig)
|
await updateConfig(effects, this.manifest, spec, newConfig)
|
||||||
await effects.store.set({
|
await configFile.write(effects, newConfig)
|
||||||
path: EMBASSY_POINTER_PATH_PREFIX,
|
|
||||||
value: newConfig,
|
|
||||||
})
|
|
||||||
const setConfigValue = this.manifest.config?.set
|
const setConfigValue = this.manifest.config?.set
|
||||||
if (!setConfigValue) return
|
if (!setConfigValue) return
|
||||||
if (setConfigValue.type === "docker") {
|
if (setConfigValue.type === "docker") {
|
||||||
@@ -706,15 +779,11 @@ export class SystemForEmbassy implements System {
|
|||||||
rawDepends: { [x: string]: readonly string[] },
|
rawDepends: { [x: string]: readonly string[] },
|
||||||
configuring: boolean,
|
configuring: boolean,
|
||||||
) {
|
) {
|
||||||
const storedDependsOn = (await effects.store.get({
|
const storedDependsOn = await dependsOnFile.read().once()
|
||||||
packageId: this.manifest.id,
|
|
||||||
path: EMBASSY_DEPENDS_ON_PATH_PREFIX,
|
|
||||||
})) as Record<string, readonly string[]>
|
|
||||||
|
|
||||||
const requiredDeps = {
|
const requiredDeps = {
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
Object.entries(this.manifest.dependencies || {})
|
Object.entries(this.manifest.dependencies ?? {})
|
||||||
?.filter((x) => x[1].requirement.type === "required")
|
.filter(([k, v]) => v?.requirement.type === "required")
|
||||||
.map((x) => [x[0], []]) || [],
|
.map((x) => [x[0], []]) || [],
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -728,10 +797,7 @@ export class SystemForEmbassy implements System {
|
|||||||
? storedDependsOn
|
? storedDependsOn
|
||||||
: requiredDeps
|
: requiredDeps
|
||||||
|
|
||||||
await effects.store.set({
|
await dependsOnFile.write(effects, dependsOn)
|
||||||
path: EMBASSY_DEPENDS_ON_PATH_PREFIX,
|
|
||||||
value: dependsOn,
|
|
||||||
})
|
|
||||||
|
|
||||||
await effects.setDependencies({
|
await effects.setDependencies({
|
||||||
dependencies: Object.entries(dependsOn).flatMap(
|
dependencies: Object.entries(dependsOn).flatMap(
|
||||||
@@ -755,31 +821,33 @@ export class SystemForEmbassy implements System {
|
|||||||
|
|
||||||
async migration(
|
async migration(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
version: { from: string } | { to: string },
|
version:
|
||||||
|
| { from: VersionRange | ExtendedVersion }
|
||||||
|
| { to: VersionRange | ExtendedVersion },
|
||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
): Promise<{ configured: boolean }> {
|
): Promise<{ configured: boolean } | null> {
|
||||||
let migration
|
let migration
|
||||||
let args: [string, ...string[]]
|
let args: [string, ...string[]]
|
||||||
if ("from" in version) {
|
if ("from" in version) {
|
||||||
args = [version.from, "from"]
|
if (overlaps(this.version, version.from)) return null
|
||||||
const fromExver = ExtendedVersion.parse(version.from)
|
args = [version.from.toString(), "from"]
|
||||||
if (!this.manifest.migrations) return { configured: true }
|
if (!this.manifest.migrations) return { configured: true }
|
||||||
migration = Object.entries(this.manifest.migrations.from)
|
migration = Object.entries(this.manifest.migrations.from)
|
||||||
.map(
|
.map(
|
||||||
([version, procedure]) =>
|
([version, procedure]) =>
|
||||||
[VersionRange.parseEmver(version), procedure] as const,
|
[VersionRange.parseEmver(version), procedure] as const,
|
||||||
)
|
)
|
||||||
.find(([versionEmver, _]) => versionEmver.satisfiedBy(fromExver))
|
.find(([versionEmver, _]) => overlaps(versionEmver, version.from))
|
||||||
} else {
|
} else {
|
||||||
args = [version.to, "to"]
|
if (overlaps(this.version, version.to)) return null
|
||||||
const toExver = ExtendedVersion.parse(version.to)
|
args = [version.to.toString(), "to"]
|
||||||
if (!this.manifest.migrations) return { configured: true }
|
if (!this.manifest.migrations) return { configured: true }
|
||||||
migration = Object.entries(this.manifest.migrations.to)
|
migration = Object.entries(this.manifest.migrations.to)
|
||||||
.map(
|
.map(
|
||||||
([version, procedure]) =>
|
([version, procedure]) =>
|
||||||
[VersionRange.parseEmver(version), procedure] as const,
|
[VersionRange.parseEmver(version), procedure] as const,
|
||||||
)
|
)
|
||||||
.find(([versionEmver, _]) => versionEmver.satisfiedBy(toExver))
|
.find(([versionEmver, _]) => overlaps(versionEmver, version.to))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (migration) {
|
if (migration) {
|
||||||
@@ -815,13 +883,12 @@ export class SystemForEmbassy implements System {
|
|||||||
})) as any
|
})) as any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { configured: true }
|
return null
|
||||||
}
|
}
|
||||||
async properties(
|
async properties(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
): Promise<PropertiesReturn> {
|
): Promise<PropertiesReturn> {
|
||||||
// TODO BLU-J set the properties ever so often
|
|
||||||
const setConfigValue = this.manifest.properties
|
const setConfigValue = this.manifest.properties
|
||||||
if (!setConfigValue) throw new Error("There is no properties")
|
if (!setConfigValue) throw new Error("There is no properties")
|
||||||
if (setConfigValue.type === "docker") {
|
if (setConfigValue.type === "docker") {
|
||||||
@@ -969,43 +1036,52 @@ export class SystemForEmbassy implements System {
|
|||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// TODO: docker
|
// TODO: docker
|
||||||
const oldConfig = (await effects.store.get({
|
await effects.mount({
|
||||||
packageId: id,
|
location: `/media/embassy/${id}`,
|
||||||
path: EMBASSY_POINTER_PATH_PREFIX,
|
target: {
|
||||||
callback: () => {
|
|
||||||
this.dependenciesAutoconfig(effects, id, timeoutMs)
|
|
||||||
},
|
|
||||||
})) as U.Config
|
|
||||||
if (!oldConfig) return
|
|
||||||
const moduleCode = await this.moduleCode
|
|
||||||
const method = moduleCode?.dependencies?.[id]?.autoConfigure
|
|
||||||
if (!method) return
|
|
||||||
const newConfig = (await method(
|
|
||||||
polyfillEffects(effects, this.manifest),
|
|
||||||
JSON.parse(JSON.stringify(oldConfig)),
|
|
||||||
).then((x) => {
|
|
||||||
if ("result" in x) return x.result
|
|
||||||
if ("error" in x) throw new Error("Error getting config: " + x.error)
|
|
||||||
throw new Error("Error getting config: " + x["error-code"][1])
|
|
||||||
})) as any
|
|
||||||
const diff = partialDiff(oldConfig, newConfig)
|
|
||||||
if (diff) {
|
|
||||||
await effects.action.request({
|
|
||||||
actionId: "config",
|
|
||||||
packageId: id,
|
packageId: id,
|
||||||
replayId: `${id}/config`,
|
volumeId: "embassy",
|
||||||
severity: "important",
|
subpath: null,
|
||||||
reason: `Configure this dependency for the needs of ${this.manifest.title}`,
|
readonly: true,
|
||||||
input: {
|
idmap: [],
|
||||||
kind: "partial",
|
},
|
||||||
value: diff.diff,
|
})
|
||||||
},
|
configFile
|
||||||
when: {
|
.withPath(`/media/embassy/${id}/config.json`)
|
||||||
condition: "input-not-matches",
|
.read()
|
||||||
once: false,
|
.onChange(effects, async (oldConfig: U.Config) => {
|
||||||
},
|
if (!oldConfig) return { cancel: false }
|
||||||
|
const moduleCode = await this.moduleCode
|
||||||
|
const method = moduleCode?.dependencies?.[id]?.autoConfigure
|
||||||
|
if (!method) return { cancel: true }
|
||||||
|
const newConfig = (await method(
|
||||||
|
polyfillEffects(effects, this.manifest),
|
||||||
|
JSON.parse(JSON.stringify(oldConfig)),
|
||||||
|
).then((x) => {
|
||||||
|
if ("result" in x) return x.result
|
||||||
|
if ("error" in x) throw new Error("Error getting config: " + x.error)
|
||||||
|
throw new Error("Error getting config: " + x["error-code"][1])
|
||||||
|
})) as any
|
||||||
|
const diff = partialDiff(oldConfig, newConfig)
|
||||||
|
if (diff) {
|
||||||
|
await effects.action.createTask({
|
||||||
|
actionId: "config",
|
||||||
|
packageId: id,
|
||||||
|
replayId: `${id}/config`,
|
||||||
|
severity: "important",
|
||||||
|
reason: `Configure this dependency for the needs of ${this.manifest.title}`,
|
||||||
|
input: {
|
||||||
|
kind: "partial",
|
||||||
|
value: diff.diff,
|
||||||
|
},
|
||||||
|
when: {
|
||||||
|
condition: "input-not-matches",
|
||||||
|
once: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return { cancel: false }
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1107,11 +1183,21 @@ async function updateConfig(
|
|||||||
) {
|
) {
|
||||||
if (specValue.target === "config") {
|
if (specValue.target === "config") {
|
||||||
const jp = require("jsonpath")
|
const jp = require("jsonpath")
|
||||||
const remoteConfig = await effects.store.get({
|
const depId = specValue["package-id"]
|
||||||
packageId: specValue["package-id"],
|
await effects.mount({
|
||||||
callback: () => effects.restart(),
|
location: `/media/embassy/${depId}`,
|
||||||
path: EMBASSY_POINTER_PATH_PREFIX,
|
target: {
|
||||||
|
packageId: depId,
|
||||||
|
volumeId: "embassy",
|
||||||
|
subpath: null,
|
||||||
|
readonly: true,
|
||||||
|
idmap: [],
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
const remoteConfig = configFile
|
||||||
|
.withPath(`/media/embassy/${depId}/config.json`)
|
||||||
|
.read()
|
||||||
|
.once()
|
||||||
console.debug(remoteConfig)
|
console.debug(remoteConfig)
|
||||||
const configValue = specValue.multi
|
const configValue = specValue.multi
|
||||||
? jp.query(remoteConfig, specValue.selector)
|
? jp.query(remoteConfig, specValue.selector)
|
||||||
@@ -1152,14 +1238,14 @@ async function updateConfig(
|
|||||||
const url: string =
|
const url: string =
|
||||||
filled === null || filled.addressInfo === null
|
filled === null || filled.addressInfo === null
|
||||||
? ""
|
? ""
|
||||||
: catchFn(() =>
|
: catchFn(
|
||||||
utils.hostnameInfoToAddress(
|
() =>
|
||||||
specValue.target === "lan-address"
|
(specValue.target === "lan-address"
|
||||||
? filled.addressInfo!.localHostnames[0] ||
|
? filled.addressInfo!.filter({ kind: "mdns" }) ||
|
||||||
filled.addressInfo!.onionHostnames[0]
|
filled.addressInfo!.onion
|
||||||
: filled.addressInfo!.onionHostnames[0] ||
|
: filled.addressInfo!.onion ||
|
||||||
filled.addressInfo!.localHostnames[0],
|
filled.addressInfo!.filter({ kind: "mdns" })
|
||||||
),
|
).hostnames[0].hostname.value,
|
||||||
) || ""
|
) || ""
|
||||||
mutConfigValue[key] = url
|
mutConfigValue[key] = url
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,123 +14,113 @@ import {
|
|||||||
import { matchVolume } from "./matchVolume"
|
import { matchVolume } from "./matchVolume"
|
||||||
import { matchDockerProcedure } from "../../../Models/DockerProcedure"
|
import { matchDockerProcedure } from "../../../Models/DockerProcedure"
|
||||||
|
|
||||||
const matchJsProcedure = object(
|
const matchJsProcedure = object({
|
||||||
{
|
type: literal("script"),
|
||||||
type: literal("script"),
|
args: array(unknown).nullable().optional().defaultTo([]),
|
||||||
args: array(unknown),
|
})
|
||||||
},
|
|
||||||
["args"],
|
|
||||||
{
|
|
||||||
args: [],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const matchProcedure = some(matchDockerProcedure, matchJsProcedure)
|
const matchProcedure = some(matchDockerProcedure, matchJsProcedure)
|
||||||
export type Procedure = typeof matchProcedure._TYPE
|
export type Procedure = typeof matchProcedure._TYPE
|
||||||
|
|
||||||
const matchAction = object(
|
const matchAction = object({
|
||||||
{
|
name: string,
|
||||||
name: string,
|
description: string,
|
||||||
description: string,
|
warning: string.nullable().optional(),
|
||||||
warning: string,
|
implementation: matchProcedure,
|
||||||
implementation: matchProcedure,
|
"allowed-statuses": array(literals("running", "stopped")),
|
||||||
"allowed-statuses": array(literals("running", "stopped")),
|
"input-spec": unknown.nullable().optional(),
|
||||||
"input-spec": unknown,
|
})
|
||||||
},
|
export const matchManifest = object({
|
||||||
["warning", "input-spec", "input-spec"],
|
id: string,
|
||||||
)
|
title: string,
|
||||||
export const matchManifest = object(
|
version: string,
|
||||||
{
|
main: matchDockerProcedure,
|
||||||
id: string,
|
assets: object({
|
||||||
title: string,
|
assets: string.nullable().optional(),
|
||||||
version: string,
|
scripts: string.nullable().optional(),
|
||||||
main: matchDockerProcedure,
|
})
|
||||||
assets: object(
|
.nullable()
|
||||||
{
|
.optional(),
|
||||||
assets: string,
|
"health-checks": dictionary([
|
||||||
scripts: string,
|
string,
|
||||||
},
|
every(
|
||||||
["assets", "scripts"],
|
matchProcedure,
|
||||||
|
object({
|
||||||
|
name: string,
|
||||||
|
["success-message"]: string.nullable().optional(),
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
"health-checks": dictionary([
|
]),
|
||||||
string,
|
config: object({
|
||||||
every(
|
get: matchProcedure,
|
||||||
matchProcedure,
|
set: matchProcedure,
|
||||||
object(
|
})
|
||||||
{
|
.nullable()
|
||||||
name: string,
|
.optional(),
|
||||||
["success-message"]: string,
|
properties: matchProcedure.nullable().optional(),
|
||||||
},
|
volumes: dictionary([string, matchVolume]),
|
||||||
["success-message"],
|
interfaces: dictionary([
|
||||||
),
|
string,
|
||||||
),
|
object({
|
||||||
]),
|
name: string,
|
||||||
config: object({
|
description: string,
|
||||||
get: matchProcedure,
|
"tor-config": object({
|
||||||
set: matchProcedure,
|
"port-mapping": dictionary([string, string]),
|
||||||
|
})
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
|
"lan-config": dictionary([
|
||||||
|
string,
|
||||||
|
object({
|
||||||
|
ssl: boolean,
|
||||||
|
internal: number,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
|
ui: boolean,
|
||||||
|
protocols: array(string),
|
||||||
}),
|
}),
|
||||||
properties: matchProcedure,
|
]),
|
||||||
volumes: dictionary([string, matchVolume]),
|
backup: object({
|
||||||
interfaces: dictionary([
|
create: matchProcedure,
|
||||||
string,
|
restore: matchProcedure,
|
||||||
object(
|
}),
|
||||||
{
|
migrations: object({
|
||||||
name: string,
|
to: dictionary([string, matchProcedure]),
|
||||||
description: string,
|
from: dictionary([string, matchProcedure]),
|
||||||
"tor-config": object({
|
})
|
||||||
"port-mapping": dictionary([string, string]),
|
.nullable()
|
||||||
}),
|
.optional(),
|
||||||
"lan-config": dictionary([
|
dependencies: dictionary([
|
||||||
string,
|
string,
|
||||||
object({
|
object({
|
||||||
ssl: boolean,
|
version: string,
|
||||||
internal: number,
|
requirement: some(
|
||||||
}),
|
object({
|
||||||
]),
|
type: literal("opt-in"),
|
||||||
ui: boolean,
|
how: string,
|
||||||
protocols: array(string),
|
}),
|
||||||
},
|
object({
|
||||||
["lan-config", "tor-config"],
|
type: literal("opt-out"),
|
||||||
|
how: string,
|
||||||
|
}),
|
||||||
|
object({
|
||||||
|
type: literal("required"),
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
]),
|
description: string.nullable().optional(),
|
||||||
backup: object({
|
config: object({
|
||||||
create: matchProcedure,
|
check: matchProcedure,
|
||||||
restore: matchProcedure,
|
"auto-configure": matchProcedure,
|
||||||
}),
|
})
|
||||||
migrations: object({
|
.nullable()
|
||||||
to: dictionary([string, matchProcedure]),
|
.optional(),
|
||||||
from: dictionary([string, matchProcedure]),
|
})
|
||||||
}),
|
.nullable()
|
||||||
dependencies: dictionary([
|
.optional(),
|
||||||
string,
|
]),
|
||||||
object(
|
|
||||||
{
|
|
||||||
version: string,
|
|
||||||
requirement: some(
|
|
||||||
object({
|
|
||||||
type: literal("opt-in"),
|
|
||||||
how: string,
|
|
||||||
}),
|
|
||||||
object({
|
|
||||||
type: literal("opt-out"),
|
|
||||||
how: string,
|
|
||||||
}),
|
|
||||||
object({
|
|
||||||
type: literal("required"),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
description: string,
|
|
||||||
config: object({
|
|
||||||
check: matchProcedure,
|
|
||||||
"auto-configure": matchProcedure,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
["description", "config"],
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
|
|
||||||
actions: dictionary([string, matchAction]),
|
actions: dictionary([string, matchAction]),
|
||||||
},
|
})
|
||||||
["config", "actions", "properties", "migrations", "dependencies"],
|
|
||||||
)
|
|
||||||
export type Manifest = typeof matchManifest._TYPE
|
export type Manifest = typeof matchManifest._TYPE
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { object, literal, string, boolean, some } from "ts-matches"
|
import { object, literal, string, boolean, some } from "ts-matches"
|
||||||
|
|
||||||
const matchDataVolume = object(
|
const matchDataVolume = object({
|
||||||
{
|
type: literal("data"),
|
||||||
type: literal("data"),
|
readonly: boolean.optional(),
|
||||||
readonly: boolean,
|
})
|
||||||
},
|
|
||||||
["readonly"],
|
|
||||||
)
|
|
||||||
const matchAssetVolume = object({
|
const matchAssetVolume = object({
|
||||||
type: literal("assets"),
|
type: literal("assets"),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -135,12 +135,9 @@ export const polyfillEffects = (
|
|||||||
[input.command, ...(input.args || [])].join(" "),
|
[input.command, ...(input.args || [])].join(" "),
|
||||||
)
|
)
|
||||||
const daemon = promiseSubcontainer.then((subcontainer) =>
|
const daemon = promiseSubcontainer.then((subcontainer) =>
|
||||||
daemons.runCommand()(
|
daemons.runCommand()(effects, subcontainer, {
|
||||||
effects,
|
command: [input.command, ...(input.args || [])],
|
||||||
subcontainer,
|
}),
|
||||||
[input.command, ...(input.args || [])],
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
wait: () =>
|
wait: () =>
|
||||||
@@ -169,12 +166,12 @@ export const polyfillEffects = (
|
|||||||
{ imageId: manifest.main.image },
|
{ imageId: manifest.main.image },
|
||||||
commands,
|
commands,
|
||||||
{
|
{
|
||||||
mounts: Mounts.of().addVolume(
|
mounts: Mounts.of().mountVolume({
|
||||||
input.volumeId,
|
volumeId: input.volumeId,
|
||||||
null,
|
subpath: null,
|
||||||
"/drive",
|
mountpoint: "/drive",
|
||||||
false,
|
readonly: false,
|
||||||
),
|
}),
|
||||||
},
|
},
|
||||||
commands.join(" "),
|
commands.join(" "),
|
||||||
)
|
)
|
||||||
@@ -206,12 +203,12 @@ export const polyfillEffects = (
|
|||||||
{ imageId: manifest.main.image },
|
{ imageId: manifest.main.image },
|
||||||
commands,
|
commands,
|
||||||
{
|
{
|
||||||
mounts: Mounts.of().addVolume(
|
mounts: Mounts.of().mountVolume({
|
||||||
input.volumeId,
|
volumeId: input.volumeId,
|
||||||
null,
|
subpath: null,
|
||||||
"/drive",
|
mountpoint: "/drive",
|
||||||
false,
|
readonly: false,
|
||||||
),
|
}),
|
||||||
},
|
},
|
||||||
commands.join(" "),
|
commands.join(" "),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { matchOldConfigSpec, transformConfigSpec } from "./transformConfigSpec"
|
import {
|
||||||
import fixtureEmbasyPagesConfig from "./__fixtures__/embasyPagesConfig"
|
matchOldConfigSpec,
|
||||||
|
matchOldValueSpecList,
|
||||||
|
transformConfigSpec,
|
||||||
|
} from "./transformConfigSpec"
|
||||||
|
import fixtureEmbassyPagesConfig from "./__fixtures__/embassyPagesConfig"
|
||||||
|
import fixtureRTLConfig from "./__fixtures__/rtlConfig"
|
||||||
import searNXG from "./__fixtures__/searNXG"
|
import searNXG from "./__fixtures__/searNXG"
|
||||||
import bitcoind from "./__fixtures__/bitcoind"
|
import bitcoind from "./__fixtures__/bitcoind"
|
||||||
import nostr from "./__fixtures__/nostr"
|
import nostr from "./__fixtures__/nostr"
|
||||||
@@ -8,14 +13,25 @@ import nostrConfig2 from "./__fixtures__/nostrConfig2"
|
|||||||
describe("transformConfigSpec", () => {
|
describe("transformConfigSpec", () => {
|
||||||
test("matchOldConfigSpec(embassyPages.homepage.variants[web-page])", () => {
|
test("matchOldConfigSpec(embassyPages.homepage.variants[web-page])", () => {
|
||||||
matchOldConfigSpec.unsafeCast(
|
matchOldConfigSpec.unsafeCast(
|
||||||
fixtureEmbasyPagesConfig.homepage.variants["web-page"],
|
fixtureEmbassyPagesConfig.homepage.variants["web-page"],
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
test("matchOldConfigSpec(embassyPages)", () => {
|
test("matchOldConfigSpec(embassyPages)", () => {
|
||||||
matchOldConfigSpec.unsafeCast(fixtureEmbasyPagesConfig)
|
matchOldConfigSpec.unsafeCast(fixtureEmbassyPagesConfig)
|
||||||
})
|
})
|
||||||
test("transformConfigSpec(embassyPages)", () => {
|
test("transformConfigSpec(embassyPages)", () => {
|
||||||
const spec = matchOldConfigSpec.unsafeCast(fixtureEmbasyPagesConfig)
|
const spec = matchOldConfigSpec.unsafeCast(fixtureEmbassyPagesConfig)
|
||||||
|
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("matchOldConfigSpec(RTL.nodes)", () => {
|
||||||
|
matchOldValueSpecList.unsafeCast(fixtureRTLConfig.nodes)
|
||||||
|
})
|
||||||
|
test("matchOldConfigSpec(RTL)", () => {
|
||||||
|
matchOldConfigSpec.unsafeCast(fixtureRTLConfig)
|
||||||
|
})
|
||||||
|
test("transformConfigSpec(RTL)", () => {
|
||||||
|
const spec = matchOldConfigSpec.unsafeCast(fixtureRTLConfig)
|
||||||
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): IST.InputSpec {
|
|||||||
immutable: false,
|
immutable: false,
|
||||||
}
|
}
|
||||||
} else if (oldVal.type === "list") {
|
} else if (oldVal.type === "list") {
|
||||||
|
if (isUnionList(oldVal)) return inputSpec
|
||||||
newVal = getListSpec(oldVal)
|
newVal = getListSpec(oldVal)
|
||||||
} else if (oldVal.type === "number") {
|
} else if (oldVal.type === "number") {
|
||||||
const range = Range.from(oldVal.range)
|
const range = Range.from(oldVal.range)
|
||||||
@@ -177,15 +178,17 @@ export function transformOldConfigToNew(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isList(val) && isObjectList(val)) {
|
if (isList(val)) {
|
||||||
if (!config[key]) return obj
|
if (!config[key]) return obj
|
||||||
|
|
||||||
newVal = (config[key] as object[]).map((obj) =>
|
if (isObjectList(val)) {
|
||||||
transformOldConfigToNew(
|
newVal = (config[key] as object[]).map((obj) =>
|
||||||
matchOldConfigSpec.unsafeCast(val.spec.spec),
|
transformOldConfigToNew(
|
||||||
obj,
|
matchOldConfigSpec.unsafeCast(val.spec.spec),
|
||||||
),
|
obj,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
} else if (isUnionList(val)) return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPointer(val)) {
|
if (isPointer(val)) {
|
||||||
@@ -203,6 +206,7 @@ export function transformNewConfigToOld(
|
|||||||
spec: OldConfigSpec,
|
spec: OldConfigSpec,
|
||||||
config: Record<string, any>,
|
config: Record<string, any>,
|
||||||
): Record<string, any> {
|
): Record<string, any> {
|
||||||
|
if (!config) return config
|
||||||
return Object.entries(spec).reduce((obj, [key, val]) => {
|
return Object.entries(spec).reduce((obj, [key, val]) => {
|
||||||
let newVal = config[key]
|
let newVal = config[key]
|
||||||
|
|
||||||
@@ -223,13 +227,15 @@ export function transformNewConfigToOld(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isList(val) && isObjectList(val)) {
|
if (isList(val)) {
|
||||||
newVal = (config[key] as object[]).map((obj) =>
|
if (isObjectList(val)) {
|
||||||
transformNewConfigToOld(
|
newVal = (config[key] as object[]).map((obj) =>
|
||||||
matchOldConfigSpec.unsafeCast(val.spec.spec),
|
transformNewConfigToOld(
|
||||||
obj,
|
matchOldConfigSpec.unsafeCast(val.spec.spec),
|
||||||
),
|
obj,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
} else if (isUnionList(val)) return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -375,15 +381,17 @@ function isNumberList(
|
|||||||
): val is OldValueSpecList & { subtype: "number" } {
|
): val is OldValueSpecList & { subtype: "number" } {
|
||||||
return val.subtype === "number"
|
return val.subtype === "number"
|
||||||
}
|
}
|
||||||
|
|
||||||
function isObjectList(
|
function isObjectList(
|
||||||
val: OldValueSpecList,
|
val: OldValueSpecList,
|
||||||
): val is OldValueSpecList & { subtype: "object" } {
|
): val is OldValueSpecList & { subtype: "object" } {
|
||||||
if (["union"].includes(val.subtype)) {
|
|
||||||
throw new Error("Invalid list subtype. enum, string, and object permitted.")
|
|
||||||
}
|
|
||||||
return val.subtype === "object"
|
return val.subtype === "object"
|
||||||
}
|
}
|
||||||
|
function isUnionList(
|
||||||
|
val: OldValueSpecList,
|
||||||
|
): val is OldValueSpecList & { subtype: "union" } {
|
||||||
|
return val.subtype === "union"
|
||||||
|
}
|
||||||
|
|
||||||
export type OldConfigSpec = Record<string, OldValueSpec>
|
export type OldConfigSpec = Record<string, OldValueSpec>
|
||||||
const [_matchOldConfigSpec, setMatchOldConfigSpec] = deferred<unknown>()
|
const [_matchOldConfigSpec, setMatchOldConfigSpec] = deferred<unknown>()
|
||||||
export const matchOldConfigSpec = _matchOldConfigSpec as Parser<
|
export const matchOldConfigSpec = _matchOldConfigSpec as Parser<
|
||||||
@@ -396,100 +404,71 @@ export const matchOldDefaultString = anyOf(
|
|||||||
)
|
)
|
||||||
type OldDefaultString = typeof matchOldDefaultString._TYPE
|
type OldDefaultString = typeof matchOldDefaultString._TYPE
|
||||||
|
|
||||||
export const matchOldValueSpecString = object(
|
export const matchOldValueSpecString = object({
|
||||||
{
|
type: literals("string"),
|
||||||
type: literals("string"),
|
name: string,
|
||||||
name: string,
|
masked: boolean.nullable().optional(),
|
||||||
masked: boolean,
|
copyable: boolean.nullable().optional(),
|
||||||
copyable: boolean,
|
nullable: boolean.nullable().optional(),
|
||||||
nullable: boolean,
|
placeholder: string.nullable().optional(),
|
||||||
placeholder: string,
|
pattern: string.nullable().optional(),
|
||||||
pattern: string,
|
"pattern-description": string.nullable().optional(),
|
||||||
"pattern-description": string,
|
default: matchOldDefaultString.nullable().optional(),
|
||||||
default: matchOldDefaultString,
|
textarea: boolean.nullable().optional(),
|
||||||
textarea: boolean,
|
description: string.nullable().optional(),
|
||||||
description: string,
|
warning: string.nullable().optional(),
|
||||||
warning: string,
|
})
|
||||||
},
|
|
||||||
[
|
|
||||||
"masked",
|
|
||||||
"copyable",
|
|
||||||
"nullable",
|
|
||||||
"placeholder",
|
|
||||||
"pattern",
|
|
||||||
"pattern-description",
|
|
||||||
"default",
|
|
||||||
"textarea",
|
|
||||||
"description",
|
|
||||||
"warning",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
export const matchOldValueSpecNumber = object(
|
export const matchOldValueSpecNumber = object({
|
||||||
{
|
type: literals("number"),
|
||||||
type: literals("number"),
|
nullable: boolean,
|
||||||
nullable: boolean,
|
name: string,
|
||||||
name: string,
|
range: string,
|
||||||
range: string,
|
integral: boolean,
|
||||||
integral: boolean,
|
default: number.nullable().optional(),
|
||||||
default: number,
|
description: string.nullable().optional(),
|
||||||
description: string,
|
warning: string.nullable().optional(),
|
||||||
warning: string,
|
units: string.nullable().optional(),
|
||||||
units: string,
|
placeholder: anyOf(number, string).nullable().optional(),
|
||||||
placeholder: anyOf(number, string),
|
})
|
||||||
},
|
|
||||||
["default", "description", "warning", "units", "placeholder"],
|
|
||||||
)
|
|
||||||
type OldValueSpecNumber = typeof matchOldValueSpecNumber._TYPE
|
type OldValueSpecNumber = typeof matchOldValueSpecNumber._TYPE
|
||||||
|
|
||||||
export const matchOldValueSpecBoolean = object(
|
export const matchOldValueSpecBoolean = object({
|
||||||
{
|
type: literals("boolean"),
|
||||||
type: literals("boolean"),
|
default: boolean,
|
||||||
default: boolean,
|
name: string,
|
||||||
name: string,
|
description: string.nullable().optional(),
|
||||||
description: string,
|
warning: string.nullable().optional(),
|
||||||
warning: string,
|
})
|
||||||
},
|
|
||||||
["description", "warning"],
|
|
||||||
)
|
|
||||||
type OldValueSpecBoolean = typeof matchOldValueSpecBoolean._TYPE
|
type OldValueSpecBoolean = typeof matchOldValueSpecBoolean._TYPE
|
||||||
|
|
||||||
const matchOldValueSpecObject = object(
|
const matchOldValueSpecObject = object({
|
||||||
{
|
type: literals("object"),
|
||||||
type: literals("object"),
|
spec: _matchOldConfigSpec,
|
||||||
spec: _matchOldConfigSpec,
|
name: string,
|
||||||
name: string,
|
description: string.nullable().optional(),
|
||||||
description: string,
|
warning: string.nullable().optional(),
|
||||||
warning: string,
|
})
|
||||||
},
|
|
||||||
["description", "warning"],
|
|
||||||
)
|
|
||||||
type OldValueSpecObject = typeof matchOldValueSpecObject._TYPE
|
type OldValueSpecObject = typeof matchOldValueSpecObject._TYPE
|
||||||
|
|
||||||
const matchOldValueSpecEnum = object(
|
const matchOldValueSpecEnum = object({
|
||||||
{
|
values: array(string),
|
||||||
values: array(string),
|
"value-names": dictionary([string, string]),
|
||||||
"value-names": dictionary([string, string]),
|
type: literals("enum"),
|
||||||
type: literals("enum"),
|
default: string,
|
||||||
default: string,
|
name: string,
|
||||||
name: string,
|
description: string.nullable().optional(),
|
||||||
description: string,
|
warning: string.nullable().optional(),
|
||||||
warning: string,
|
})
|
||||||
},
|
|
||||||
["description", "warning"],
|
|
||||||
)
|
|
||||||
type OldValueSpecEnum = typeof matchOldValueSpecEnum._TYPE
|
type OldValueSpecEnum = typeof matchOldValueSpecEnum._TYPE
|
||||||
|
|
||||||
const matchOldUnionTagSpec = object(
|
const matchOldUnionTagSpec = object({
|
||||||
{
|
id: string, // The name of the field containing one of the union variants
|
||||||
id: string, // The name of the field containing one of the union variants
|
"variant-names": dictionary([string, string]), // The name of each variant
|
||||||
"variant-names": dictionary([string, string]), // The name of each variant
|
name: string,
|
||||||
name: string,
|
description: string.nullable().optional(),
|
||||||
description: string,
|
warning: string.nullable().optional(),
|
||||||
warning: string,
|
})
|
||||||
},
|
|
||||||
["description", "warning"],
|
|
||||||
)
|
|
||||||
const matchOldValueSpecUnion = object({
|
const matchOldValueSpecUnion = object({
|
||||||
type: literals("union"),
|
type: literals("union"),
|
||||||
tag: matchOldUnionTagSpec,
|
tag: matchOldUnionTagSpec,
|
||||||
@@ -514,57 +493,51 @@ setOldUniqueBy(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const matchOldListValueSpecObject = object(
|
const matchOldListValueSpecObject = object({
|
||||||
{
|
spec: _matchOldConfigSpec, // this is a mapped type of the config object at this level, replacing the object's values with specs on those values
|
||||||
spec: _matchOldConfigSpec, // this is a mapped type of the config object at this level, replacing the object's values with specs on those values
|
"unique-by": matchOldUniqueBy.nullable().optional(), // indicates whether duplicates can be permitted in the list
|
||||||
"unique-by": matchOldUniqueBy, // indicates whether duplicates can be permitted in the list
|
"display-as": string.nullable().optional(), // this should be a handlebars template which can make use of the entire config which corresponds to 'spec'
|
||||||
"display-as": string, // this should be a handlebars template which can make use of the entire config which corresponds to 'spec'
|
})
|
||||||
},
|
const matchOldListValueSpecUnion = object({
|
||||||
["display-as", "unique-by"],
|
"unique-by": matchOldUniqueBy.nullable().optional(),
|
||||||
)
|
"display-as": string.nullable().optional(),
|
||||||
const matchOldListValueSpecString = object(
|
tag: matchOldUnionTagSpec,
|
||||||
{
|
variants: dictionary([string, _matchOldConfigSpec]),
|
||||||
masked: boolean,
|
})
|
||||||
copyable: boolean,
|
const matchOldListValueSpecString = object({
|
||||||
pattern: string,
|
masked: boolean.nullable().optional(),
|
||||||
"pattern-description": string,
|
copyable: boolean.nullable().optional(),
|
||||||
placeholder: string,
|
pattern: string.nullable().optional(),
|
||||||
},
|
"pattern-description": string.nullable().optional(),
|
||||||
["pattern", "pattern-description", "placeholder", "copyable", "masked"],
|
placeholder: string.nullable().optional(),
|
||||||
)
|
})
|
||||||
|
|
||||||
const matchOldListValueSpecEnum = object({
|
const matchOldListValueSpecEnum = object({
|
||||||
values: array(string),
|
values: array(string),
|
||||||
"value-names": dictionary([string, string]),
|
"value-names": dictionary([string, string]),
|
||||||
})
|
})
|
||||||
const matchOldListValueSpecNumber = object(
|
const matchOldListValueSpecNumber = object({
|
||||||
{
|
range: string,
|
||||||
range: string,
|
integral: boolean,
|
||||||
integral: boolean,
|
units: string.nullable().optional(),
|
||||||
units: string,
|
placeholder: anyOf(number, string).nullable().optional(),
|
||||||
placeholder: anyOf(number, string),
|
})
|
||||||
},
|
|
||||||
["units", "placeholder"],
|
|
||||||
)
|
|
||||||
|
|
||||||
// represents a spec for a list
|
// represents a spec for a list
|
||||||
const matchOldValueSpecList = every(
|
export const matchOldValueSpecList = every(
|
||||||
object(
|
object({
|
||||||
{
|
type: literals("list"),
|
||||||
type: literals("list"),
|
range: string, // '[0,1]' (inclusive) OR '[0,*)' (right unbounded), normal math rules
|
||||||
range: string, // '[0,1]' (inclusive) OR '[0,*)' (right unbounded), normal math rules
|
default: anyOf(
|
||||||
default: anyOf(
|
array(string),
|
||||||
array(string),
|
array(number),
|
||||||
array(number),
|
array(matchOldDefaultString),
|
||||||
array(matchOldDefaultString),
|
array(object),
|
||||||
array(object),
|
),
|
||||||
),
|
name: string,
|
||||||
name: string,
|
description: string.nullable().optional(),
|
||||||
description: string,
|
warning: string.nullable().optional(),
|
||||||
warning: string,
|
}),
|
||||||
},
|
|
||||||
["description", "warning"],
|
|
||||||
),
|
|
||||||
anyOf(
|
anyOf(
|
||||||
object({
|
object({
|
||||||
subtype: literals("string"),
|
subtype: literals("string"),
|
||||||
@@ -582,6 +555,10 @@ const matchOldValueSpecList = every(
|
|||||||
subtype: literals("number"),
|
subtype: literals("number"),
|
||||||
spec: matchOldListValueSpecNumber,
|
spec: matchOldListValueSpecNumber,
|
||||||
}),
|
}),
|
||||||
|
object({
|
||||||
|
subtype: literals("union"),
|
||||||
|
spec: matchOldListValueSpecUnion,
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
type OldValueSpecList = typeof matchOldValueSpecList._TYPE
|
type OldValueSpecList = typeof matchOldValueSpecList._TYPE
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { System } from "../../Interfaces/System"
|
import { System } from "../../Interfaces/System"
|
||||||
import { Effects } from "../../Models/Effects"
|
import { Effects } from "../../Models/Effects"
|
||||||
import { T, utils } from "@start9labs/start-sdk"
|
import { ExtendedVersion, T, utils, VersionRange } from "@start9labs/start-sdk"
|
||||||
import { Optional } from "ts-matches/lib/parsers/interfaces"
|
|
||||||
|
|
||||||
export const STARTOS_JS_LOCATION = "/usr/lib/startos/package/index.js"
|
export const STARTOS_JS_LOCATION = "/usr/lib/startos/package/index.js"
|
||||||
|
|
||||||
@@ -11,6 +10,7 @@ type RunningMain = {
|
|||||||
|
|
||||||
export class SystemForStartOs implements System {
|
export class SystemForStartOs implements System {
|
||||||
private runningMain: RunningMain | undefined
|
private runningMain: RunningMain | undefined
|
||||||
|
private starting: boolean = false
|
||||||
|
|
||||||
static of() {
|
static of() {
|
||||||
return new SystemForStartOs(require(STARTOS_JS_LOCATION))
|
return new SystemForStartOs(require(STARTOS_JS_LOCATION))
|
||||||
@@ -19,22 +19,23 @@ export class SystemForStartOs implements System {
|
|||||||
constructor(readonly abi: T.ABI) {
|
constructor(readonly abi: T.ABI) {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
async containerInit(effects: Effects): Promise<void> {
|
|
||||||
return void (await this.abi.containerInit({ effects }))
|
async init(
|
||||||
}
|
|
||||||
async packageInit(
|
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
|
kind: "install" | "update" | "restore" | null,
|
||||||
|
): Promise<void> {
|
||||||
|
return void (await this.abi.init({ effects, kind }))
|
||||||
|
}
|
||||||
|
|
||||||
|
async exit(
|
||||||
|
effects: Effects,
|
||||||
|
target: ExtendedVersion | VersionRange | null,
|
||||||
timeoutMs: number | null = null,
|
timeoutMs: number | null = null,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return void (await this.abi.packageInit({ effects }))
|
await this.stop()
|
||||||
}
|
return void (await this.abi.uninit({ effects, target }))
|
||||||
async packageUninit(
|
|
||||||
effects: Effects,
|
|
||||||
nextVersion: Optional<string> = null,
|
|
||||||
timeoutMs: number | null = null,
|
|
||||||
): Promise<void> {
|
|
||||||
return void (await this.abi.packageUninit({ effects, nextVersion }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async createBackup(
|
async createBackup(
|
||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
@@ -43,14 +44,6 @@ export class SystemForStartOs implements System {
|
|||||||
effects,
|
effects,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
async restoreBackup(
|
|
||||||
effects: T.Effects,
|
|
||||||
timeoutMs: number | null,
|
|
||||||
): Promise<void> {
|
|
||||||
return void (await this.abi.restoreBackup({
|
|
||||||
effects,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
getActionInput(
|
getActionInput(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
id: string,
|
id: string,
|
||||||
@@ -71,28 +64,27 @@ export class SystemForStartOs implements System {
|
|||||||
return action.run({ effects, input })
|
return action.run({ effects, input })
|
||||||
}
|
}
|
||||||
|
|
||||||
async exit(): Promise<void> {}
|
|
||||||
|
|
||||||
async start(effects: Effects): Promise<void> {
|
async start(effects: Effects): Promise<void> {
|
||||||
if (this.runningMain) return
|
try {
|
||||||
effects.constRetry = utils.once(() => effects.restart())
|
if (this.runningMain || this.starting) return
|
||||||
let mainOnTerm: () => Promise<void> | undefined
|
this.starting = true
|
||||||
const started = async (onTerm: () => Promise<void>) => {
|
effects.constRetry = utils.once(() => {
|
||||||
await effects.setMainStatus({ status: "running" })
|
console.debug(".const() triggered")
|
||||||
mainOnTerm = onTerm
|
effects.restart()
|
||||||
return null
|
|
||||||
}
|
|
||||||
const daemons = await (
|
|
||||||
await this.abi.main({
|
|
||||||
effects,
|
|
||||||
started,
|
|
||||||
})
|
})
|
||||||
).build()
|
let mainOnTerm: () => Promise<void> | undefined
|
||||||
this.runningMain = {
|
const daemons = await (
|
||||||
stop: async () => {
|
await this.abi.main({
|
||||||
if (mainOnTerm) await mainOnTerm()
|
effects,
|
||||||
await daemons.term()
|
})
|
||||||
},
|
).build()
|
||||||
|
this.runningMain = {
|
||||||
|
stop: async () => {
|
||||||
|
await daemons.term()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.starting = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { types as T } from "@start9labs/start-sdk"
|
import {
|
||||||
|
ExtendedVersion,
|
||||||
|
types as T,
|
||||||
|
VersionRange,
|
||||||
|
} from "@start9labs/start-sdk"
|
||||||
import { Effects } from "../Models/Effects"
|
import { Effects } from "../Models/Effects"
|
||||||
import { CallbackHolder } from "../Models/CallbackHolder"
|
import { CallbackHolder } from "../Models/CallbackHolder"
|
||||||
import { Optional } from "ts-matches/lib/parsers/interfaces"
|
|
||||||
|
|
||||||
export type Procedure =
|
export type Procedure =
|
||||||
| "/packageInit"
|
|
||||||
| "/packageUninit"
|
|
||||||
| "/backup/create"
|
| "/backup/create"
|
||||||
| "/backup/restore"
|
|
||||||
| `/actions/${string}/getInput`
|
| `/actions/${string}/getInput`
|
||||||
| `/actions/${string}/run`
|
| `/actions/${string}/run`
|
||||||
|
|
||||||
@@ -15,20 +15,15 @@ export type ExecuteResult =
|
|||||||
| { ok: unknown }
|
| { ok: unknown }
|
||||||
| { err: { code: number; message: string } }
|
| { err: { code: number; message: string } }
|
||||||
export type System = {
|
export type System = {
|
||||||
containerInit(effects: T.Effects): Promise<void>
|
init(
|
||||||
|
effects: T.Effects,
|
||||||
|
kind: "install" | "update" | "restore" | null,
|
||||||
|
): Promise<void>
|
||||||
|
|
||||||
start(effects: T.Effects): Promise<void>
|
start(effects: T.Effects): Promise<void>
|
||||||
stop(): Promise<void>
|
stop(): Promise<void>
|
||||||
|
|
||||||
packageInit(effects: Effects, timeoutMs: number | null): Promise<void>
|
|
||||||
packageUninit(
|
|
||||||
effects: Effects,
|
|
||||||
nextVersion: Optional<string>,
|
|
||||||
timeoutMs: number | null,
|
|
||||||
): Promise<void>
|
|
||||||
|
|
||||||
createBackup(effects: T.Effects, timeoutMs: number | null): Promise<void>
|
createBackup(effects: T.Effects, timeoutMs: number | null): Promise<void>
|
||||||
restoreBackup(effects: T.Effects, timeoutMs: number | null): Promise<void>
|
|
||||||
runAction(
|
runAction(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
actionId: string,
|
actionId: string,
|
||||||
@@ -41,7 +36,10 @@ export type System = {
|
|||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
): Promise<T.ActionInput | null>
|
): Promise<T.ActionInput | null>
|
||||||
|
|
||||||
exit(): Promise<void>
|
exit(
|
||||||
|
effects: Effects,
|
||||||
|
target: ExtendedVersion | VersionRange | null,
|
||||||
|
): Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RunningMain = {
|
export type RunningMain = {
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ export class CallbackHolder {
|
|||||||
constructor(private effects?: T.Effects) {}
|
constructor(private effects?: T.Effects) {}
|
||||||
|
|
||||||
private callbacks = new Map<number, Function>()
|
private callbacks = new Map<number, Function>()
|
||||||
private children: WeakRef<CallbackHolder>[] = []
|
private onLeaveContextCallbacks: Function[] = []
|
||||||
|
private children: Map<string, CallbackHolder> = new Map()
|
||||||
private newId() {
|
private newId() {
|
||||||
return CallbackIdCell.inc++
|
return CallbackIdCell.inc++
|
||||||
}
|
}
|
||||||
@@ -32,23 +33,30 @@ export class CallbackHolder {
|
|||||||
})
|
})
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
child(): CallbackHolder {
|
child(name: string): CallbackHolder {
|
||||||
const child = new CallbackHolder()
|
this.removeChild(name)
|
||||||
this.children.push(new WeakRef(child))
|
const child = new CallbackHolder(this.effects)
|
||||||
|
this.children.set(name, child)
|
||||||
return child
|
return child
|
||||||
}
|
}
|
||||||
removeChild(child: CallbackHolder) {
|
|
||||||
this.children = this.children.filter((c) => {
|
getChild(name: string): CallbackHolder | null {
|
||||||
const ref = c.deref()
|
return this.children.get(name) || null
|
||||||
return ref && ref !== child
|
}
|
||||||
})
|
|
||||||
|
removeChild(name: string) {
|
||||||
|
const child = this.children.get(name)
|
||||||
|
if (child) {
|
||||||
|
child.leaveContext()
|
||||||
|
this.children.delete(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private getCallback(index: number): Function | undefined {
|
private getCallback(index: number): Function | undefined {
|
||||||
let callback = this.callbacks.get(index)
|
let callback = this.callbacks.get(index)
|
||||||
if (callback) this.callbacks.delete(index)
|
if (callback) this.callbacks.delete(index)
|
||||||
else {
|
else {
|
||||||
for (let i = 0; i < this.children.length; i++) {
|
for (let [_, child] of this.children) {
|
||||||
callback = this.children[i].deref()?.getCallback(index)
|
callback = child.getCallback(index)
|
||||||
if (callback) return callback
|
if (callback) return callback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,6 +65,25 @@ export class CallbackHolder {
|
|||||||
callCallback(index: number, args: any[]): Promise<unknown> {
|
callCallback(index: number, args: any[]): Promise<unknown> {
|
||||||
const callback = this.getCallback(index)
|
const callback = this.getCallback(index)
|
||||||
if (!callback) return Promise.resolve()
|
if (!callback) return Promise.resolve()
|
||||||
return Promise.resolve().then(() => callback(...args))
|
return Promise.resolve()
|
||||||
|
.then(() => callback(...args))
|
||||||
|
.catch((e) => console.error("callback failed", e))
|
||||||
|
}
|
||||||
|
onLeaveContext(fn: Function) {
|
||||||
|
this.onLeaveContextCallbacks.push(fn)
|
||||||
|
}
|
||||||
|
leaveContext() {
|
||||||
|
for (let [_, child] of this.children) {
|
||||||
|
child.leaveContext()
|
||||||
|
}
|
||||||
|
this.children = new Map()
|
||||||
|
for (let fn of this.onLeaveContextCallbacks) {
|
||||||
|
try {
|
||||||
|
fn()
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.onLeaveContextCallbacks = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,31 +17,25 @@ const Path = string
|
|||||||
|
|
||||||
export type VolumeId = string
|
export type VolumeId = string
|
||||||
export type Path = string
|
export type Path = string
|
||||||
export const matchDockerProcedure = object(
|
export const matchDockerProcedure = object({
|
||||||
{
|
type: literal("docker"),
|
||||||
type: literal("docker"),
|
image: string,
|
||||||
image: string,
|
system: boolean.optional(),
|
||||||
system: boolean,
|
entrypoint: string,
|
||||||
entrypoint: string,
|
args: array(string).defaultTo([]),
|
||||||
args: array(string),
|
mounts: dictionary([VolumeId, Path]).optional(),
|
||||||
mounts: dictionary([VolumeId, Path]),
|
"io-format": literals(
|
||||||
"io-format": literals(
|
"json",
|
||||||
"json",
|
"json-pretty",
|
||||||
"json-pretty",
|
"yaml",
|
||||||
"yaml",
|
"cbor",
|
||||||
"cbor",
|
"toml",
|
||||||
"toml",
|
"toml-pretty",
|
||||||
"toml-pretty",
|
)
|
||||||
),
|
.nullable()
|
||||||
"sigterm-timeout": some(number, matchDuration),
|
.optional(),
|
||||||
inject: boolean,
|
"sigterm-timeout": some(number, matchDuration).onMismatch(30),
|
||||||
},
|
inject: boolean.defaultTo(false),
|
||||||
["io-format", "sigterm-timeout", "system", "args", "inject", "mounts"],
|
})
|
||||||
{
|
|
||||||
"sigterm-timeout": 30,
|
|
||||||
inject: false,
|
|
||||||
args: [],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
export type DockerProcedure = typeof matchDockerProcedure._TYPE
|
export type DockerProcedure = typeof matchDockerProcedure._TYPE
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ const getDependencies: AllGetDependencies = {
|
|||||||
system: getSystem,
|
system: getSystem,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let s of ["SIGTERM", "SIGINT", "SIGHUP"]) {
|
||||||
|
process.on(s, (s) => {
|
||||||
|
console.log(`Caught ${s}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
new RpcListener(getDependencies)
|
new RpcListener(getDependencies)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,8 +4,13 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if mountpoint tmp/combined; then sudo umount -R tmp/combined; fi
|
RUST_ARCH="$ARCH"
|
||||||
if mountpoint tmp/lower; then sudo umount tmp/lower; fi
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
|
RUST_ARCH="riscv64gc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if mountpoint -q tmp/combined; then sudo umount -l tmp/combined; fi
|
||||||
|
if mountpoint -q tmp/lower; then sudo umount tmp/lower; fi
|
||||||
sudo rm -rf tmp
|
sudo rm -rf tmp
|
||||||
mkdir -p tmp/lower tmp/upper tmp/work tmp/combined
|
mkdir -p tmp/lower tmp/upper tmp/work tmp/combined
|
||||||
if which squashfuse > /dev/null; then
|
if which squashfuse > /dev/null; then
|
||||||
@@ -13,19 +18,23 @@ if which squashfuse > /dev/null; then
|
|||||||
else
|
else
|
||||||
sudo mount debian.${ARCH}.squashfs tmp/lower
|
sudo mount debian.${ARCH}.squashfs tmp/lower
|
||||||
fi
|
fi
|
||||||
sudo mount -t overlay -olowerdir=tmp/lower,upperdir=tmp/upper,workdir=tmp/work overlay tmp/combined
|
if which fuse-overlayfs > /dev/null; then
|
||||||
|
sudo fuse-overlayfs -olowerdir=tmp/lower,upperdir=tmp/upper,workdir=tmp/work overlay tmp/combined
|
||||||
|
else
|
||||||
|
sudo mount -t overlay -olowerdir=tmp/lower,upperdir=tmp/upper,workdir=tmp/work overlay tmp/combined
|
||||||
|
fi
|
||||||
|
|
||||||
QEMU=
|
QEMU=
|
||||||
if [ "$ARCH" != "$(uname -m)" ]; then
|
if [ "$ARCH" != "$(uname -m)" ]; then
|
||||||
QEMU=/usr/bin/qemu-${ARCH}-static
|
QEMU=/usr/bin/qemu-${ARCH}
|
||||||
if ! which qemu-$ARCH-static > /dev/null; then
|
if ! which qemu-$ARCH > /dev/null; then
|
||||||
>&2 echo qemu-user-static is required for cross-platform builds
|
>&2 echo qemu-user is required for cross-platform builds
|
||||||
sudo umount tmp/combined
|
sudo umount tmp/combined
|
||||||
sudo umount tmp/lower
|
sudo umount tmp/lower
|
||||||
sudo rm -rf tmp
|
sudo rm -rf tmp
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
sudo cp $(which qemu-$ARCH-static) tmp/combined${QEMU}
|
sudo cp $(which qemu-$ARCH) tmp/combined${QEMU}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo mkdir -p tmp/combined/usr/lib/startos/
|
sudo mkdir -p tmp/combined/usr/lib/startos/
|
||||||
@@ -33,8 +42,12 @@ sudo rsync -a --copy-unsafe-links dist/ tmp/combined/usr/lib/startos/init/
|
|||||||
sudo chown -R 0:0 tmp/combined/usr/lib/startos/
|
sudo chown -R 0:0 tmp/combined/usr/lib/startos/
|
||||||
sudo cp container-runtime.service tmp/combined/lib/systemd/system/container-runtime.service
|
sudo cp container-runtime.service tmp/combined/lib/systemd/system/container-runtime.service
|
||||||
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime.service
|
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime.service
|
||||||
sudo cp ../core/target/$ARCH-unknown-linux-musl/release/containerbox tmp/combined/usr/bin/start-cli
|
sudo cp container-runtime-failure.service tmp/combined/lib/systemd/system/container-runtime-failure.service
|
||||||
sudo chown 0:0 tmp/combined/usr/bin/start-cli
|
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime-failure.service
|
||||||
|
sudo cp ../core/target/${RUST_ARCH}-unknown-linux-musl/release/start-container tmp/combined/usr/bin/start-container
|
||||||
|
echo -e '#!/bin/bash\nexec start-container "$@"' | sudo tee tmp/combined/usr/bin/start-cli # TODO: remove
|
||||||
|
sudo chmod +x tmp/combined/usr/bin/start-cli
|
||||||
|
sudo chown 0:0 tmp/combined/usr/bin/start-container
|
||||||
echo container-runtime | sha256sum | head -c 32 | cat - <(echo) | sudo tee tmp/combined/etc/machine-id
|
echo container-runtime | sha256sum | head -c 32 | cat - <(echo) | sudo tee tmp/combined/etc/machine-id
|
||||||
cat deb-install.sh | sudo systemd-nspawn --console=pipe -D tmp/combined $QEMU /bin/bash
|
cat deb-install.sh | sudo systemd-nspawn --console=pipe -D tmp/combined $QEMU /bin/bash
|
||||||
sudo truncate -s 0 tmp/combined/etc/machine-id
|
sudo truncate -s 0 tmp/combined/etc/machine-id
|
||||||
|
|||||||
7346
core/Cargo.lock
generated
7346
core/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
|
|
||||||
members = ["helpers", "models", "startos"]
|
members = ["startos"]
|
||||||
|
|||||||
2
core/Cross.toml
Normal file
2
core/Cross.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[build]
|
||||||
|
pre-build = ["apt-get update && apt-get install -y rsync"]
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
## Artifacts
|
## Artifacts
|
||||||
|
|
||||||
The StartOS backend is packed into a single binary `startbox` that is symlinked under
|
The StartOS backend is packed into a single binary `startbox` that is symlinked under
|
||||||
several different names for different behaviour:
|
several different names for different behavior:
|
||||||
|
|
||||||
- `startd`: This is the main daemon of StartOS
|
- `startd`: This is the main daemon of StartOS
|
||||||
- `start-cli`: This is a CLI tool that will allow you to issue commands to
|
- `start-cli`: This is a CLI tool that will allow you to issue commands to
|
||||||
|
|||||||
@@ -2,53 +2,78 @@
|
|||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
set -ea
|
set -ea
|
||||||
|
|
||||||
|
INSTALL=false
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--install)
|
||||||
|
INSTALL=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
>&2 echo "Unknown option: $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
PROFILE=${PROFILE:-release}
|
||||||
ARCH=$(uname -m)
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -z "${ARCH:-}" ]; then
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$ARCH" = "arm64" ]; then
|
if [ "$ARCH" = "arm64" ]; then
|
||||||
ARCH="aarch64"
|
ARCH="aarch64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$KERNEL_NAME" ]; then
|
RUST_ARCH="$ARCH"
|
||||||
KERNEL_NAME=$(uname -s)
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
|
RUST_ARCH="riscv64gc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$TARGET" ]; then
|
if [ -z "${KERNEL_NAME:-}" ]; then
|
||||||
if [ "$KERNEL_NAME" = "Linux" ]; then
|
KERNEL_NAME=$(uname -s)
|
||||||
TARGET="$ARCH-unknown-linux-musl"
|
|
||||||
elif [ "$KERNEL_NAME" = "Darwin" ]; then
|
|
||||||
TARGET="$ARCH-apple-darwin"
|
|
||||||
else
|
|
||||||
>&2 echo "unknown kernel $KERNEL_NAME"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
USE_TTY=
|
if [ -z "${TARGET:-}" ]; then
|
||||||
if tty -s; then
|
if [ "$KERNEL_NAME" = "Linux" ]; then
|
||||||
USE_TTY="-it"
|
TARGET="$RUST_ARCH-unknown-linux-musl"
|
||||||
|
elif [ "$KERNEL_NAME" = "Darwin" ]; then
|
||||||
|
TARGET="$RUST_ARCH-apple-darwin"
|
||||||
|
else
|
||||||
|
>&2 echo "unknown kernel $KERNEL_NAME"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
FEATURES="$(echo "${ENVIRONMENT:-}" | sed 's/-/,/g')"
|
||||||
RUSTFLAGS=""
|
RUSTFLAGS=""
|
||||||
|
if [[ "${ENVIRONMENT:-}" =~ (^|-)console($|-) ]]; then
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
RUSTFLAGS="--cfg tokio_unstable"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if which zig > /dev/null && [ "$ENFORCE_USE_DOCKER" != 1 ]; do
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=$FEATURES --locked --bin start-cli --target=$TARGET
|
||||||
RUSTFLAGS=$RUSTFLAGS sh -c "cd core && cargo zigbuild --release --no-default-features --features cli,$FEATURES --locked --bin start-cli --target=$TARGET"
|
if [ "$(ls -nd "core/target/$TARGET/$PROFILE/start-cli" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
else
|
rust-zig-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /usr/local/cargo"
|
||||||
alias 'rust-zig-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/cargo-zigbuild'
|
fi
|
||||||
RUSTFLAGS=$RUSTFLAGS rust-zig-builder sh -c "cd core && cargo zigbuild --release --no-default-features --features cli,$FEATURES --locked --bin start-cli --target=$TARGET"
|
|
||||||
|
|
||||||
if [ "$(ls -nd core/target/$TARGET/release/start-cli | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$INSTALL" = "true" ]; then
|
||||||
rust-zig-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
cp "core/target/$TARGET/$PROFILE/start-cli" ~/.cargo/bin/start-cli
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
|
||||||
|
|
||||||
set -ea
|
|
||||||
shopt -s expand_aliases
|
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
|
||||||
ARCH=$(uname -m)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$ARCH" = "arm64" ]; then
|
|
||||||
ARCH="aarch64"
|
|
||||||
fi
|
|
||||||
|
|
||||||
USE_TTY=
|
|
||||||
if tty -s; then
|
|
||||||
USE_TTY="-it"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd ..
|
|
||||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
|
||||||
RUSTFLAGS=""
|
|
||||||
|
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
|
||||||
RUSTFLAGS="--cfg tokio_unstable"
|
|
||||||
fi
|
|
||||||
|
|
||||||
alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
|
|
||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
|
||||||
rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features container-runtime,$FEATURES --locked --bin containerbox --target=$ARCH-unknown-linux-musl"
|
|
||||||
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/containerbox | awk '{ print $3 }')" != "$UID" ]; then
|
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
|
||||||
fi
|
|
||||||
@@ -2,9 +2,21 @@
|
|||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
set -ea
|
set -ea
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
|
PROFILE=${PROFILE:-release}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
@@ -13,24 +25,22 @@ if [ "$ARCH" = "arm64" ]; then
|
|||||||
ARCH="aarch64"
|
ARCH="aarch64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
USE_TTY=
|
RUST_ARCH="$ARCH"
|
||||||
if tty -s; then
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
USE_TTY="-it"
|
RUST_ARCH="riscv64gc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
RUSTFLAGS=""
|
RUSTFLAGS=""
|
||||||
|
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||||
RUSTFLAGS="--cfg tokio_unstable"
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
|
|
||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli,registry,$FEATURES --locked --bin registrybox --target=$ARCH-unknown-linux-musl"
|
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=$FEATURES --locked --bin registrybox --target=$RUST_ARCH-unknown-linux-musl
|
||||||
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/registrybox | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/registrybox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /usr/local/cargo"
|
||||||
fi
|
fi
|
||||||
46
core/build-start-container.sh
Executable file
46
core/build-start-container.sh
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
|
set -ea
|
||||||
|
shopt -s expand_aliases
|
||||||
|
|
||||||
|
PROFILE=${PROFILE:-release}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$ARCH" ]; then
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$ARCH" = "arm64" ]; then
|
||||||
|
ARCH="aarch64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
RUST_ARCH="$ARCH"
|
||||||
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
|
RUST_ARCH="riscv64gc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
|
RUSTFLAGS=""
|
||||||
|
|
||||||
|
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||||
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "FEATURES=\"$FEATURES\""
|
||||||
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
|
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=$FEATURES --locked --bin start-container --target=$RUST_ARCH-unknown-linux-musl
|
||||||
|
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/start-container" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /usr/local/cargo"
|
||||||
|
fi
|
||||||
@@ -2,9 +2,21 @@
|
|||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
set -ea
|
set -ea
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
|
PROFILE=${PROFILE:-release}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
@@ -13,24 +25,22 @@ if [ "$ARCH" = "arm64" ]; then
|
|||||||
ARCH="aarch64"
|
ARCH="aarch64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
USE_TTY=
|
RUST_ARCH="$ARCH"
|
||||||
if tty -s; then
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
USE_TTY="-it"
|
RUST_ARCH="riscv64gc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
RUSTFLAGS=""
|
RUSTFLAGS=""
|
||||||
|
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||||
RUSTFLAGS="--cfg tokio_unstable"
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
|
|
||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli,daemon,$FEATURES --locked --bin startbox --target=$ARCH-unknown-linux-musl"
|
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=$FEATURES --locked --bin startbox --target=$RUST_ARCH-unknown-linux-musl
|
||||||
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/startbox | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/startbox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /usr/local/cargo"
|
||||||
fi
|
fi
|
||||||
@@ -2,35 +2,43 @@
|
|||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
set -ea
|
set -ea
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
|
PROFILE=${PROFILE:-release}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$ARCH" = "arm64" ]; then
|
if [ "$ARCH" = "arm64" ]; then
|
||||||
ARCH="aarch64"
|
ARCH="aarch64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
USE_TTY=
|
RUST_ARCH="$ARCH"
|
||||||
if tty -s; then
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
USE_TTY="-it"
|
RUST_ARCH="riscv64gc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
RUSTFLAGS=""
|
RUSTFLAGS=""
|
||||||
|
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
|
||||||
RUSTFLAGS="--cfg tokio_unstable"
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
|
|
||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
rust-musl-builder sh -c "cd core && cargo test --release --features=test,$FEATURES 'export_bindings_' && chown \$UID:\$UID startos/bindings"
|
rust-zig-builder cargo test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features test,$FEATURES --locked 'export_bindings_'
|
||||||
if [ "$(ls -nd core/startos/bindings | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd "core/startos/bindings" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID startos/bindings && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID core/startos/bindings && chown -R $UID:$UID /usr/local/cargo"
|
||||||
fi
|
fi
|
||||||
46
core/build-tunnelbox.sh
Executable file
46
core/build-tunnelbox.sh
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
|
set -ea
|
||||||
|
shopt -s expand_aliases
|
||||||
|
|
||||||
|
PROFILE=${PROFILE:-release}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$ARCH" ]; then
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$ARCH" = "arm64" ]; then
|
||||||
|
ARCH="aarch64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
RUST_ARCH="$ARCH"
|
||||||
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
|
RUST_ARCH="riscv64gc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
|
RUSTFLAGS=""
|
||||||
|
|
||||||
|
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||||
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "FEATURES=\"$FEATURES\""
|
||||||
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
|
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=$FEATURES --locked --bin tunnelbox --target=$RUST_ARCH-unknown-linux-musl
|
||||||
|
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/tunnelbox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /usr/local/cargo"
|
||||||
|
fi
|
||||||
8
core/builder-alias.sh
Normal file
8
core/builder-alias.sh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
USE_TTY=
|
||||||
|
if tty -s; then
|
||||||
|
USE_TTY="-it"
|
||||||
|
fi
|
||||||
|
|
||||||
|
alias 'rust-zig-builder'='docker run '"$USE_TTY"' --rm -e "RUSTFLAGS=$RUSTFLAGS" -e "PKG_CONFIG_SYSROOT_DIR=/opt/sysroot/$ARCH" -e PKG_CONFIG_PATH="" -e PKG_CONFIG_LIBDIR="/opt/sysroot/$ARCH/usr/lib/pkgconfig" -e "AWS_LC_SYS_CMAKE_TOOLCHAIN_FILE_riscv64gc_unknown_linux_musl=/root/cmake-overrides/toolchain-riscv64-musl-clang.cmake" -e SCCACHE_GHA_ENABLED -e SCCACHE_GHA_VERSION -e ACTIONS_RESULTS_URL -e ACTIONS_RUNTIME_TOKEN -v "$HOME/.cargo/registry":/usr/local/cargo/registry -v "$HOME/.cargo/git":/usr/local/cargo/git -v "$HOME/.cache/sccache":/root/.cache/sccache -v "$HOME/.cache/cargo-zigbuild:/root/.cache/cargo-zigbuild" -v "$(pwd)":/workdir -w /workdir start9/cargo-zigbuild'
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "helpers"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
color-eyre = "0.6.2"
|
|
||||||
futures = "0.3.28"
|
|
||||||
lazy_async_pool = "0.3.3"
|
|
||||||
models = { path = "../models" }
|
|
||||||
pin-project = "1.1.3"
|
|
||||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" }
|
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
|
||||||
tokio-stream = { version = "0.1.14", features = ["io-util", "sync"] }
|
|
||||||
tracing = "0.1.39"
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
use std::task::Poll;
|
|
||||||
|
|
||||||
use tokio::io::{AsyncRead, ReadBuf};
|
|
||||||
|
|
||||||
#[pin_project::pin_project]
|
|
||||||
pub struct ByteReplacementReader<R> {
|
|
||||||
pub replace: u8,
|
|
||||||
pub with: u8,
|
|
||||||
#[pin]
|
|
||||||
pub inner: R,
|
|
||||||
}
|
|
||||||
impl<R: AsyncRead> AsyncRead for ByteReplacementReader<R> {
|
|
||||||
fn poll_read(
|
|
||||||
self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
buf: &mut ReadBuf<'_>,
|
|
||||||
) -> std::task::Poll<std::io::Result<()>> {
|
|
||||||
let this = self.project();
|
|
||||||
match this.inner.poll_read(cx, buf) {
|
|
||||||
Poll::Ready(Ok(())) => {
|
|
||||||
for idx in 0..buf.filled().len() {
|
|
||||||
if buf.filled()[idx] == *this.replace {
|
|
||||||
buf.filled_mut()[idx] = *this.with;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
a => a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
use std::future::Future;
|
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use color_eyre::eyre::{eyre, Context, Error};
|
|
||||||
use futures::future::BoxFuture;
|
|
||||||
use futures::FutureExt;
|
|
||||||
use models::ResultExt;
|
|
||||||
use tokio::fs::File;
|
|
||||||
use tokio::sync::oneshot;
|
|
||||||
use tokio::task::{JoinError, JoinHandle, LocalSet};
|
|
||||||
|
|
||||||
mod byte_replacement_reader;
|
|
||||||
mod rsync;
|
|
||||||
mod script_dir;
|
|
||||||
pub use byte_replacement_reader::*;
|
|
||||||
pub use rsync::*;
|
|
||||||
pub use script_dir::*;
|
|
||||||
|
|
||||||
pub fn const_true() -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_tmp_path(path: impl AsRef<Path>) -> Result<PathBuf, Error> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
if let (Some(parent), Some(file_name)) =
|
|
||||||
(path.parent(), path.file_name().and_then(|f| f.to_str()))
|
|
||||||
{
|
|
||||||
Ok(parent.join(format!(".{}.tmp", file_name)))
|
|
||||||
} else {
|
|
||||||
Err(eyre!("invalid path: {}", path.display()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn canonicalize(
|
|
||||||
path: impl AsRef<Path> + Send + Sync,
|
|
||||||
create_parent: bool,
|
|
||||||
) -> Result<PathBuf, Error> {
|
|
||||||
fn create_canonical_folder<'a>(
|
|
||||||
path: impl AsRef<Path> + Send + Sync + 'a,
|
|
||||||
) -> BoxFuture<'a, Result<PathBuf, Error>> {
|
|
||||||
async move {
|
|
||||||
let path = canonicalize(path, true).await?;
|
|
||||||
tokio::fs::create_dir(&path)
|
|
||||||
.await
|
|
||||||
.with_context(|| path.display().to_string())?;
|
|
||||||
Ok(path)
|
|
||||||
}
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
let path = path.as_ref();
|
|
||||||
if tokio::fs::metadata(path).await.is_err() {
|
|
||||||
let parent = path.parent().unwrap_or(Path::new("."));
|
|
||||||
if let Some(file_name) = path.file_name() {
|
|
||||||
if create_parent && tokio::fs::metadata(parent).await.is_err() {
|
|
||||||
return Ok(create_canonical_folder(parent).await?.join(file_name));
|
|
||||||
} else {
|
|
||||||
return Ok(tokio::fs::canonicalize(parent)
|
|
||||||
.await
|
|
||||||
.with_context(|| parent.display().to_string())?
|
|
||||||
.join(file_name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokio::fs::canonicalize(&path)
|
|
||||||
.await
|
|
||||||
.with_context(|| path.display().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pin_project::pin_project(PinnedDrop)]
|
|
||||||
pub struct NonDetachingJoinHandle<T>(#[pin] JoinHandle<T>);
|
|
||||||
impl<T> NonDetachingJoinHandle<T> {
|
|
||||||
pub async fn wait_for_abort(self) -> Result<T, JoinError> {
|
|
||||||
self.abort();
|
|
||||||
self.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T> From<JoinHandle<T>> for NonDetachingJoinHandle<T> {
|
|
||||||
fn from(t: JoinHandle<T>) -> Self {
|
|
||||||
NonDetachingJoinHandle(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for NonDetachingJoinHandle<T> {
|
|
||||||
type Target = JoinHandle<T>;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T> DerefMut for NonDetachingJoinHandle<T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[pin_project::pinned_drop]
|
|
||||||
impl<T> PinnedDrop for NonDetachingJoinHandle<T> {
|
|
||||||
fn drop(self: std::pin::Pin<&mut Self>) {
|
|
||||||
let this = self.project();
|
|
||||||
this.0.into_ref().get_ref().abort()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T> Future for NonDetachingJoinHandle<T> {
|
|
||||||
type Output = Result<T, JoinError>;
|
|
||||||
fn poll(
|
|
||||||
self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> std::task::Poll<Self::Output> {
|
|
||||||
let this = self.project();
|
|
||||||
this.0.poll(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AtomicFile {
|
|
||||||
tmp_path: PathBuf,
|
|
||||||
path: PathBuf,
|
|
||||||
file: Option<File>,
|
|
||||||
}
|
|
||||||
impl AtomicFile {
|
|
||||||
pub async fn new(
|
|
||||||
path: impl AsRef<Path> + Send + Sync,
|
|
||||||
tmp_path: Option<impl AsRef<Path> + Send + Sync>,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
let path = canonicalize(&path, true).await?;
|
|
||||||
let tmp_path = if let Some(tmp_path) = tmp_path {
|
|
||||||
canonicalize(&tmp_path, true).await?
|
|
||||||
} else {
|
|
||||||
to_tmp_path(&path)?
|
|
||||||
};
|
|
||||||
let file = File::create(&tmp_path)
|
|
||||||
.await
|
|
||||||
.with_context(|| tmp_path.display().to_string())?;
|
|
||||||
Ok(Self {
|
|
||||||
tmp_path,
|
|
||||||
path,
|
|
||||||
file: Some(file),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn rollback(mut self) -> Result<(), Error> {
|
|
||||||
drop(self.file.take());
|
|
||||||
tokio::fs::remove_file(&self.tmp_path)
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("rm {}", self.tmp_path.display()))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn save(mut self) -> Result<(), Error> {
|
|
||||||
use tokio::io::AsyncWriteExt;
|
|
||||||
if let Some(file) = self.file.as_mut() {
|
|
||||||
file.flush().await?;
|
|
||||||
file.shutdown().await?;
|
|
||||||
file.sync_all().await?;
|
|
||||||
}
|
|
||||||
drop(self.file.take());
|
|
||||||
tokio::fs::rename(&self.tmp_path, &self.path)
|
|
||||||
.await
|
|
||||||
.with_context(|| {
|
|
||||||
format!("mv {} -> {}", self.tmp_path.display(), self.path.display())
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::ops::Deref for AtomicFile {
|
|
||||||
type Target = File;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.file.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::ops::DerefMut for AtomicFile {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
self.file.as_mut().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Drop for AtomicFile {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if let Some(file) = self.file.take() {
|
|
||||||
drop(file);
|
|
||||||
let path = std::mem::take(&mut self.tmp_path);
|
|
||||||
tokio::spawn(async move { tokio::fs::remove_file(path).await.log_err() });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TimedResource<T: 'static + Send> {
|
|
||||||
handle: NonDetachingJoinHandle<Option<T>>,
|
|
||||||
ready: oneshot::Sender<()>,
|
|
||||||
}
|
|
||||||
impl<T: 'static + Send> TimedResource<T> {
|
|
||||||
pub fn new(resource: T, timer: Duration) -> Self {
|
|
||||||
let (send, recv) = oneshot::channel();
|
|
||||||
let handle = tokio::spawn(async move {
|
|
||||||
tokio::select! {
|
|
||||||
_ = tokio::time::sleep(timer) => {
|
|
||||||
drop(resource);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
_ = recv => Some(resource),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Self {
|
|
||||||
handle: handle.into(),
|
|
||||||
ready: send,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_with_destructor<
|
|
||||||
Fn: FnOnce(T) -> Fut + Send + 'static,
|
|
||||||
Fut: Future<Output = ()> + Send,
|
|
||||||
>(
|
|
||||||
resource: T,
|
|
||||||
timer: Duration,
|
|
||||||
destructor: Fn,
|
|
||||||
) -> Self {
|
|
||||||
let (send, recv) = oneshot::channel();
|
|
||||||
let handle = tokio::spawn(async move {
|
|
||||||
tokio::select! {
|
|
||||||
_ = tokio::time::sleep(timer) => {
|
|
||||||
destructor(resource).await;
|
|
||||||
None
|
|
||||||
},
|
|
||||||
_ = recv => Some(resource),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Self {
|
|
||||||
handle: handle.into(),
|
|
||||||
ready: send,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get(self) -> Option<T> {
|
|
||||||
let _ = self.ready.send(());
|
|
||||||
self.handle.await.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_timed_out(&self) -> bool {
|
|
||||||
self.ready.is_closed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn spawn_local<
|
|
||||||
T: 'static + Send,
|
|
||||||
F: FnOnce() -> Fut + Send + 'static,
|
|
||||||
Fut: Future<Output = T> + 'static,
|
|
||||||
>(
|
|
||||||
fut: F,
|
|
||||||
) -> NonDetachingJoinHandle<T> {
|
|
||||||
let (send, recv) = tokio::sync::oneshot::channel();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
tokio::runtime::Builder::new_current_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
.block_on(async move {
|
|
||||||
let set = LocalSet::new();
|
|
||||||
send.send(set.spawn_local(fut()).into())
|
|
||||||
.unwrap_or_else(|_| unreachable!());
|
|
||||||
set.await
|
|
||||||
})
|
|
||||||
});
|
|
||||||
recv.await.unwrap()
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use models::{PackageId, VersionString};
|
|
||||||
|
|
||||||
pub const PKG_SCRIPT_DIR: &str = "package-data/scripts";
|
|
||||||
|
|
||||||
pub fn script_dir<P: AsRef<Path>>(
|
|
||||||
datadir: P,
|
|
||||||
pkg_id: &PackageId,
|
|
||||||
version: &VersionString,
|
|
||||||
) -> PathBuf {
|
|
||||||
datadir
|
|
||||||
.as_ref()
|
|
||||||
.join(&*PKG_SCRIPT_DIR)
|
|
||||||
.join(pkg_id)
|
|
||||||
.join(version.as_str())
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
|
||||||
|
|
||||||
set -ea
|
|
||||||
shopt -s expand_aliases
|
|
||||||
|
|
||||||
web="../web/dist/static"
|
|
||||||
[ -d "$web" ] || mkdir -p "$web"
|
|
||||||
|
|
||||||
if [ -z "$PLATFORM" ]; then
|
|
||||||
PLATFORM=$(uname -m)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$PLATFORM" = "arm64" ]; then
|
|
||||||
PLATFORM="aarch64"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cargo install --path=./startos --no-default-features --features=cli,docker,registry --bin start-cli --locked
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "models"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
axum = "0.7.5"
|
|
||||||
base64 = "0.21.4"
|
|
||||||
color-eyre = "0.6.2"
|
|
||||||
ed25519-dalek = { version = "2.0.0", features = ["serde"] }
|
|
||||||
lazy_static = "1.4"
|
|
||||||
mbrman = "0.5.2"
|
|
||||||
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
|
||||||
"serde",
|
|
||||||
] }
|
|
||||||
ipnet = "2.8.0"
|
|
||||||
num_enum = "0.7.1"
|
|
||||||
openssl = { version = "0.10.57", features = ["vendored"] }
|
|
||||||
patch-db = { version = "*", path = "../../patch-db/patch-db", features = [
|
|
||||||
"trace",
|
|
||||||
] }
|
|
||||||
rand = "0.8.5"
|
|
||||||
regex = "1.10.2"
|
|
||||||
reqwest = "0.12"
|
|
||||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" }
|
|
||||||
rustls = "0.23"
|
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
sqlx = { version = "0.7.2", features = [
|
|
||||||
"chrono",
|
|
||||||
"runtime-tokio-rustls",
|
|
||||||
"postgres",
|
|
||||||
] }
|
|
||||||
ssh-key = "0.6.2"
|
|
||||||
ts-rs = { git = "https://github.com/dr-bonez/ts-rs.git", branch = "feature/top-level-as" } # "8"
|
|
||||||
thiserror = "1.0"
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
|
||||||
torut = { git = "https://github.com/Start9Labs/torut.git", branch = "update/dependencies" }
|
|
||||||
tracing = "0.1.39"
|
|
||||||
yasi = "0.1.5"
|
|
||||||
zbus = "5"
|
|
||||||
@@ -1,612 +0,0 @@
|
|||||||
use std::fmt::{Debug, Display};
|
|
||||||
|
|
||||||
use axum::http::uri::InvalidUri;
|
|
||||||
use axum::http::StatusCode;
|
|
||||||
use color_eyre::eyre::eyre;
|
|
||||||
use num_enum::TryFromPrimitive;
|
|
||||||
use patch_db::Revision;
|
|
||||||
use rpc_toolkit::reqwest;
|
|
||||||
use rpc_toolkit::yajrc::{
|
|
||||||
RpcError, INVALID_PARAMS_ERROR, INVALID_REQUEST_ERROR, METHOD_NOT_FOUND_ERROR, PARSE_ERROR,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::InvalidId;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
|
|
||||||
#[repr(i32)]
|
|
||||||
pub enum ErrorKind {
|
|
||||||
Unknown = 1,
|
|
||||||
Filesystem = 2,
|
|
||||||
Docker = 3,
|
|
||||||
ConfigSpecViolation = 4,
|
|
||||||
ConfigRulesViolation = 5,
|
|
||||||
NotFound = 6,
|
|
||||||
IncorrectPassword = 7,
|
|
||||||
VersionIncompatible = 8,
|
|
||||||
Network = 9,
|
|
||||||
Registry = 10,
|
|
||||||
Serialization = 11,
|
|
||||||
Deserialization = 12,
|
|
||||||
Utf8 = 13,
|
|
||||||
ParseVersion = 14,
|
|
||||||
IncorrectDisk = 15,
|
|
||||||
// Nginx = 16,
|
|
||||||
Dependency = 17,
|
|
||||||
ParseS9pk = 18,
|
|
||||||
ParseUrl = 19,
|
|
||||||
DiskNotAvailable = 20,
|
|
||||||
BlockDevice = 21,
|
|
||||||
InvalidOnionAddress = 22,
|
|
||||||
Pack = 23,
|
|
||||||
ValidateS9pk = 24,
|
|
||||||
DiskCorrupted = 25, // Remove
|
|
||||||
Tor = 26,
|
|
||||||
ConfigGen = 27,
|
|
||||||
ParseNumber = 28,
|
|
||||||
Database = 29,
|
|
||||||
InvalidId = 30,
|
|
||||||
InvalidSignature = 31,
|
|
||||||
Backup = 32,
|
|
||||||
Restore = 33,
|
|
||||||
Authorization = 34,
|
|
||||||
AutoConfigure = 35,
|
|
||||||
Action = 36,
|
|
||||||
RateLimited = 37,
|
|
||||||
InvalidRequest = 38,
|
|
||||||
MigrationFailed = 39,
|
|
||||||
Uninitialized = 40,
|
|
||||||
ParseNetAddress = 41,
|
|
||||||
ParseSshKey = 42,
|
|
||||||
SoundError = 43,
|
|
||||||
ParseTimestamp = 44,
|
|
||||||
ParseSysInfo = 45,
|
|
||||||
Wifi = 46,
|
|
||||||
Journald = 47,
|
|
||||||
DiskManagement = 48,
|
|
||||||
OpenSsl = 49,
|
|
||||||
PasswordHashGeneration = 50,
|
|
||||||
DiagnosticMode = 51,
|
|
||||||
ParseDbField = 52,
|
|
||||||
Duplicate = 53,
|
|
||||||
MultipleErrors = 54,
|
|
||||||
Incoherent = 55,
|
|
||||||
InvalidBackupTargetId = 56,
|
|
||||||
ProductKeyMismatch = 57,
|
|
||||||
LanPortConflict = 58,
|
|
||||||
Javascript = 59,
|
|
||||||
Pem = 60,
|
|
||||||
TLSInit = 61,
|
|
||||||
Ascii = 62,
|
|
||||||
MissingHeader = 63,
|
|
||||||
Grub = 64,
|
|
||||||
Systemd = 65,
|
|
||||||
OpenSsh = 66,
|
|
||||||
Zram = 67,
|
|
||||||
Lshw = 68,
|
|
||||||
CpuSettings = 69,
|
|
||||||
Firmware = 70,
|
|
||||||
Timeout = 71,
|
|
||||||
Lxc = 72,
|
|
||||||
Cancelled = 73,
|
|
||||||
Git = 74,
|
|
||||||
DBus = 75,
|
|
||||||
}
|
|
||||||
impl ErrorKind {
|
|
||||||
pub fn as_str(&self) -> &'static str {
|
|
||||||
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",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for ErrorKind {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Error {
|
|
||||||
pub source: color_eyre::eyre::Error,
|
|
||||||
pub kind: ErrorKind,
|
|
||||||
pub revision: Option<Revision>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}: {}", self.kind.as_str(), self.source)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Error {
|
|
||||||
pub fn new<E: Into<color_eyre::eyre::Error>>(source: E, kind: ErrorKind) -> Self {
|
|
||||||
Error {
|
|
||||||
source: source.into(),
|
|
||||||
kind,
|
|
||||||
revision: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn clone_output(&self) -> Self {
|
|
||||||
Error {
|
|
||||||
source: ErrorData {
|
|
||||||
details: format!("{}", self.source),
|
|
||||||
debug: format!("{:?}", self.source),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
kind: self.kind,
|
|
||||||
revision: self.revision.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl axum::response::IntoResponse for Error {
|
|
||||||
fn into_response(self) -> axum::response::Response {
|
|
||||||
let mut res = axum::Json(RpcError::from(self)).into_response();
|
|
||||||
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<std::convert::Infallible> for Error {
|
|
||||||
fn from(value: std::convert::Infallible) -> Self {
|
|
||||||
match value {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<InvalidId> for Error {
|
|
||||||
fn from(err: InvalidId) -> Self {
|
|
||||||
Error::new(err, ErrorKind::InvalidId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<std::io::Error> for Error {
|
|
||||||
fn from(e: std::io::Error) -> Self {
|
|
||||||
Error::new(e, ErrorKind::Filesystem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<std::str::Utf8Error> for Error {
|
|
||||||
fn from(e: std::str::Utf8Error) -> Self {
|
|
||||||
Error::new(e, ErrorKind::Utf8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<std::string::FromUtf8Error> for Error {
|
|
||||||
fn from(e: std::string::FromUtf8Error) -> Self {
|
|
||||||
Error::new(e, ErrorKind::Utf8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<exver::ParseError> for Error {
|
|
||||||
fn from(e: exver::ParseError) -> Self {
|
|
||||||
Error::new(e, ErrorKind::ParseVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<rpc_toolkit::url::ParseError> for Error {
|
|
||||||
fn from(e: rpc_toolkit::url::ParseError) -> Self {
|
|
||||||
Error::new(e, ErrorKind::ParseUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<std::num::ParseIntError> for Error {
|
|
||||||
fn from(e: std::num::ParseIntError) -> Self {
|
|
||||||
Error::new(e, ErrorKind::ParseNumber)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<std::num::ParseFloatError> for Error {
|
|
||||||
fn from(e: std::num::ParseFloatError) -> Self {
|
|
||||||
Error::new(e, ErrorKind::ParseNumber)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<patch_db::Error> for Error {
|
|
||||||
fn from(e: patch_db::Error) -> Self {
|
|
||||||
Error::new(e, ErrorKind::Database)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<sqlx::Error> for Error {
|
|
||||||
fn from(e: sqlx::Error) -> Self {
|
|
||||||
Error::new(e, ErrorKind::Database)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<ed25519_dalek::SignatureError> for Error {
|
|
||||||
fn from(e: ed25519_dalek::SignatureError) -> Self {
|
|
||||||
Error::new(e, ErrorKind::InvalidSignature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<std::net::AddrParseError> for Error {
|
|
||||||
fn from(e: std::net::AddrParseError) -> Self {
|
|
||||||
Error::new(e, ErrorKind::ParseNetAddress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<torut::control::ConnError> for Error {
|
|
||||||
fn from(e: torut::control::ConnError) -> Self {
|
|
||||||
Error::new(e, ErrorKind::Tor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<ipnet::AddrParseError> for Error {
|
|
||||||
fn from(e: ipnet::AddrParseError) -> Self {
|
|
||||||
Error::new(e, ErrorKind::ParseNetAddress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<openssl::error::ErrorStack> for Error {
|
|
||||||
fn from(e: openssl::error::ErrorStack) -> Self {
|
|
||||||
Error::new(eyre!("{}", e), ErrorKind::OpenSsl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<mbrman::Error> for Error {
|
|
||||||
fn from(e: mbrman::Error) -> Self {
|
|
||||||
Error::new(e, ErrorKind::DiskManagement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<InvalidUri> for Error {
|
|
||||||
fn from(e: InvalidUri) -> Self {
|
|
||||||
Error::new(eyre!("{}", e), ErrorKind::ParseUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<ssh_key::Error> for Error {
|
|
||||||
fn from(e: ssh_key::Error) -> Self {
|
|
||||||
Error::new(e, ErrorKind::OpenSsh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<reqwest::Error> for Error {
|
|
||||||
fn from(e: reqwest::Error) -> Self {
|
|
||||||
let kind = match e {
|
|
||||||
_ if e.is_builder() => ErrorKind::ParseUrl,
|
|
||||||
_ if e.is_decode() => ErrorKind::Deserialization,
|
|
||||||
_ => ErrorKind::Network,
|
|
||||||
};
|
|
||||||
Error::new(e, kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<torut::onion::OnionAddressParseError> for Error {
|
|
||||||
fn from(e: torut::onion::OnionAddressParseError) -> Self {
|
|
||||||
Error::new(e, ErrorKind::Tor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<zbus::Error> for Error {
|
|
||||||
fn from(e: zbus::Error) -> Self {
|
|
||||||
Error::new(e, ErrorKind::DBus)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<rustls::Error> for Error {
|
|
||||||
fn from(e: rustls::Error) -> Self {
|
|
||||||
Error::new(e, ErrorKind::OpenSsl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<patch_db::value::Error> for Error {
|
|
||||||
fn from(value: patch_db::value::Error) -> Self {
|
|
||||||
match value.kind {
|
|
||||||
patch_db::value::ErrorKind::Serialization => {
|
|
||||||
Error::new(value.source, ErrorKind::Serialization)
|
|
||||||
}
|
|
||||||
patch_db::value::ErrorKind::Deserialization => {
|
|
||||||
Error::new(value.source, ErrorKind::Deserialization)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
|
||||||
pub struct ErrorData {
|
|
||||||
pub details: String,
|
|
||||||
pub debug: String,
|
|
||||||
}
|
|
||||||
impl Display for ErrorData {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
Display::fmt(&self.details, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Debug for ErrorData {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
Display::fmt(&self.debug, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::error::Error for ErrorData {}
|
|
||||||
impl From<Error> for ErrorData {
|
|
||||||
fn from(value: Error) -> Self {
|
|
||||||
Self {
|
|
||||||
details: value.to_string(),
|
|
||||||
debug: format!("{:?}", value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<&RpcError> for ErrorData {
|
|
||||||
fn from(value: &RpcError) -> Self {
|
|
||||||
Self {
|
|
||||||
details: value
|
|
||||||
.data
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|d| {
|
|
||||||
d.as_object()
|
|
||||||
.and_then(|d| {
|
|
||||||
d.get("details")
|
|
||||||
.and_then(|d| d.as_str().map(|s| s.to_owned()))
|
|
||||||
})
|
|
||||||
.or_else(|| d.as_str().map(|s| s.to_owned()))
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| value.message.clone().into_owned()),
|
|
||||||
debug: value
|
|
||||||
.data
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|d| {
|
|
||||||
d.as_object()
|
|
||||||
.and_then(|d| {
|
|
||||||
d.get("debug")
|
|
||||||
.and_then(|d| d.as_str().map(|s| s.to_owned()))
|
|
||||||
})
|
|
||||||
.or_else(|| d.as_str().map(|s| s.to_owned()))
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| value.message.clone().into_owned()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Error> for RpcError {
|
|
||||||
fn from(e: Error) -> Self {
|
|
||||||
let mut data_object = serde_json::Map::with_capacity(3);
|
|
||||||
data_object.insert("details".to_owned(), format!("{}", e.source).into());
|
|
||||||
data_object.insert("debug".to_owned(), format!("{:?}", e.source).into());
|
|
||||||
data_object.insert(
|
|
||||||
"revision".to_owned(),
|
|
||||||
match serde_json::to_value(&e.revision) {
|
|
||||||
Ok(a) => a,
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!("Error serializing revision for Error object: {}", e);
|
|
||||||
serde_json::Value::Null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
RpcError {
|
|
||||||
code: e.kind as i32,
|
|
||||||
message: e.kind.as_str().into(),
|
|
||||||
data: Some(
|
|
||||||
match serde_json::to_value(&ErrorData {
|
|
||||||
details: format!("{}", e.source),
|
|
||||||
debug: format!("{:?}", e.source),
|
|
||||||
}) {
|
|
||||||
Ok(a) => a,
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!("Error serializing revision for Error object: {}", e);
|
|
||||||
serde_json::Value::Null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<RpcError> for Error {
|
|
||||||
fn from(e: RpcError) -> Self {
|
|
||||||
Error::new(
|
|
||||||
ErrorData::from(&e),
|
|
||||||
if let Ok(kind) = e.code.try_into() {
|
|
||||||
kind
|
|
||||||
} else if e.code == METHOD_NOT_FOUND_ERROR.code {
|
|
||||||
ErrorKind::NotFound
|
|
||||||
} else if e.code == PARSE_ERROR.code
|
|
||||||
|| e.code == INVALID_PARAMS_ERROR.code
|
|
||||||
|| e.code == INVALID_REQUEST_ERROR.code
|
|
||||||
{
|
|
||||||
ErrorKind::Deserialization
|
|
||||||
} else {
|
|
||||||
ErrorKind::Unknown
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct ErrorCollection(Vec<Error>);
|
|
||||||
impl ErrorCollection {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle<T, E: Into<Error>>(&mut self, result: Result<T, E>) -> Option<T> {
|
|
||||||
match result {
|
|
||||||
Ok(a) => Some(a),
|
|
||||||
Err(e) => {
|
|
||||||
self.0.push(e.into());
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_result(self) -> Result<(), Error> {
|
|
||||||
if self.0.is_empty() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Error::new(eyre!("{}", self), ErrorKind::MultipleErrors))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<ErrorCollection> for Result<(), Error> {
|
|
||||||
fn from(e: ErrorCollection) -> Self {
|
|
||||||
e.into_result()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T, E: Into<Error>> Extend<Result<T, E>> for ErrorCollection {
|
|
||||||
fn extend<I: IntoIterator<Item = Result<T, E>>>(&mut self, iter: I) {
|
|
||||||
for item in iter {
|
|
||||||
self.handle(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for ErrorCollection {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
for (idx, e) in self.0.iter().enumerate() {
|
|
||||||
if idx > 0 {
|
|
||||||
write!(f, "; ")?;
|
|
||||||
}
|
|
||||||
write!(f, "{}", e)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ResultExt<T, E>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error>;
|
|
||||||
fn with_ctx<F: FnOnce(&E) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error>;
|
|
||||||
fn log_err(self) -> Option<T>;
|
|
||||||
}
|
|
||||||
impl<T, E> ResultExt<T, E> for Result<T, E>
|
|
||||||
where
|
|
||||||
color_eyre::eyre::Error: From<E>,
|
|
||||||
{
|
|
||||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
|
||||||
self.map_err(|e| Error {
|
|
||||||
source: e.into(),
|
|
||||||
kind,
|
|
||||||
revision: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_ctx<F: FnOnce(&E) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
|
||||||
self.map_err(|e| {
|
|
||||||
let (kind, ctx) = f(&e);
|
|
||||||
let source = color_eyre::eyre::Error::from(e);
|
|
||||||
let ctx = format!("{}: {}", ctx, source);
|
|
||||||
let source = source.wrap_err(ctx);
|
|
||||||
Error {
|
|
||||||
kind,
|
|
||||||
source,
|
|
||||||
revision: None,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log_err(self) -> Option<T> {
|
|
||||||
match self {
|
|
||||||
Ok(a) => Some(a),
|
|
||||||
Err(e) => {
|
|
||||||
let e: color_eyre::eyre::Error = e.into();
|
|
||||||
tracing::error!("{e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T> ResultExt<T, Error> for Result<T, Error> {
|
|
||||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
|
||||||
self.map_err(|e| Error {
|
|
||||||
source: e.source,
|
|
||||||
kind,
|
|
||||||
revision: e.revision,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_ctx<F: FnOnce(&Error) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
|
||||||
self.map_err(|e| {
|
|
||||||
let (kind, ctx) = f(&e);
|
|
||||||
let source = e.source;
|
|
||||||
let ctx = format!("{}: {}", ctx, source);
|
|
||||||
let source = source.wrap_err(ctx);
|
|
||||||
Error {
|
|
||||||
kind,
|
|
||||||
source,
|
|
||||||
revision: e.revision,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log_err(self) -> Option<T> {
|
|
||||||
match self {
|
|
||||||
Ok(a) => Some(a),
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("{e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait OptionExt<T>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
fn or_not_found(self, message: impl std::fmt::Display) -> Result<T, Error>;
|
|
||||||
}
|
|
||||||
impl<T> OptionExt<T> for Option<T> {
|
|
||||||
fn or_not_found(self, message: impl std::fmt::Display) -> Result<T, Error> {
|
|
||||||
self.ok_or_else(|| Error::new(eyre!("{}", message), ErrorKind::NotFound))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! ensure_code {
|
|
||||||
($x:expr, $c:expr, $fmt:expr $(, $arg:expr)*) => {
|
|
||||||
if !($x) {
|
|
||||||
return Err(Error::new(color_eyre::eyre::eyre!($fmt, $($arg, )*), $c));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use color_eyre::eyre::bail;
|
|
||||||
use container_init::{Input, Output, ProcessId, RpcId};
|
|
||||||
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
/// Used by the js-executor, it is the ability to just create a command in an already running exec
|
|
||||||
pub type ExecCommand = Arc<
|
|
||||||
dyn Fn(
|
|
||||||
String,
|
|
||||||
Vec<String>,
|
|
||||||
UnboundedSender<container_init::Output>,
|
|
||||||
Option<Duration>,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<RpcId, String>> + 'static>>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
>;
|
|
||||||
|
|
||||||
/// Used by the js-executor, it is the ability to just create a command in an already running exec
|
|
||||||
pub type SendKillSignal = Arc<
|
|
||||||
dyn Fn(RpcId, u32) -> Pin<Box<dyn Future<Output = Result<(), String>> + 'static>>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
>;
|
|
||||||
|
|
||||||
pub trait CommandInserter {
|
|
||||||
fn insert_command(
|
|
||||||
&self,
|
|
||||||
command: String,
|
|
||||||
args: Vec<String>,
|
|
||||||
sender: UnboundedSender<container_init::Output>,
|
|
||||||
timeout: Option<Duration>,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Option<RpcId>>>>;
|
|
||||||
|
|
||||||
fn send_signal(&self, id: RpcId, command: u32) -> Pin<Box<dyn Future<Output = ()>>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type ArcCommandInserter = Arc<Mutex<Option<Box<dyn CommandInserter>>>>;
|
|
||||||
|
|
||||||
pub struct ExecutingCommand {
|
|
||||||
rpc_id: RpcId,
|
|
||||||
/// Will exist until killed
|
|
||||||
command_inserter: Arc<Mutex<Option<ArcCommandInserter>>>,
|
|
||||||
owned_futures: Arc<Mutex<Vec<Pin<Box<dyn Future<Output = ()>>>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExecutingCommand {
|
|
||||||
pub async fn new(
|
|
||||||
command_inserter: ArcCommandInserter,
|
|
||||||
command: String,
|
|
||||||
args: Vec<String>,
|
|
||||||
timeout: Option<Duration>,
|
|
||||||
) -> Result<ExecutingCommand, color_eyre::Report> {
|
|
||||||
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel::<Output>();
|
|
||||||
let rpc_id = {
|
|
||||||
let locked_command_inserter = command_inserter.lock().await;
|
|
||||||
let locked_command_inserter = match &*locked_command_inserter {
|
|
||||||
Some(a) => a,
|
|
||||||
None => bail!("Expecting containers.main in the package manifest".to_string()),
|
|
||||||
};
|
|
||||||
match locked_command_inserter
|
|
||||||
.insert_command(command, args, sender, timeout)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Some(a) => a,
|
|
||||||
None => bail!("Couldn't get command started ".to_string()),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let executing_commands = ExecutingCommand {
|
|
||||||
rpc_id,
|
|
||||||
command_inserter: Arc::new(Mutex::new(Some(command_inserter.clone()))),
|
|
||||||
owned_futures: Default::default(),
|
|
||||||
};
|
|
||||||
// let waiting = self.wait()
|
|
||||||
Ok(executing_commands)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn wait(
|
|
||||||
rpc_id: RpcId,
|
|
||||||
mut outputs: UnboundedReceiver<Output>,
|
|
||||||
) -> Result<String, (Option<i32>, String)> {
|
|
||||||
let (process_id_send, process_id_recv) = tokio::sync::oneshot::channel::<ProcessId>();
|
|
||||||
let mut answer = String::new();
|
|
||||||
let mut command_error = String::new();
|
|
||||||
let mut status: Option<i32> = None;
|
|
||||||
let mut process_id_send = Some(process_id_send);
|
|
||||||
while let Some(output) = outputs.recv().await {
|
|
||||||
match output {
|
|
||||||
Output::ProcessId(process_id) => {
|
|
||||||
if let Some(process_id_send) = process_id_send.take() {
|
|
||||||
if let Err(err) = process_id_send.send(process_id) {
|
|
||||||
tracing::error!(
|
|
||||||
"Could not get a process id {process_id:?} sent for {rpc_id:?}"
|
|
||||||
);
|
|
||||||
tracing::debug!("{err:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Output::Line(value) => {
|
|
||||||
answer.push_str(&value);
|
|
||||||
answer.push('\n');
|
|
||||||
}
|
|
||||||
Output::Error(error) => {
|
|
||||||
command_error.push_str(&error);
|
|
||||||
command_error.push('\n');
|
|
||||||
}
|
|
||||||
Output::Done(error_code) => {
|
|
||||||
status = error_code;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !command_error.is_empty() {
|
|
||||||
return Err((status, command_error));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(answer)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_signal(&self, signal: u32) {
|
|
||||||
let locked = self.command_inserter.lock().await;
|
|
||||||
let inner = match &*locked {
|
|
||||||
Some(a) => a,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
let locked = inner.lock().await;
|
|
||||||
let command_inserter = match &*locked {
|
|
||||||
Some(a) => a,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
command_inserter.send_signal(self.rpc_id, signal);
|
|
||||||
}
|
|
||||||
/// Should only be called when output::done
|
|
||||||
async fn killed(&self) {
|
|
||||||
*self.owned_futures.lock().await = Default::default();
|
|
||||||
*self.command_inserter.lock().await = Default::default();
|
|
||||||
}
|
|
||||||
pub fn rpc_id(&self) -> RpcId {
|
|
||||||
self.rpc_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for ExecutingCommand {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let command_inserter = self.command_inserter.clone();
|
|
||||||
let rpc_id = self.rpc_id.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let command_inserter_lock = command_inserter.lock().await;
|
|
||||||
let command_inserter = match &*command_inserter_lock {
|
|
||||||
Some(a) => a,
|
|
||||||
None => {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
command_inserter.send_kill_command(rpc_id, 9).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
mod clap;
|
|
||||||
mod data_url;
|
|
||||||
mod errors;
|
|
||||||
mod id;
|
|
||||||
mod mime;
|
|
||||||
mod procedure_name;
|
|
||||||
mod version;
|
|
||||||
|
|
||||||
pub use clap::*;
|
|
||||||
pub use data_url::*;
|
|
||||||
pub use errors::*;
|
|
||||||
pub use id::*;
|
|
||||||
pub use mime::*;
|
|
||||||
pub use procedure_name::*;
|
|
||||||
pub use version::*;
|
|
||||||
@@ -2,9 +2,21 @@
|
|||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
set -ea
|
set -ea
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
|
PROFILE=${PROFILE:-release}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
@@ -22,15 +34,12 @@ cd ..
|
|||||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
RUSTFLAGS=""
|
RUSTFLAGS=""
|
||||||
|
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||||
RUSTFLAGS="--cfg tokio_unstable"
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
|
|
||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
rust-musl-builder sh -c "apt-get update && apt-get install -y rsync && cd core && cargo test --release --features=test,$FEATURES --workspace --locked --target=$ARCH-unknown-linux-musl -- --skip export_bindings_ && chown \$UID:\$UID target"
|
rust-zig-builder cargo test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=test,$FEATURES --workspace --locked --lib -- --skip export_bindings_
|
||||||
if [ "$(ls -nd core/target | awk '{ print $3 }')" != "$UID" ]; then
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /usr/local/cargo"
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
|
||||||
fi
|
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
||||||
description = "The core of StartOS"
|
description = "The core of StartOS"
|
||||||
documentation = "https://docs.rs/start-os"
|
documentation = "https://docs.rs/start-os"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
keywords = [
|
keywords = [
|
||||||
"self-hosted",
|
|
||||||
"raspberry-pi",
|
|
||||||
"privacy",
|
|
||||||
"bitcoin",
|
"bitcoin",
|
||||||
"full-node",
|
"full-node",
|
||||||
"lightning",
|
"lightning",
|
||||||
|
"privacy",
|
||||||
|
"raspberry-pi",
|
||||||
|
"self-hosted",
|
||||||
]
|
]
|
||||||
|
license = "MIT"
|
||||||
name = "start-os"
|
name = "start-os"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/Start9Labs/start-os"
|
repository = "https://github.com/Start9Labs/start-os"
|
||||||
version = "0.3.6-alpha.18" # VERSION_BUMP
|
version = "0.4.0-alpha.16" # VERSION_BUMP
|
||||||
license = "MIT"
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "startos"
|
name = "startos"
|
||||||
@@ -23,47 +23,68 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "startbox"
|
name = "startbox"
|
||||||
path = "src/main.rs"
|
path = "src/main/startbox.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "start-cli"
|
name = "start-cli"
|
||||||
path = "src/main.rs"
|
path = "src/main/start-cli.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "containerbox"
|
name = "start-container"
|
||||||
path = "src/main.rs"
|
path = "src/main/start-container.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "registrybox"
|
name = "registrybox"
|
||||||
path = "src/main.rs"
|
path = "src/main/registrybox.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tunnelbox"
|
||||||
|
path = "src/main/tunnelbox.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
cli = []
|
arti = [
|
||||||
container-runtime = ["procfs", "tty-spawn"]
|
"arti-client",
|
||||||
daemon = ["mail-send"]
|
"safelog",
|
||||||
registry = []
|
"tor-cell",
|
||||||
default = ["cli", "daemon", "registry", "container-runtime"]
|
"tor-hscrypto",
|
||||||
|
"tor-hsservice",
|
||||||
|
"tor-keymgr",
|
||||||
|
"tor-llcrypto",
|
||||||
|
"tor-proto",
|
||||||
|
"tor-rtcompat",
|
||||||
|
]
|
||||||
|
console = ["console-subscriber", "tokio/tracing"]
|
||||||
|
default = []
|
||||||
dev = []
|
dev = []
|
||||||
unstable = ["console-subscriber", "tokio/tracing"]
|
|
||||||
docker = []
|
|
||||||
test = []
|
test = []
|
||||||
|
unstable = ["backtrace-on-stack-overflow"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes = { version = "0.7.5", features = ["ctr"] }
|
aes = { version = "0.7.5", features = ["ctr"] }
|
||||||
|
arti-client = { version = "0.33", features = [
|
||||||
|
"compression",
|
||||||
|
"ephemeral-keystore",
|
||||||
|
"experimental-api",
|
||||||
|
"onion-service-client",
|
||||||
|
"onion-service-service",
|
||||||
|
"rustls",
|
||||||
|
"static",
|
||||||
|
"tokio",
|
||||||
|
], default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
async-acme = { version = "0.6.0", git = "https://github.com/dr-bonez/async-acme.git", features = [
|
async-acme = { version = "0.6.0", git = "https://github.com/dr-bonez/async-acme.git", features = [
|
||||||
"use_rustls",
|
"use_rustls",
|
||||||
"use_tokio",
|
"use_tokio",
|
||||||
] }
|
] }
|
||||||
async-compression = { version = "0.4.4", features = [
|
async-compression = { version = "0.4.32", features = [
|
||||||
"gzip",
|
|
||||||
"brotli",
|
"brotli",
|
||||||
|
"gzip",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"zstd",
|
||||||
] }
|
] }
|
||||||
async-stream = "0.3.5"
|
async-stream = "0.3.5"
|
||||||
async-trait = "0.1.74"
|
async-trait = "0.1.74"
|
||||||
axum = { version = "0.7.3", features = ["ws"] }
|
axum = { version = "0.8.4", features = ["ws", "http2"] }
|
||||||
barrage = "0.2.3"
|
backtrace-on-stack-overflow = { version = "0.3.0", optional = true }
|
||||||
backhand = "0.18.0"
|
|
||||||
base32 = "0.5.0"
|
base32 = "0.5.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
base64ct = "1.6.0"
|
base64ct = "1.6.0"
|
||||||
@@ -73,21 +94,24 @@ bytes = "1"
|
|||||||
chrono = { version = "0.4.31", features = ["serde"] }
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
clap = { version = "4.4.12", features = ["string"] }
|
clap = { version = "4.4.12", features = ["string"] }
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
console = "0.15.7"
|
console = "0.16.2"
|
||||||
console-subscriber = { version = "0.3.0", optional = true }
|
console-subscriber = { version = "0.5.0", optional = true }
|
||||||
const_format = "0.2.34"
|
const_format = "0.2.34"
|
||||||
cookie = "0.18.0"
|
cookie = "0.18.0"
|
||||||
cookie_store = "0.21.0"
|
cookie_store = "0.22.0"
|
||||||
|
curve25519-dalek = "4.1.3"
|
||||||
der = { version = "0.7.9", features = ["derive", "pem"] }
|
der = { version = "0.7.9", features = ["derive", "pem"] }
|
||||||
digest = "0.10.7"
|
digest = "0.10.7"
|
||||||
divrem = "1.0.0"
|
divrem = "1.0.0"
|
||||||
ed25519 = { version = "2.2.3", features = ["pkcs8", "pem", "alloc"] }
|
dns-lookup = "3.0.1"
|
||||||
ed25519-dalek = { version = "2.1.1", features = [
|
ed25519 = { version = "2.2.3", features = ["alloc", "pem", "pkcs8"] }
|
||||||
|
ed25519-dalek = { version = "2.2.0", features = [
|
||||||
|
"digest",
|
||||||
|
"hazmat",
|
||||||
|
"pkcs8",
|
||||||
|
"rand_core",
|
||||||
"serde",
|
"serde",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
"rand_core",
|
|
||||||
"digest",
|
|
||||||
"pkcs8",
|
|
||||||
] }
|
] }
|
||||||
ed25519-dalek-v1 = { package = "ed25519-dalek", version = "1" }
|
ed25519-dalek-v1 = { package = "ed25519-dalek", version = "1" }
|
||||||
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
||||||
@@ -96,50 +120,61 @@ exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git",
|
|||||||
fd-lock-rs = "0.1.4"
|
fd-lock-rs = "0.1.4"
|
||||||
form_urlencoded = "1.2.1"
|
form_urlencoded = "1.2.1"
|
||||||
futures = "0.3.28"
|
futures = "0.3.28"
|
||||||
gpt = "3.1.0"
|
gpt = "4.1.0"
|
||||||
helpers = { path = "../helpers" }
|
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
hickory-client = "0.25.2"
|
||||||
|
hickory-server = "0.25.2"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
http = "1.0.0"
|
http = "1.0.0"
|
||||||
http-body-util = "0.1"
|
http-body-util = "0.1"
|
||||||
hyper = { version = "1.5", features = ["server", "http1", "http2"] }
|
hyper = { version = "1.5", features = ["http1", "http2", "server"] }
|
||||||
hyper-util = { version = "0.1.10", features = [
|
hyper-util = { version = "0.1.10", features = [
|
||||||
|
"http1",
|
||||||
|
"http2",
|
||||||
"server",
|
"server",
|
||||||
"server-auto",
|
"server-auto",
|
||||||
"server-graceful",
|
"server-graceful",
|
||||||
"service",
|
"service",
|
||||||
"http1",
|
|
||||||
"http2",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
] }
|
] }
|
||||||
id-pool = { version = "0.2.2", default-features = false, features = [
|
id-pool = { version = "0.2.2", default-features = false, features = [
|
||||||
"serde",
|
"serde",
|
||||||
"u16",
|
"u16",
|
||||||
] }
|
] }
|
||||||
imbl = "2.0.3"
|
iddqd = "0.3.14"
|
||||||
imbl-value = "0.1.2"
|
imbl = { version = "6", features = ["serde", "small-chunks"] }
|
||||||
|
imbl-value = { version = "0.4.3", features = ["ts-rs"] }
|
||||||
include_dir = { version = "0.7.3", features = ["metadata"] }
|
include_dir = { version = "0.7.3", features = ["metadata"] }
|
||||||
indexmap = { version = "2.0.2", features = ["serde"] }
|
indexmap = { version = "2.0.2", features = ["serde"] }
|
||||||
indicatif = { version = "0.17.7", features = ["tokio"] }
|
indicatif = { version = "0.18.3", features = ["tokio"] }
|
||||||
|
inotify = "0.11.0"
|
||||||
integer-encoding = { version = "4.0.0", features = ["tokio_async"] }
|
integer-encoding = { version = "4.0.0", features = ["tokio_async"] }
|
||||||
ipnet = { version = "2.8.0", features = ["serde"] }
|
ipnet = { version = "2.8.0", features = ["serde"] }
|
||||||
iprange = { version = "0.6.7", features = ["serde"] }
|
|
||||||
isocountry = "0.3.2"
|
isocountry = "0.3.2"
|
||||||
itertools = "0.13.0"
|
itertools = "0.14.0"
|
||||||
jaq-core = "0.10.1"
|
jaq-core = "0.10.1"
|
||||||
jaq-std = "0.10.0"
|
jaq-std = "0.10.0"
|
||||||
josekit = "0.8.4"
|
josekit = "0.10.3"
|
||||||
jsonpath_lib = { git = "https://github.com/Start9Labs/jsonpath.git" }
|
jsonpath_lib = { git = "https://github.com/Start9Labs/jsonpath.git" }
|
||||||
lazy_async_pool = "0.3.3"
|
lazy_async_pool = "0.3.3"
|
||||||
lazy_format = "2.0"
|
lazy_format = "2.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
lettre = { version = "0.11.18", default-features = false, features = [
|
||||||
|
"aws-lc-rs",
|
||||||
|
"builder",
|
||||||
|
"hostname",
|
||||||
|
"pool",
|
||||||
|
"rustls-platform-verifier",
|
||||||
|
"smtp-transport",
|
||||||
|
"tokio1-rustls",
|
||||||
|
] }
|
||||||
libc = "0.2.149"
|
libc = "0.2.149"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
|
mbrman = "0.6.0"
|
||||||
|
miette = { version = "7.6.0", features = ["fancy"] }
|
||||||
mio = "1"
|
mio = "1"
|
||||||
mbrman = "0.5.2"
|
|
||||||
models = { version = "*", path = "../models" }
|
|
||||||
new_mime_guess = "4"
|
new_mime_guess = "4"
|
||||||
nix = { version = "0.29.0", features = [
|
nix = { version = "0.30.1", features = [
|
||||||
"fs",
|
"fs",
|
||||||
"mount",
|
"mount",
|
||||||
"net",
|
"net",
|
||||||
@@ -148,10 +183,10 @@ nix = { version = "0.29.0", features = [
|
|||||||
"signal",
|
"signal",
|
||||||
"user",
|
"user",
|
||||||
] }
|
] }
|
||||||
nom = "7.1.3"
|
nom = "8.0.0"
|
||||||
num = "0.4.1"
|
num = "0.4.1"
|
||||||
num_enum = "0.7.0"
|
|
||||||
num_cpus = "1.16.0"
|
num_cpus = "1.16.0"
|
||||||
|
num_enum = "0.7.0"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
openssh-keys = "0.6.2"
|
openssh-keys = "0.6.2"
|
||||||
openssl = { version = "0.10.57", features = ["vendored"] }
|
openssl = { version = "0.10.57", features = ["vendored"] }
|
||||||
@@ -163,70 +198,83 @@ pbkdf2 = "0.12.2"
|
|||||||
pin-project = "1.1.3"
|
pin-project = "1.1.3"
|
||||||
pkcs8 = { version = "0.10.2", features = ["std"] }
|
pkcs8 = { version = "0.10.2", features = ["std"] }
|
||||||
prettytable-rs = "0.10.0"
|
prettytable-rs = "0.10.0"
|
||||||
procfs = { version = "0.16.0", optional = true }
|
|
||||||
proptest = "1.3.1"
|
proptest = "1.3.1"
|
||||||
proptest-derive = "0.5.0"
|
proptest-derive = "0.7.0"
|
||||||
qrcode = "0.14.1"
|
qrcode = "0.14.1"
|
||||||
rand = "0.9.0"
|
r3bl_tui = "0.7.6"
|
||||||
|
rand = "0.9.2"
|
||||||
regex = "1.10.2"
|
regex = "1.10.2"
|
||||||
reqwest = { version = "0.12.4", features = ["stream", "json", "socks"] }
|
reqwest = { version = "0.12.25", features = [
|
||||||
reqwest_cookie_store = "0.8.0"
|
"json",
|
||||||
|
"socks",
|
||||||
|
"stream",
|
||||||
|
"http2",
|
||||||
|
] }
|
||||||
|
reqwest_cookie_store = "0.9.0"
|
||||||
rpassword = "7.2.0"
|
rpassword = "7.2.0"
|
||||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" }
|
rust-argon2 = "3.0.0"
|
||||||
rust-argon2 = "2.0.0"
|
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git" }
|
||||||
rustyline-async = "0.4.1"
|
safelog = { version = "0.4.8", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
semver = { version = "1.0.20", features = ["serde"] }
|
semver = { version = "1.0.20", features = ["serde"] }
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
serde_cbor = { package = "ciborium", version = "0.2.1" }
|
serde_cbor = { package = "ciborium", version = "0.2.1" }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_toml = { package = "toml", version = "0.8.2" }
|
serde_toml = { package = "toml", version = "0.9.9+spec-1.0.0" }
|
||||||
serde_urlencoded = "0.7"
|
serde_yaml = { package = "serde_yml", version = "0.0.12" }
|
||||||
serde_with = { version = "3.4.0", features = ["macros", "json"] }
|
sha-crypt = "0.5.0"
|
||||||
serde_yaml = { package = "serde_yml", version = "0.0.10" }
|
|
||||||
sha2 = "0.10.2"
|
sha2 = "0.10.2"
|
||||||
shell-words = "1"
|
|
||||||
signal-hook = "0.3.17"
|
signal-hook = "0.3.17"
|
||||||
simple-logging = "2.0.2"
|
socket2 = { version = "0.6.0", features = ["all"] }
|
||||||
socket2 = "0.5.7"
|
socks5-impl = { version = "0.7.2", features = ["client", "server"] }
|
||||||
sqlx = { version = "0.7.2", features = [
|
sqlx = { version = "0.8.6", features = [
|
||||||
"chrono",
|
|
||||||
"runtime-tokio-rustls",
|
|
||||||
"postgres",
|
"postgres",
|
||||||
] }
|
"runtime-tokio-rustls",
|
||||||
|
], default-features = false }
|
||||||
sscanf = "0.4.1"
|
sscanf = "0.4.1"
|
||||||
ssh-key = { version = "0.6.2", features = ["ed25519"] }
|
ssh-key = { version = "0.6.2", features = ["ed25519"] }
|
||||||
tar = "0.4.40"
|
tar = "0.4.40"
|
||||||
thiserror = "1.0.49"
|
termion = "4.0.5"
|
||||||
textwrap = "0.16.1"
|
textwrap = "0.16.1"
|
||||||
|
thiserror = "2.0.12"
|
||||||
tokio = { version = "1.38.1", features = ["full"] }
|
tokio = { version = "1.38.1", features = ["full"] }
|
||||||
tokio-rustls = "0.26.0"
|
tokio-rustls = "0.26.4"
|
||||||
tokio-socks = "0.5.1"
|
tokio-stream = { version = "0.1.14", features = ["io-util", "net", "sync"] }
|
||||||
tokio-stream = { version = "0.1.14", features = ["io-util", "sync", "net"] }
|
|
||||||
tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" }
|
tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" }
|
||||||
tokio-tungstenite = { version = "0.23.1", features = ["native-tls", "url"] }
|
tokio-tungstenite = { version = "0.26.2", features = ["native-tls", "url"] }
|
||||||
tokio-util = { version = "0.7.9", features = ["io"] }
|
tokio-util = { version = "0.7.9", features = ["io"] }
|
||||||
torut = { git = "https://github.com/Start9Labs/torut.git", branch = "update/dependencies", features = [
|
tor-cell = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
"serialize",
|
tor-hscrypto = { version = "0.33", features = [
|
||||||
] }
|
"full",
|
||||||
|
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
|
tor-hsservice = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
|
tor-keymgr = { version = "0.33", features = [
|
||||||
|
"ephemeral-keystore",
|
||||||
|
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
|
tor-llcrypto = { version = "0.33", features = [
|
||||||
|
"full",
|
||||||
|
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
|
tor-proto = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
|
tor-rtcompat = { version = "0.33", features = [
|
||||||
|
"rustls",
|
||||||
|
"tokio",
|
||||||
|
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
|
torut = "0.2.1"
|
||||||
tower-service = "0.3.3"
|
tower-service = "0.3.3"
|
||||||
tracing = "0.1.39"
|
tracing = "0.1.39"
|
||||||
tracing-error = "0.2.0"
|
tracing-error = "0.2.0"
|
||||||
tracing-futures = "0.2.5"
|
|
||||||
tracing-journald = "0.3.0"
|
tracing-journald = "0.3.0"
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
trust-dns-server = "0.23.1"
|
ts-rs = "9.0.1"
|
||||||
ts-rs = { git = "https://github.com/dr-bonez/ts-rs.git", branch = "feature/top-level-as" } # "8.1.0"
|
typed-builder = "0.23.2"
|
||||||
tty-spawn = { version = "0.4.0", optional = true }
|
|
||||||
typed-builder = "0.18.0"
|
|
||||||
unix-named-pipe = "0.2.0"
|
|
||||||
url = { version = "2.4.1", features = ["serde"] }
|
url = { version = "2.4.1", features = ["serde"] }
|
||||||
urlencoding = "2.1.3"
|
|
||||||
uuid = { version = "1.4.1", features = ["v4"] }
|
uuid = { version = "1.4.1", features = ["v4"] }
|
||||||
|
visit-rs = "0.1.1"
|
||||||
|
x25519-dalek = { version = "2.0.1", features = ["static_secrets"] }
|
||||||
zbus = "5.1.1"
|
zbus = "5.1.1"
|
||||||
zeroize = "1.6.0"
|
|
||||||
mail-send = { git = "https://github.com/dr-bonez/mail-send.git", branch = "main", optional = true }
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
rustls = "0.23.20"
|
procfs = "0.18.0"
|
||||||
rustls-pki-types = { version = "1.10.1", features = ["alloc"] }
|
pty-process = "0.5.1"
|
||||||
|
|
||||||
[profile.test]
|
[profile.test]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use imbl_value::InternedString;
|
||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
use openssl::x509::X509;
|
use openssl::x509::X509;
|
||||||
use torut::onion::TorSecretKeyV3;
|
|
||||||
|
|
||||||
use crate::db::model::DatabaseModel;
|
use crate::db::model::DatabaseModel;
|
||||||
use crate::hostname::{generate_hostname, generate_id, Hostname};
|
use crate::hostname::{Hostname, generate_hostname, generate_id};
|
||||||
use crate::net::ssl::{generate_key, make_root_cert};
|
use crate::net::ssl::{gen_nistp256, make_root_cert};
|
||||||
|
use crate::net::tor::TorSecretKey;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
|
|
||||||
@@ -19,28 +21,28 @@ fn hash_password(password: &str) -> Result<String, Error> {
|
|||||||
.with_kind(crate::ErrorKind::PasswordHashGeneration)
|
.with_kind(crate::ErrorKind::PasswordHashGeneration)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AccountInfo {
|
pub struct AccountInfo {
|
||||||
pub server_id: String,
|
pub server_id: String,
|
||||||
pub hostname: Hostname,
|
pub hostname: Hostname,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub tor_keys: Vec<TorSecretKeyV3>,
|
pub tor_keys: Vec<TorSecretKey>,
|
||||||
pub root_ca_key: PKey<Private>,
|
pub root_ca_key: PKey<Private>,
|
||||||
pub root_ca_cert: X509,
|
pub root_ca_cert: X509,
|
||||||
pub ssh_key: ssh_key::PrivateKey,
|
pub ssh_key: ssh_key::PrivateKey,
|
||||||
pub compat_s9pk_key: ed25519_dalek::SigningKey,
|
pub developer_key: ed25519_dalek::SigningKey,
|
||||||
}
|
}
|
||||||
impl AccountInfo {
|
impl AccountInfo {
|
||||||
pub fn new(password: &str, start_time: SystemTime) -> Result<Self, Error> {
|
pub fn new(password: &str, start_time: SystemTime) -> Result<Self, Error> {
|
||||||
let server_id = generate_id();
|
let server_id = generate_id();
|
||||||
let hostname = generate_hostname();
|
let hostname = generate_hostname();
|
||||||
let tor_key = vec![TorSecretKeyV3::generate()];
|
let tor_key = vec![TorSecretKey::generate()];
|
||||||
let root_ca_key = generate_key()?;
|
let root_ca_key = gen_nistp256()?;
|
||||||
let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?;
|
let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?;
|
||||||
let ssh_key = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::random(
|
let ssh_key = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::random(
|
||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
));
|
));
|
||||||
let compat_s9pk_key =
|
let developer_key =
|
||||||
ed25519_dalek::SigningKey::generate(&mut ssh_key::rand_core::OsRng::default());
|
ed25519_dalek::SigningKey::generate(&mut ssh_key::rand_core::OsRng::default());
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
server_id,
|
server_id,
|
||||||
@@ -50,7 +52,7 @@ impl AccountInfo {
|
|||||||
root_ca_key,
|
root_ca_key,
|
||||||
root_ca_cert,
|
root_ca_cert,
|
||||||
ssh_key,
|
ssh_key,
|
||||||
compat_s9pk_key,
|
developer_key,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +61,13 @@ impl AccountInfo {
|
|||||||
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
let hostname = Hostname(db.as_public().as_server_info().as_hostname().de()?);
|
||||||
let password = db.as_private().as_password().de()?;
|
let password = db.as_private().as_password().de()?;
|
||||||
let key_store = db.as_private().as_key_store();
|
let key_store = db.as_private().as_key_store();
|
||||||
let tor_addrs = db.as_public().as_server_info().as_host().as_onions().de()?;
|
let tor_addrs = db
|
||||||
|
.as_public()
|
||||||
|
.as_server_info()
|
||||||
|
.as_network()
|
||||||
|
.as_host()
|
||||||
|
.as_onions()
|
||||||
|
.de()?;
|
||||||
let tor_keys = tor_addrs
|
let tor_keys = tor_addrs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tor_addr| key_store.as_onion().get_key(&tor_addr))
|
.map(|tor_addr| key_store.as_onion().get_key(&tor_addr))
|
||||||
@@ -68,7 +76,7 @@ impl AccountInfo {
|
|||||||
let root_ca_key = cert_store.as_root_key().de()?.0;
|
let root_ca_key = cert_store.as_root_key().de()?.0;
|
||||||
let root_ca_cert = cert_store.as_root_cert().de()?.0;
|
let root_ca_cert = cert_store.as_root_cert().de()?.0;
|
||||||
let ssh_key = db.as_private().as_ssh_privkey().de()?.0;
|
let ssh_key = db.as_private().as_ssh_privkey().de()?.0;
|
||||||
let compat_s9pk_key = db.as_private().as_compat_s9pk_key().de()?.0;
|
let compat_s9pk_key = db.as_private().as_developer_key().de()?.0;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
server_id,
|
server_id,
|
||||||
@@ -78,7 +86,7 @@ impl AccountInfo {
|
|||||||
root_ca_key,
|
root_ca_key,
|
||||||
root_ca_cert,
|
root_ca_cert,
|
||||||
ssh_key,
|
ssh_key,
|
||||||
compat_s9pk_key,
|
developer_key: compat_s9pk_key,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,31 +97,44 @@ impl AccountInfo {
|
|||||||
server_info
|
server_info
|
||||||
.as_pubkey_mut()
|
.as_pubkey_mut()
|
||||||
.ser(&self.ssh_key.public_key().to_openssh()?)?;
|
.ser(&self.ssh_key.public_key().to_openssh()?)?;
|
||||||
server_info.as_host_mut().as_onions_mut().ser(
|
server_info
|
||||||
&self
|
.as_network_mut()
|
||||||
.tor_keys
|
.as_host_mut()
|
||||||
.iter()
|
.as_onions_mut()
|
||||||
.map(|tor_key| tor_key.public().get_onion_address())
|
.ser(
|
||||||
.collect(),
|
&self
|
||||||
)?;
|
.tor_keys
|
||||||
|
.iter()
|
||||||
|
.map(|tor_key| tor_key.onion_address())
|
||||||
|
.collect(),
|
||||||
|
)?;
|
||||||
|
server_info.as_password_hash_mut().ser(&self.password)?;
|
||||||
db.as_private_mut().as_password_mut().ser(&self.password)?;
|
db.as_private_mut().as_password_mut().ser(&self.password)?;
|
||||||
db.as_private_mut()
|
db.as_private_mut()
|
||||||
.as_ssh_privkey_mut()
|
.as_ssh_privkey_mut()
|
||||||
.ser(Pem::new_ref(&self.ssh_key))?;
|
.ser(Pem::new_ref(&self.ssh_key))?;
|
||||||
db.as_private_mut()
|
db.as_private_mut()
|
||||||
.as_compat_s9pk_key_mut()
|
.as_developer_key_mut()
|
||||||
.ser(Pem::new_ref(&self.compat_s9pk_key))?;
|
.ser(Pem::new_ref(&self.developer_key))?;
|
||||||
let key_store = db.as_private_mut().as_key_store_mut();
|
let key_store = db.as_private_mut().as_key_store_mut();
|
||||||
for tor_key in &self.tor_keys {
|
for tor_key in &self.tor_keys {
|
||||||
key_store.as_onion_mut().insert_key(tor_key)?;
|
key_store.as_onion_mut().insert_key(tor_key)?;
|
||||||
}
|
}
|
||||||
let cert_store = key_store.as_local_certs_mut();
|
let cert_store = key_store.as_local_certs_mut();
|
||||||
cert_store
|
if cert_store.as_root_cert().de()?.0 != self.root_ca_cert {
|
||||||
.as_root_key_mut()
|
cert_store
|
||||||
.ser(Pem::new_ref(&self.root_ca_key))?;
|
.as_root_key_mut()
|
||||||
cert_store
|
.ser(Pem::new_ref(&self.root_ca_key))?;
|
||||||
.as_root_cert_mut()
|
cert_store
|
||||||
.ser(Pem::new_ref(&self.root_ca_cert))?;
|
.as_root_cert_mut()
|
||||||
|
.ser(Pem::new_ref(&self.root_ca_cert))?;
|
||||||
|
let int_key = crate::net::ssl::gen_nistp256()?;
|
||||||
|
let int_cert =
|
||||||
|
crate::net::ssl::make_int_cert((&self.root_ca_key, &self.root_ca_cert), &int_key)?;
|
||||||
|
cert_store.as_int_key_mut().ser(&Pem(int_key))?;
|
||||||
|
cert_store.as_int_cert_mut().ser(&Pem(int_cert))?;
|
||||||
|
cert_store.as_leaves_mut().ser(&BTreeMap::new())?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,4 +142,17 @@ impl AccountInfo {
|
|||||||
self.password = hash_password(password)?;
|
self.password = hash_password(password)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hostnames(&self) -> impl IntoIterator<Item = InternedString> + Send + '_ {
|
||||||
|
[
|
||||||
|
self.hostname.no_dot_host_name(),
|
||||||
|
self.hostname.local_domain_name(),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.chain(
|
||||||
|
self.tor_keys
|
||||||
|
.iter()
|
||||||
|
.map(|k| InternedString::from_display(&k.onion_address())),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use clap::{CommandFactory, FromArgMatches, Parser};
|
use clap::{CommandFactory, FromArgMatches, Parser};
|
||||||
pub use models::ActionId;
|
|
||||||
use models::PackageId;
|
|
||||||
use qrcode::QrCode;
|
use qrcode::QrCode;
|
||||||
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
|
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
pub use crate::ActionId;
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
|
use crate::db::model::package::TaskSeverity;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::rpc_continuations::Guid;
|
use crate::rpc_continuations::Guid;
|
||||||
use crate::util::serde::{
|
use crate::util::serde::{
|
||||||
display_serializable, HandlerExtSerde, StdinDeserializable, WithIoFormat,
|
HandlerExtSerde, StdinDeserializable, WithIoFormat, display_serializable,
|
||||||
};
|
};
|
||||||
|
use crate::{PackageId, ReplayId};
|
||||||
|
|
||||||
pub fn action_api<C: Context>() -> ParentHandler<C> {
|
pub fn action_api<C: Context>() -> ParentHandler<C> {
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
@@ -38,12 +39,21 @@ pub fn action_api<C: Context>() -> ParentHandler<C> {
|
|||||||
.with_about("Run service action")
|
.with_about("Run service action")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
"clear-task",
|
||||||
|
from_fn_async(clear_task)
|
||||||
|
.no_display()
|
||||||
|
.with_about("Clear a service task")
|
||||||
|
.with_call_remote::<CliContext>(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ActionInput {
|
pub struct ActionInput {
|
||||||
|
#[serde(default)]
|
||||||
|
pub event_id: Guid,
|
||||||
#[ts(type = "Record<string, unknown>")]
|
#[ts(type = "Record<string, unknown>")]
|
||||||
pub spec: Value,
|
pub spec: Value,
|
||||||
#[ts(type = "Record<string, unknown> | null")]
|
#[ts(type = "Record<string, unknown> | null")]
|
||||||
@@ -83,6 +93,28 @@ pub enum ActionResult {
|
|||||||
#[serde(rename = "1")]
|
#[serde(rename = "1")]
|
||||||
V1(ActionResultV1),
|
V1(ActionResultV1),
|
||||||
}
|
}
|
||||||
|
impl ActionResult {
|
||||||
|
pub fn upcast(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::V0(ActionResultV0 {
|
||||||
|
message,
|
||||||
|
value,
|
||||||
|
copyable,
|
||||||
|
qr,
|
||||||
|
}) => Self::V1(ActionResultV1 {
|
||||||
|
title: "Action Complete".into(),
|
||||||
|
message: Some(message),
|
||||||
|
result: value.map(|value| ActionResultValue::Single {
|
||||||
|
value,
|
||||||
|
copyable,
|
||||||
|
qr,
|
||||||
|
masked: false,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
Self::V1(a) => Self::V1(a),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
impl fmt::Display for ActionResult {
|
impl fmt::Display for ActionResult {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@@ -222,20 +254,25 @@ impl fmt::Display for ActionResultV1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_action_result<T: Serialize>(params: WithIoFormat<T>, result: Option<ActionResult>) {
|
pub fn display_action_result<T: Serialize>(
|
||||||
|
params: WithIoFormat<T>,
|
||||||
|
result: Option<ActionResult>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let Some(result) = result else {
|
let Some(result) = result else {
|
||||||
return;
|
return Ok(());
|
||||||
};
|
};
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
return display_serializable(format, result);
|
return display_serializable(format, result);
|
||||||
}
|
}
|
||||||
println!("{result}")
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, TS)]
|
#[derive(Deserialize, Serialize, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RunActionParams {
|
pub struct RunActionParams {
|
||||||
pub package_id: PackageId,
|
pub package_id: PackageId,
|
||||||
|
pub event_id: Option<Guid>,
|
||||||
pub action_id: ActionId,
|
pub action_id: ActionId,
|
||||||
#[ts(optional, type = "any")]
|
#[ts(optional, type = "any")]
|
||||||
pub input: Option<Value>,
|
pub input: Option<Value>,
|
||||||
@@ -244,6 +281,7 @@ pub struct RunActionParams {
|
|||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
struct CliRunActionParams {
|
struct CliRunActionParams {
|
||||||
pub package_id: PackageId,
|
pub package_id: PackageId,
|
||||||
|
pub event_id: Option<Guid>,
|
||||||
pub action_id: ActionId,
|
pub action_id: ActionId,
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub input: StdinDeserializable<Option<Value>>,
|
pub input: StdinDeserializable<Option<Value>>,
|
||||||
@@ -252,12 +290,14 @@ impl From<CliRunActionParams> for RunActionParams {
|
|||||||
fn from(
|
fn from(
|
||||||
CliRunActionParams {
|
CliRunActionParams {
|
||||||
package_id,
|
package_id,
|
||||||
|
event_id,
|
||||||
action_id,
|
action_id,
|
||||||
input,
|
input,
|
||||||
}: CliRunActionParams,
|
}: CliRunActionParams,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
package_id,
|
package_id,
|
||||||
|
event_id,
|
||||||
action_id,
|
action_id,
|
||||||
input: input.0,
|
input: input.0,
|
||||||
}
|
}
|
||||||
@@ -297,6 +337,7 @@ pub async fn run_action(
|
|||||||
ctx: RpcContext,
|
ctx: RpcContext,
|
||||||
RunActionParams {
|
RunActionParams {
|
||||||
package_id,
|
package_id,
|
||||||
|
event_id,
|
||||||
action_id,
|
action_id,
|
||||||
input,
|
input,
|
||||||
}: RunActionParams,
|
}: RunActionParams,
|
||||||
@@ -306,6 +347,54 @@ pub async fn run_action(
|
|||||||
.await
|
.await
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.or_not_found(lazy_format!("Manager for {}", package_id))?
|
.or_not_found(lazy_format!("Manager for {}", package_id))?
|
||||||
.run_action(Guid::new(), action_id, input.unwrap_or_default())
|
.run_action(
|
||||||
|
event_id.unwrap_or_default(),
|
||||||
|
action_id,
|
||||||
|
input.unwrap_or_default(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
|
.map(|res| res.map(ActionResult::upcast))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[command(rename_all = "kebab-case")]
|
||||||
|
pub struct ClearTaskParams {
|
||||||
|
pub package_id: PackageId,
|
||||||
|
pub replay_id: ReplayId,
|
||||||
|
#[arg(long)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub force: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn clear_task(
|
||||||
|
ctx: RpcContext,
|
||||||
|
ClearTaskParams {
|
||||||
|
package_id,
|
||||||
|
replay_id,
|
||||||
|
force,
|
||||||
|
}: ClearTaskParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
ctx.db
|
||||||
|
.mutate(|db| {
|
||||||
|
if let Some(task) = db
|
||||||
|
.as_public_mut()
|
||||||
|
.as_package_data_mut()
|
||||||
|
.as_idx_mut(&package_id)
|
||||||
|
.or_not_found(&package_id)?
|
||||||
|
.as_tasks_mut()
|
||||||
|
.remove(&replay_id)?
|
||||||
|
{
|
||||||
|
if !force && task.as_task().as_severity().de()? == TaskSeverity::Critical {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("Cannot clear critical task"),
|
||||||
|
ErrorKind::InvalidRequest,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,27 +3,27 @@ use std::collections::BTreeMap;
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use imbl_value::{json, InternedString};
|
use imbl_value::{InternedString, json};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler};
|
use rpc_toolkit::{CallRemote, Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::DatabaseModel;
|
use crate::middleware::auth::session::{
|
||||||
use crate::middleware::auth::{
|
AsLogoutSessionId, HasLoggedOutSessions, HashSessionToken, LoginRes, SessionAuthContext,
|
||||||
AsLogoutSessionId, HasLoggedOutSessions, HashSessionToken, LoginRes,
|
|
||||||
};
|
};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::crypto::EncryptedWire;
|
use crate::util::crypto::EncryptedWire;
|
||||||
use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat};
|
use crate::util::io::create_file_mod;
|
||||||
use crate::{ensure_code, Error, ResultExt};
|
use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable};
|
||||||
|
use crate::{Error, ResultExt, ensure_code};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, TS)]
|
#[derive(Debug, Clone, Default, Deserialize, Serialize, TS)]
|
||||||
#[ts(as = "BTreeMap::<String, Session>")]
|
|
||||||
pub struct Sessions(pub BTreeMap<InternedString, Session>);
|
pub struct Sessions(pub BTreeMap<InternedString, Session>);
|
||||||
impl Sessions {
|
impl Sessions {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@@ -41,6 +41,33 @@ impl Map for Sessions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn write_shadow(password: &str) -> Result<(), Error> {
|
||||||
|
let hash: String = sha_crypt::sha512_simple(password, &sha_crypt::Sha512Params::default())
|
||||||
|
.map_err(|e| Error::new(eyre!("{e:?}"), ErrorKind::Serialization))?;
|
||||||
|
let shadow_contents = tokio::fs::read_to_string("/etc/shadow").await?;
|
||||||
|
let mut shadow_file =
|
||||||
|
create_file_mod("/media/startos/config/overlay/etc/shadow", 0o640).await?;
|
||||||
|
for line in shadow_contents.lines() {
|
||||||
|
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)
|
||||||
|
})?;
|
||||||
|
shadow_file
|
||||||
|
.write_all(format!("{user}:{hash}:{rest}\n").as_bytes())
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
shadow_file.write_all(line.as_bytes()).await?;
|
||||||
|
shadow_file.write_all(b"\n").await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shadow_file.sync_all().await?;
|
||||||
|
tokio::fs::copy("/media/startos/config/overlay/etc/shadow", "/etc/shadow").await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, TS)]
|
#[derive(Clone, Serialize, Deserialize, TS)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@@ -83,31 +110,34 @@ impl std::str::FromStr for PasswordType {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn auth<C: Context>() -> ParentHandler<C> {
|
pub fn auth<C: Context, AC: SessionAuthContext>() -> ParentHandler<C>
|
||||||
|
where
|
||||||
|
CliContext: CallRemote<AC>,
|
||||||
|
{
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"login",
|
"login",
|
||||||
from_fn_async(login_impl)
|
from_fn_async(login_impl::<AC>)
|
||||||
.with_metadata("login", Value::Bool(true))
|
.with_metadata("login", Value::Bool(true))
|
||||||
.no_cli(),
|
.no_cli(),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"login",
|
"login",
|
||||||
from_fn_async(cli_login)
|
from_fn_async(cli_login::<AC>)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Log in to StartOS server"),
|
.with_about("Log in a new auth session"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"logout",
|
"logout",
|
||||||
from_fn_async(logout)
|
from_fn_async(logout::<AC>)
|
||||||
.with_metadata("get_session", Value::Bool(true))
|
.with_metadata("get_session", Value::Bool(true))
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Log out of StartOS server")
|
.with_about("Log out of current auth session")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"session",
|
"session",
|
||||||
session::<C>().with_about("List or kill StartOS sessions"),
|
session::<C, AC>().with_about("List or kill auth sessions"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"reset-password",
|
"reset-password",
|
||||||
@@ -117,7 +147,7 @@ pub fn auth<C: Context>() -> ParentHandler<C> {
|
|||||||
"reset-password",
|
"reset-password",
|
||||||
from_fn_async(cli_reset_password)
|
from_fn_async(cli_reset_password)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Reset StartOS password"),
|
.with_about("Reset password"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"get-pubkey",
|
"get-pubkey",
|
||||||
@@ -143,17 +173,20 @@ fn gen_pwd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn cli_login(
|
async fn cli_login<C: SessionAuthContext>(
|
||||||
HandlerArgs {
|
HandlerArgs {
|
||||||
context: ctx,
|
context: ctx,
|
||||||
parent_method,
|
parent_method,
|
||||||
method,
|
method,
|
||||||
..
|
..
|
||||||
}: HandlerArgs<CliContext>,
|
}: HandlerArgs<CliContext>,
|
||||||
) -> Result<(), RpcError> {
|
) -> Result<(), RpcError>
|
||||||
|
where
|
||||||
|
CliContext: CallRemote<C>,
|
||||||
|
{
|
||||||
let password = rpassword::prompt_password("Password: ")?;
|
let password = rpassword::prompt_password("Password: ")?;
|
||||||
|
|
||||||
ctx.call_remote::<RpcContext>(
|
ctx.call_remote::<C>(
|
||||||
&parent_method.into_iter().chain(method).join("."),
|
&parent_method.into_iter().chain(method).join("."),
|
||||||
json!({
|
json!({
|
||||||
"password": password,
|
"password": password,
|
||||||
@@ -181,66 +214,52 @@ pub fn check_password(hash: &str, password: &str) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_password_against_db(db: &DatabaseModel, password: &str) -> Result<(), Error> {
|
|
||||||
let pw_hash = db.as_private().as_password().de()?;
|
|
||||||
check_password(&pw_hash, password)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, TS)]
|
#[derive(Deserialize, Serialize, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct LoginParams {
|
pub struct LoginParams {
|
||||||
password: Option<PasswordType>,
|
password: String,
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_userAgent")] // from Auth middleware
|
#[serde(rename = "__Auth_userAgent")] // from Auth middleware
|
||||||
user_agent: Option<String>,
|
user_agent: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
ephemeral: bool,
|
ephemeral: bool,
|
||||||
#[serde(default)]
|
|
||||||
#[ts(type = "any")]
|
|
||||||
metadata: Value,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn login_impl(
|
pub async fn login_impl<C: SessionAuthContext>(
|
||||||
ctx: RpcContext,
|
ctx: C,
|
||||||
LoginParams {
|
LoginParams {
|
||||||
password,
|
password,
|
||||||
user_agent,
|
user_agent,
|
||||||
ephemeral,
|
ephemeral,
|
||||||
metadata,
|
|
||||||
}: LoginParams,
|
}: LoginParams,
|
||||||
) -> Result<LoginRes, Error> {
|
) -> Result<LoginRes, Error> {
|
||||||
let password = password.unwrap_or_default().decrypt(&ctx)?;
|
let tok = if ephemeral {
|
||||||
|
C::check_password(&ctx.db().peek().await, &password)?;
|
||||||
if ephemeral {
|
|
||||||
check_password_against_db(&ctx.db.peek().await, &password)?;
|
|
||||||
let hash_token = HashSessionToken::new();
|
let hash_token = HashSessionToken::new();
|
||||||
ctx.ephemeral_sessions.mutate(|s| {
|
ctx.ephemeral_sessions().mutate(|s| {
|
||||||
s.0.insert(
|
s.0.insert(
|
||||||
hash_token.hashed().clone(),
|
hash_token.hashed().clone(),
|
||||||
Session {
|
Session {
|
||||||
logged_in: Utc::now(),
|
logged_in: Utc::now(),
|
||||||
last_active: Utc::now(),
|
last_active: Utc::now(),
|
||||||
user_agent,
|
user_agent,
|
||||||
metadata,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
Ok(hash_token.to_login_res())
|
Ok(hash_token.to_login_res())
|
||||||
} else {
|
} else {
|
||||||
ctx.db
|
ctx.db()
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
check_password_against_db(db, &password)?;
|
C::check_password(db, &password)?;
|
||||||
let hash_token = HashSessionToken::new();
|
let hash_token = HashSessionToken::new();
|
||||||
db.as_private_mut().as_sessions_mut().insert(
|
C::access_sessions(db).insert(
|
||||||
hash_token.hashed(),
|
hash_token.hashed(),
|
||||||
&Session {
|
&Session {
|
||||||
logged_in: Utc::now(),
|
logged_in: Utc::now(),
|
||||||
last_active: Utc::now(),
|
last_active: Utc::now(),
|
||||||
user_agent,
|
user_agent,
|
||||||
metadata,
|
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -248,7 +267,11 @@ pub async fn login_impl(
|
|||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.result
|
.result
|
||||||
}
|
}?;
|
||||||
|
|
||||||
|
ctx.post_login_hook(&password).await?;
|
||||||
|
|
||||||
|
Ok(tok)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
@@ -256,12 +279,12 @@ pub async fn login_impl(
|
|||||||
#[command(rename_all = "kebab-case")]
|
#[command(rename_all = "kebab-case")]
|
||||||
pub struct LogoutParams {
|
pub struct LogoutParams {
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_session")] // from Auth middleware
|
#[serde(rename = "__Auth_session")] // from Auth middleware
|
||||||
session: InternedString,
|
session: InternedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn logout(
|
pub async fn logout<C: SessionAuthContext>(
|
||||||
ctx: RpcContext,
|
ctx: C,
|
||||||
LogoutParams { session }: LogoutParams,
|
LogoutParams { session }: LogoutParams,
|
||||||
) -> Result<Option<HasLoggedOutSessions>, Error> {
|
) -> Result<Option<HasLoggedOutSessions>, Error> {
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
@@ -277,10 +300,7 @@ pub struct Session {
|
|||||||
pub logged_in: DateTime<Utc>,
|
pub logged_in: DateTime<Utc>,
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub last_active: DateTime<Utc>,
|
pub last_active: DateTime<Utc>,
|
||||||
#[ts(skip)]
|
|
||||||
pub user_agent: Option<String>,
|
pub user_agent: Option<String>,
|
||||||
#[ts(type = "any")]
|
|
||||||
pub metadata: Value,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, TS)]
|
#[derive(Deserialize, Serialize, TS)]
|
||||||
@@ -292,29 +312,30 @@ pub struct SessionList {
|
|||||||
sessions: Sessions,
|
sessions: Sessions,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session<C: Context>() -> ParentHandler<C> {
|
pub fn session<C: Context, AC: SessionAuthContext>() -> ParentHandler<C>
|
||||||
|
where
|
||||||
|
CliContext: CallRemote<AC>,
|
||||||
|
{
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"list",
|
"list",
|
||||||
from_fn_async(list)
|
from_fn_async(list::<AC>)
|
||||||
.with_metadata("get_session", Value::Bool(true))
|
.with_metadata("get_session", Value::Bool(true))
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| {
|
.with_custom_display_fn(|handle, result| display_sessions(handle.params, result))
|
||||||
Ok(display_sessions(handle.params, result))
|
.with_about("Display all auth sessions")
|
||||||
})
|
|
||||||
.with_about("Display all server sessions")
|
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"kill",
|
"kill",
|
||||||
from_fn_async(kill)
|
from_fn_async(kill::<AC>)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Terminate existing server session(s)")
|
.with_about("Terminate existing auth session(s)")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_sessions(params: WithIoFormat<ListParams>, arg: SessionList) {
|
fn display_sessions(params: WithIoFormat<ListParams>, arg: SessionList) -> Result<(), Error> {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
@@ -327,7 +348,6 @@ fn display_sessions(params: WithIoFormat<ListParams>, arg: SessionList) {
|
|||||||
"LOGGED IN",
|
"LOGGED IN",
|
||||||
"LAST ACTIVE",
|
"LAST ACTIVE",
|
||||||
"USER AGENT",
|
"USER AGENT",
|
||||||
"METADATA",
|
|
||||||
]);
|
]);
|
||||||
for (id, session) in arg.sessions.0 {
|
for (id, session) in arg.sessions.0 {
|
||||||
let mut row = row![
|
let mut row = row![
|
||||||
@@ -335,7 +355,6 @@ fn display_sessions(params: WithIoFormat<ListParams>, arg: SessionList) {
|
|||||||
&format!("{}", session.logged_in),
|
&format!("{}", session.logged_in),
|
||||||
&format!("{}", session.last_active),
|
&format!("{}", session.last_active),
|
||||||
session.user_agent.as_deref().unwrap_or("N/A"),
|
session.user_agent.as_deref().unwrap_or("N/A"),
|
||||||
&format!("{}", session.metadata),
|
|
||||||
];
|
];
|
||||||
if Some(id) == arg.current {
|
if Some(id) == arg.current {
|
||||||
row.iter_mut()
|
row.iter_mut()
|
||||||
@@ -344,7 +363,8 @@ fn display_sessions(params: WithIoFormat<ListParams>, arg: SessionList) {
|
|||||||
}
|
}
|
||||||
table.add_row(row);
|
table.add_row(row);
|
||||||
}
|
}
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
@@ -353,18 +373,18 @@ fn display_sessions(params: WithIoFormat<ListParams>, arg: SessionList) {
|
|||||||
pub struct ListParams {
|
pub struct ListParams {
|
||||||
#[arg(skip)]
|
#[arg(skip)]
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_session")] // from Auth middleware
|
#[serde(rename = "__Auth_session")] // from Auth middleware
|
||||||
session: Option<InternedString>,
|
session: Option<InternedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[command(display(display_sessions))]
|
// #[command(display(display_sessions))]
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn list(
|
pub async fn list<C: SessionAuthContext>(
|
||||||
ctx: RpcContext,
|
ctx: C,
|
||||||
ListParams { session, .. }: ListParams,
|
ListParams { session, .. }: ListParams,
|
||||||
) -> Result<SessionList, Error> {
|
) -> Result<SessionList, Error> {
|
||||||
let mut sessions = ctx.db.peek().await.into_private().into_sessions().de()?;
|
let mut sessions = C::access_sessions(&mut ctx.db().peek().await).de()?;
|
||||||
ctx.ephemeral_sessions.peek(|s| {
|
ctx.ephemeral_sessions().peek(|s| {
|
||||||
sessions
|
sessions
|
||||||
.0
|
.0
|
||||||
.extend(s.0.iter().map(|(k, v)| (k.clone(), v.clone())))
|
.extend(s.0.iter().map(|(k, v)| (k.clone(), v.clone())))
|
||||||
@@ -398,7 +418,10 @@ pub struct KillParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn kill(ctx: RpcContext, KillParams { ids }: KillParams) -> Result<(), Error> {
|
pub async fn kill<C: SessionAuthContext>(
|
||||||
|
ctx: C,
|
||||||
|
KillParams { ids }: KillParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
HasLoggedOutSessions::new(ids.into_iter().map(KillSessionId::new), &ctx).await?;
|
HasLoggedOutSessions::new(ids.into_iter().map(KillSessionId::new), &ctx).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -454,30 +477,19 @@ pub async fn reset_password_impl(
|
|||||||
let old_password = old_password.unwrap_or_default().decrypt(&ctx)?;
|
let old_password = old_password.unwrap_or_default().decrypt(&ctx)?;
|
||||||
let new_password = new_password.unwrap_or_default().decrypt(&ctx)?;
|
let new_password = new_password.unwrap_or_default().decrypt(&ctx)?;
|
||||||
|
|
||||||
let mut account = ctx.account.write().await;
|
let account = ctx.account.mutate(|account| {
|
||||||
if !argon2::verify_encoded(&account.password, old_password.as_bytes())
|
if !argon2::verify_encoded(&account.password, old_password.as_bytes())
|
||||||
.with_kind(crate::ErrorKind::IncorrectPassword)?
|
.with_kind(crate::ErrorKind::IncorrectPassword)?
|
||||||
{
|
{
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("Incorrect Password"),
|
eyre!("Incorrect Password"),
|
||||||
crate::ErrorKind::IncorrectPassword,
|
crate::ErrorKind::IncorrectPassword,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
account.set_password(&new_password)?;
|
account.set_password(&new_password)?;
|
||||||
let account_password = &account.password;
|
Ok(account.clone())
|
||||||
let account = account.clone();
|
})?;
|
||||||
ctx.db
|
ctx.db.mutate(|d| account.save(d)).await.result
|
||||||
.mutate(|d| {
|
|
||||||
d.as_public_mut()
|
|
||||||
.as_server_info_mut()
|
|
||||||
.as_password_hash_mut()
|
|
||||||
.ser(account_password)?;
|
|
||||||
account.save(d)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
|
|||||||
@@ -5,17 +5,15 @@ use std::sync::Arc;
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use helpers::AtomicFile;
|
|
||||||
use imbl::OrdSet;
|
use imbl::OrdSet;
|
||||||
use models::PackageId;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use super::target::{BackupTargetId, PackageBackupInfo};
|
|
||||||
use super::PackageBackupReport;
|
use super::PackageBackupReport;
|
||||||
use crate::auth::check_password_against_db;
|
use super::target::{BackupTargetId, PackageBackupInfo};
|
||||||
|
use crate::PackageId;
|
||||||
use crate::backup::os::OsBackup;
|
use crate::backup::os::OsBackup;
|
||||||
use crate::backup::{BackupReport, ServerBackupReport};
|
use crate::backup::{BackupReport, ServerBackupReport};
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
@@ -24,9 +22,10 @@ use crate::db::model::{Database, DatabaseModel};
|
|||||||
use crate::disk::mount::backup::BackupMountGuard;
|
use crate::disk::mount::backup::BackupMountGuard;
|
||||||
use crate::disk::mount::filesystem::ReadWrite;
|
use crate::disk::mount::filesystem::ReadWrite;
|
||||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||||
use crate::notifications::{notify, NotificationLevel};
|
use crate::middleware::auth::session::SessionAuthContext;
|
||||||
|
use crate::notifications::{NotificationLevel, notify};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::io::dir_copy;
|
use crate::util::io::{AtomicFile, dir_copy};
|
||||||
use crate::util::serde::IoFormat;
|
use crate::util::serde::IoFormat;
|
||||||
use crate::version::VersionT;
|
use crate::version::VersionT;
|
||||||
|
|
||||||
@@ -170,7 +169,7 @@ pub async fn backup_all(
|
|||||||
let ((fs, package_ids, server_id), status_guard) = (
|
let ((fs, package_ids, server_id), status_guard) = (
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
check_password_against_db(db, &password)?;
|
RpcContext::check_password(db, &password)?;
|
||||||
let fs = target_id.load(db)?;
|
let fs = target_id.load(db)?;
|
||||||
let package_ids = if let Some(ids) = package_ids {
|
let package_ids = if let Some(ids) = package_ids {
|
||||||
ids.into_iter().collect()
|
ids.into_iter().collect()
|
||||||
@@ -223,18 +222,7 @@ fn assure_backing_up<'a>(
|
|||||||
.as_server_info_mut()
|
.as_server_info_mut()
|
||||||
.as_status_info_mut()
|
.as_status_info_mut()
|
||||||
.as_backup_progress_mut();
|
.as_backup_progress_mut();
|
||||||
if backing_up
|
if backing_up.transpose_ref().is_some() {
|
||||||
.clone()
|
|
||||||
.de()?
|
|
||||||
.iter()
|
|
||||||
.flat_map(|x| x.values())
|
|
||||||
.fold(false, |acc, x| {
|
|
||||||
if !x.complete {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
acc
|
|
||||||
})
|
|
||||||
{
|
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("Server is already backing up!"),
|
eyre!("Server is already backing up!"),
|
||||||
ErrorKind::InvalidRequest,
|
ErrorKind::InvalidRequest,
|
||||||
@@ -287,6 +275,22 @@ async fn perform_backup(
|
|||||||
timestamp: Utc::now(),
|
timestamp: Utc::now(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ctx.db
|
||||||
|
.mutate(|db| {
|
||||||
|
if let Some(progress) = db
|
||||||
|
.as_public_mut()
|
||||||
|
.as_server_info_mut()
|
||||||
|
.as_status_info_mut()
|
||||||
|
.as_backup_progress_mut()
|
||||||
|
.transpose_mut()
|
||||||
|
{
|
||||||
|
progress.insert(&id, &BackupProgress { complete: true })?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
}
|
}
|
||||||
backup_report.insert(
|
backup_report.insert(
|
||||||
id.clone(),
|
id.clone(),
|
||||||
@@ -307,19 +311,14 @@ async fn perform_backup(
|
|||||||
let ui = ctx.db.peek().await.into_public().into_ui().de()?;
|
let ui = ctx.db.peek().await.into_public().into_ui().de()?;
|
||||||
|
|
||||||
let mut os_backup_file =
|
let mut os_backup_file =
|
||||||
AtomicFile::new(backup_guard.path().join("os-backup.json"), None::<PathBuf>)
|
AtomicFile::new(backup_guard.path().join("os-backup.json"), None::<PathBuf>).await?;
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Filesystem)?;
|
|
||||||
os_backup_file
|
os_backup_file
|
||||||
.write_all(&IoFormat::Json.to_vec(&OsBackup {
|
.write_all(&IoFormat::Json.to_vec(&OsBackup {
|
||||||
account: ctx.account.read().await.clone(),
|
account: ctx.account.peek(|a| a.clone()),
|
||||||
ui,
|
ui,
|
||||||
})?)
|
})?)
|
||||||
.await?;
|
.await?;
|
||||||
os_backup_file
|
os_backup_file.save().await?;
|
||||||
.save()
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Filesystem)?;
|
|
||||||
|
|
||||||
let luks_folder_old = backup_guard.path().join("luks.old");
|
let luks_folder_old = backup_guard.path().join("luks.old");
|
||||||
if tokio::fs::metadata(&luks_folder_old).await.is_ok() {
|
if tokio::fs::metadata(&luks_folder_old).await.is_ok() {
|
||||||
@@ -337,7 +336,7 @@ async fn perform_backup(
|
|||||||
let timestamp = Utc::now();
|
let timestamp = Utc::now();
|
||||||
|
|
||||||
backup_guard.unencrypted_metadata.version = crate::version::Current::default().semver().into();
|
backup_guard.unencrypted_metadata.version = crate::version::Current::default().semver().into();
|
||||||
backup_guard.unencrypted_metadata.hostname = ctx.account.read().await.hostname.clone();
|
backup_guard.unencrypted_metadata.hostname = ctx.account.peek(|a| a.hostname.clone());
|
||||||
backup_guard.unencrypted_metadata.timestamp = timestamp.clone();
|
backup_guard.unencrypted_metadata.timestamp = timestamp.clone();
|
||||||
backup_guard.metadata.version = crate::version::Current::default().semver().into();
|
backup_guard.metadata.version = crate::version::Current::default().semver().into();
|
||||||
backup_guard.metadata.timestamp = Some(timestamp);
|
backup_guard.metadata.timestamp = Some(timestamp);
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use models::{HostId, PackageId};
|
|
||||||
use reqwest::Url;
|
|
||||||
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::PackageId;
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::{Base32, Base64};
|
|
||||||
|
|
||||||
pub mod backup_bulk;
|
pub mod backup_bulk;
|
||||||
pub mod os;
|
pub mod os;
|
||||||
@@ -58,13 +55,3 @@ pub fn package_backup<C: Context>() -> ParentHandler<C> {
|
|||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
struct BackupMetadata {
|
|
||||||
pub timestamp: DateTime<Utc>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub network_keys: BTreeMap<HostId, Base64<[u8; 32]>>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub tor_keys: BTreeMap<HostId, Base32<[u8; 64]>>, // DEPRECATED
|
|
||||||
pub registry: Option<Url>,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ use openssl::x509::X509;
|
|||||||
use patch_db::Value;
|
use patch_db::Value;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ssh_key::private::Ed25519Keypair;
|
use ssh_key::private::Ed25519Keypair;
|
||||||
use torut::onion::TorSecretKeyV3;
|
|
||||||
|
|
||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::hostname::{generate_hostname, generate_id, Hostname};
|
use crate::hostname::{Hostname, generate_hostname, generate_id};
|
||||||
|
use crate::net::tor::TorSecretKey;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::crypto::ed25519_expand_key;
|
use crate::util::crypto::ed25519_expand_key;
|
||||||
use crate::util::serde::{Base32, Base64, Pem};
|
use crate::util::serde::{Base32, Base64, Pem};
|
||||||
@@ -36,7 +36,7 @@ impl<'de> Deserialize<'de> for OsBackup {
|
|||||||
v => {
|
v => {
|
||||||
return Err(serde::de::Error::custom(&format!(
|
return Err(serde::de::Error::custom(&format!(
|
||||||
"Unknown backup version {v}"
|
"Unknown backup version {v}"
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -85,8 +85,11 @@ impl OsBackupV0 {
|
|||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
ssh_key::Algorithm::Ed25519,
|
ssh_key::Algorithm::Ed25519,
|
||||||
)?,
|
)?,
|
||||||
tor_keys: vec![TorSecretKeyV3::from(self.tor_key.0)],
|
tor_keys: TorSecretKey::from_bytes(self.tor_key.0)
|
||||||
compat_s9pk_key: ed25519_dalek::SigningKey::generate(
|
.ok()
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
developer_key: ed25519_dalek::SigningKey::generate(
|
||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -116,8 +119,11 @@ impl OsBackupV1 {
|
|||||||
root_ca_key: self.root_ca_key.0,
|
root_ca_key: self.root_ca_key.0,
|
||||||
root_ca_cert: self.root_ca_cert.0,
|
root_ca_cert: self.root_ca_cert.0,
|
||||||
ssh_key: ssh_key::PrivateKey::from(Ed25519Keypair::from_seed(&self.net_key.0)),
|
ssh_key: ssh_key::PrivateKey::from(Ed25519Keypair::from_seed(&self.net_key.0)),
|
||||||
tor_keys: vec![TorSecretKeyV3::from(ed25519_expand_key(&self.net_key.0))],
|
tor_keys: TorSecretKey::from_bytes(ed25519_expand_key(&self.net_key.0))
|
||||||
compat_s9pk_key: ed25519_dalek::SigningKey::from_bytes(&self.net_key),
|
.ok()
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
developer_key: ed25519_dalek::SigningKey::from_bytes(&self.net_key),
|
||||||
},
|
},
|
||||||
ui: self.ui,
|
ui: self.ui,
|
||||||
}
|
}
|
||||||
@@ -134,7 +140,7 @@ struct OsBackupV2 {
|
|||||||
root_ca_key: Pem<PKey<Private>>, // PEM Encoded OpenSSL Key
|
root_ca_key: Pem<PKey<Private>>, // PEM Encoded OpenSSL Key
|
||||||
root_ca_cert: Pem<X509>, // PEM Encoded OpenSSL X509 Certificate
|
root_ca_cert: Pem<X509>, // PEM Encoded OpenSSL X509 Certificate
|
||||||
ssh_key: Pem<ssh_key::PrivateKey>, // PEM Encoded OpenSSH Key
|
ssh_key: Pem<ssh_key::PrivateKey>, // PEM Encoded OpenSSH Key
|
||||||
tor_keys: Vec<TorSecretKeyV3>, // Base64 Encoded Ed25519 Expanded Secret Key
|
tor_keys: Vec<TorSecretKey>, // Base64 Encoded Ed25519 Expanded Secret Key
|
||||||
compat_s9pk_key: Pem<ed25519_dalek::SigningKey>, // PEM Encoded ED25519 Key
|
compat_s9pk_key: Pem<ed25519_dalek::SigningKey>, // PEM Encoded ED25519 Key
|
||||||
ui: Value, // JSON Value
|
ui: Value, // JSON Value
|
||||||
}
|
}
|
||||||
@@ -149,7 +155,7 @@ impl OsBackupV2 {
|
|||||||
root_ca_cert: self.root_ca_cert.0,
|
root_ca_cert: self.root_ca_cert.0,
|
||||||
ssh_key: self.ssh_key.0,
|
ssh_key: self.ssh_key.0,
|
||||||
tor_keys: self.tor_keys,
|
tor_keys: self.tor_keys,
|
||||||
compat_s9pk_key: self.compat_s9pk_key.0,
|
developer_key: self.compat_s9pk_key.0,
|
||||||
},
|
},
|
||||||
ui: self.ui,
|
ui: self.ui,
|
||||||
}
|
}
|
||||||
@@ -162,7 +168,7 @@ impl OsBackupV2 {
|
|||||||
root_ca_cert: Pem(backup.account.root_ca_cert.clone()),
|
root_ca_cert: Pem(backup.account.root_ca_cert.clone()),
|
||||||
ssh_key: Pem(backup.account.ssh_key.clone()),
|
ssh_key: Pem(backup.account.ssh_key.clone()),
|
||||||
tor_keys: backup.account.tor_keys.clone(),
|
tor_keys: backup.account.tor_keys.clone(),
|
||||||
compat_s9pk_key: Pem(backup.account.compat_s9pk_key.clone()),
|
compat_s9pk_key: Pem(backup.account.developer_key.clone()),
|
||||||
ui: backup.ui.clone(),
|
ui: backup.ui.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ use std::collections::BTreeMap;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::{stream, StreamExt};
|
use futures::{StreamExt, stream};
|
||||||
use models::PackageId;
|
|
||||||
use patch_db::json_ptr::ROOT;
|
use patch_db::json_ptr::ROOT;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@@ -20,10 +19,13 @@ use crate::disk::mount::filesystem::ReadWrite;
|
|||||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||||
use crate::init::init;
|
use crate::init::init;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::progress::ProgressUnits;
|
||||||
use crate::s9pk::S9pk;
|
use crate::s9pk::S9pk;
|
||||||
use crate::service::service_map::DownloadInstallFuture;
|
use crate::service::service_map::DownloadInstallFuture;
|
||||||
use crate::setup::SetupExecuteProgress;
|
use crate::setup::SetupExecuteProgress;
|
||||||
|
use crate::system::sync_kiosk;
|
||||||
use crate::util::serde::IoFormat;
|
use crate::util::serde::IoFormat;
|
||||||
|
use crate::{PLATFORM, PackageId};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -80,6 +82,7 @@ pub async fn recover_full_embassy(
|
|||||||
recovery_source: TmpMountGuard,
|
recovery_source: TmpMountGuard,
|
||||||
server_id: &str,
|
server_id: &str,
|
||||||
recovery_password: &str,
|
recovery_password: &str,
|
||||||
|
kiosk: Option<bool>,
|
||||||
SetupExecuteProgress {
|
SetupExecuteProgress {
|
||||||
init_phases,
|
init_phases,
|
||||||
restore_phase,
|
restore_phase,
|
||||||
@@ -105,8 +108,12 @@ pub async fn recover_full_embassy(
|
|||||||
)
|
)
|
||||||
.with_kind(ErrorKind::PasswordHashGeneration)?;
|
.with_kind(ErrorKind::PasswordHashGeneration)?;
|
||||||
|
|
||||||
|
let kiosk = Some(kiosk.unwrap_or(true)).filter(|_| &*PLATFORM != "raspberrypi");
|
||||||
|
sync_kiosk(kiosk).await?;
|
||||||
|
|
||||||
let db = ctx.db().await?;
|
let db = ctx.db().await?;
|
||||||
db.put(&ROOT, &Database::init(&os_backup.account)?).await?;
|
db.put(&ROOT, &Database::init(&os_backup.account, kiosk)?)
|
||||||
|
.await?;
|
||||||
drop(db);
|
drop(db);
|
||||||
|
|
||||||
let init_result = init(&ctx.webserver, &ctx.config, init_phases).await?;
|
let init_result = init(&ctx.webserver, &ctx.config, init_phases).await?;
|
||||||
@@ -129,6 +136,7 @@ pub async fn recover_full_embassy(
|
|||||||
.collect();
|
.collect();
|
||||||
let tasks = restore_packages(&rpc_ctx, backup_guard, ids).await?;
|
let tasks = restore_packages(&rpc_ctx, backup_guard, ids).await?;
|
||||||
restore_phase.set_total(tasks.len() as u64);
|
restore_phase.set_total(tasks.len() as u64);
|
||||||
|
restore_phase.set_units(Some(ProgressUnits::Steps));
|
||||||
let restore_phase = Arc::new(Mutex::new(restore_phase));
|
let restore_phase = Arc::new(Mutex::new(restore_phase));
|
||||||
stream::iter(tasks)
|
stream::iter(tasks)
|
||||||
.for_each_concurrent(5, |(id, res)| {
|
.for_each_concurrent(5, |(id, res)| {
|
||||||
@@ -166,6 +174,7 @@ async fn restore_packages(
|
|||||||
.install(
|
.install(
|
||||||
ctx.clone(),
|
ctx.clone(),
|
||||||
|| S9pk::open(s9pk_path, Some(&id)),
|
|| S9pk::open(s9pk_path, Some(&id)),
|
||||||
|
None, // TODO: pull from metadata?
|
||||||
Some(backup_dir),
|
Some(backup_dir),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ use std::path::{Path, PathBuf};
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
|
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use super::{BackupTarget, BackupTargetId};
|
use super::{BackupTarget, BackupTargetId};
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::DatabaseModel;
|
use crate::db::model::DatabaseModel;
|
||||||
use crate::disk::mount::filesystem::cifs::Cifs;
|
|
||||||
use crate::disk::mount::filesystem::ReadOnly;
|
use crate::disk::mount::filesystem::ReadOnly;
|
||||||
|
use crate::disk::mount::filesystem::cifs::Cifs;
|
||||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||||
use crate::disk::util::{recovery_info, StartOsRecoveryInfo};
|
use crate::disk::util::{StartOsRecoveryInfo, recovery_info};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::KeyVal;
|
use crate::util::serde::KeyVal;
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ use std::collections::BTreeMap;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use clap::builder::ValueParserFactory;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use clap::builder::ValueParserFactory;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use digest::generic_array::GenericArray;
|
|
||||||
use digest::OutputSizeUser;
|
use digest::OutputSizeUser;
|
||||||
|
use digest::generic_array::GenericArray;
|
||||||
use exver::Version;
|
use exver::Version;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use models::{FromStrParser, PackageId};
|
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@@ -18,6 +17,7 @@ use tracing::instrument;
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use self::cifs::CifsBackupTarget;
|
use self::cifs::CifsBackupTarget;
|
||||||
|
use crate::PackageId;
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::DatabaseModel;
|
use crate::db::model::DatabaseModel;
|
||||||
use crate::disk::mount::backup::BackupMountGuard;
|
use crate::disk::mount::backup::BackupMountGuard;
|
||||||
@@ -28,9 +28,9 @@ use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
|||||||
use crate::disk::util::PartitionInfo;
|
use crate::disk::util::PartitionInfo;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::{
|
use crate::util::serde::{
|
||||||
deserialize_from_str, display_serializable, serialize_display, HandlerExtSerde, WithIoFormat,
|
HandlerExtSerde, WithIoFormat, deserialize_from_str, display_serializable, serialize_display,
|
||||||
};
|
};
|
||||||
use crate::util::VersionString;
|
use crate::util::{FromStrParser, VersionString};
|
||||||
|
|
||||||
pub mod cifs;
|
pub mod cifs;
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ pub fn target<C: Context>() -> ParentHandler<C> {
|
|||||||
from_fn_async(info)
|
from_fn_async(info)
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn::<CliContext, _>(|params, info| {
|
.with_custom_display_fn::<CliContext, _>(|params, info| {
|
||||||
Ok(display_backup_info(params.params, info))
|
display_backup_info(params.params, info)
|
||||||
})
|
})
|
||||||
.with_about("Display package backup information")
|
.with_about("Display package backup information")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
@@ -227,7 +227,7 @@ pub struct PackageBackupInfo {
|
|||||||
pub timestamp: DateTime<Utc>,
|
pub timestamp: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_backup_info(params: WithIoFormat<InfoParams>, info: BackupInfo) {
|
fn display_backup_info(params: WithIoFormat<InfoParams>, info: BackupInfo) -> Result<(), Error> {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
@@ -260,7 +260,8 @@ fn display_backup_info(params: WithIoFormat<InfoParams>, info: BackupInfo) {
|
|||||||
];
|
];
|
||||||
table.add_row(row);
|
table.add_row(row);
|
||||||
}
|
}
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
@@ -296,17 +297,20 @@ pub async fn info(
|
|||||||
}
|
}
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref USER_MOUNTS: Mutex<BTreeMap<BackupTargetId, BackupMountGuard<TmpMountGuard>>> =
|
static ref USER_MOUNTS: Mutex<BTreeMap<BackupTargetId, Result<BackupMountGuard<TmpMountGuard>, TmpMountGuard>>> =
|
||||||
Mutex::new(BTreeMap::new());
|
Mutex::new(BTreeMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[command(rename_all = "kebab-case")]
|
#[command(rename_all = "kebab-case")]
|
||||||
pub struct MountParams {
|
pub struct MountParams {
|
||||||
target_id: BackupTargetId,
|
target_id: BackupTargetId,
|
||||||
server_id: String,
|
#[arg(long)]
|
||||||
password: String,
|
server_id: Option<String>,
|
||||||
|
password: String, // TODO: rpassword
|
||||||
|
#[arg(long)]
|
||||||
|
allow_partial: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
@@ -316,24 +320,63 @@ pub async fn mount(
|
|||||||
target_id,
|
target_id,
|
||||||
server_id,
|
server_id,
|
||||||
password,
|
password,
|
||||||
|
allow_partial,
|
||||||
}: MountParams,
|
}: MountParams,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
|
let server_id = if let Some(server_id) = server_id {
|
||||||
|
server_id
|
||||||
|
} else {
|
||||||
|
ctx.db
|
||||||
|
.peek()
|
||||||
|
.await
|
||||||
|
.into_public()
|
||||||
|
.into_server_info()
|
||||||
|
.into_id()
|
||||||
|
.de()?
|
||||||
|
};
|
||||||
|
|
||||||
let mut mounts = USER_MOUNTS.lock().await;
|
let mut mounts = USER_MOUNTS.lock().await;
|
||||||
|
|
||||||
if let Some(existing) = mounts.get(&target_id) {
|
let existing = mounts.get(&target_id);
|
||||||
return Ok(existing.path().display().to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
let guard = BackupMountGuard::mount(
|
let base = match existing {
|
||||||
TmpMountGuard::mount(&target_id.clone().load(&ctx.db.peek().await)?, ReadWrite).await?,
|
Some(Ok(a)) => return Ok(a.path().display().to_string()),
|
||||||
&server_id,
|
Some(Err(e)) => e.clone(),
|
||||||
&password,
|
None => {
|
||||||
)
|
TmpMountGuard::mount(&target_id.clone().load(&ctx.db.peek().await)?, ReadWrite).await?
|
||||||
.await?;
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let guard = match BackupMountGuard::mount(base.clone(), &server_id, &password).await {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => {
|
||||||
|
if allow_partial {
|
||||||
|
mounts.insert(target_id, Err(base.clone()));
|
||||||
|
let enc_key = BackupMountGuard::<TmpMountGuard>::load_metadata(
|
||||||
|
base.path(),
|
||||||
|
&server_id,
|
||||||
|
&password,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|(_, k)| k);
|
||||||
|
return Err(e)
|
||||||
|
.with_ctx(|e| (
|
||||||
|
e.kind,
|
||||||
|
format!(
|
||||||
|
"\nThe base filesystem did successfully mount at {:?}\nWrapped Key: {:?}",
|
||||||
|
base.path(),
|
||||||
|
enc_key
|
||||||
|
)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let res = guard.path().display().to_string();
|
let res = guard.path().display().to_string();
|
||||||
|
|
||||||
mounts.insert(target_id, guard);
|
mounts.insert(target_id, Ok(guard));
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
@@ -350,11 +393,17 @@ pub async fn umount(_: RpcContext, UmountParams { target_id }: UmountParams) ->
|
|||||||
let mut mounts = USER_MOUNTS.lock().await; // TODO: move to context
|
let mut mounts = USER_MOUNTS.lock().await; // TODO: move to context
|
||||||
if let Some(target_id) = target_id {
|
if let Some(target_id) = target_id {
|
||||||
if let Some(existing) = mounts.remove(&target_id) {
|
if let Some(existing) = mounts.remove(&target_id) {
|
||||||
existing.unmount().await?;
|
match existing {
|
||||||
|
Ok(e) => e.unmount().await?,
|
||||||
|
Err(e) => e.unmount().await?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (_, existing) in std::mem::take(&mut *mounts) {
|
for (_, existing) in std::mem::take(&mut *mounts) {
|
||||||
existing.unmount().await?;
|
match existing {
|
||||||
|
Ok(e) => e.unmount().await?,
|
||||||
|
Err(e) => e.unmount().await?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,68 +1,85 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::{BTreeMap, VecDeque};
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[cfg(feature = "container-runtime")]
|
|
||||||
pub mod container_cli;
|
pub mod container_cli;
|
||||||
pub mod deprecated;
|
pub mod deprecated;
|
||||||
#[cfg(feature = "registry")]
|
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
#[cfg(feature = "cli")]
|
|
||||||
pub mod start_cli;
|
pub mod start_cli;
|
||||||
#[cfg(feature = "daemon")]
|
|
||||||
pub mod start_init;
|
pub mod start_init;
|
||||||
#[cfg(feature = "daemon")]
|
|
||||||
pub mod startd;
|
pub mod startd;
|
||||||
|
pub mod tunnel;
|
||||||
|
|
||||||
fn select_executable(name: &str) -> Option<fn(VecDeque<OsString>)> {
|
#[derive(Default)]
|
||||||
match name {
|
pub struct MultiExecutable(BTreeMap<&'static str, fn(VecDeque<OsString>)>);
|
||||||
#[cfg(feature = "cli")]
|
impl MultiExecutable {
|
||||||
"start-cli" => Some(start_cli::main),
|
pub fn enable_startd(&mut self) -> &mut Self {
|
||||||
#[cfg(feature = "container-runtime")]
|
self.0.insert("startd", startd::main);
|
||||||
"start-cli" => Some(container_cli::main),
|
self.0
|
||||||
#[cfg(feature = "daemon")]
|
.insert("embassyd", |_| deprecated::renamed("embassyd", "startd"));
|
||||||
"startd" => Some(startd::main),
|
self.0
|
||||||
#[cfg(feature = "registry")]
|
.insert("embassy-init", |_| deprecated::removed("embassy-init"));
|
||||||
"registry" => Some(registry::main),
|
self
|
||||||
"embassy-cli" => Some(|_| deprecated::renamed("embassy-cli", "start-cli")),
|
}
|
||||||
"embassy-sdk" => Some(|_| deprecated::renamed("embassy-sdk", "start-sdk")),
|
pub fn enable_start_cli(&mut self) -> &mut Self {
|
||||||
"embassyd" => Some(|_| deprecated::renamed("embassyd", "startd")),
|
self.0.insert("start-cli", start_cli::main);
|
||||||
"embassy-init" => Some(|_| deprecated::removed("embassy-init")),
|
self.0.insert("embassy-cli", |_| {
|
||||||
"contents" => Some(|_| {
|
deprecated::renamed("embassy-cli", "start-cli")
|
||||||
#[cfg(feature = "cli")]
|
});
|
||||||
println!("start-cli");
|
self.0
|
||||||
#[cfg(feature = "container-runtime")]
|
.insert("embassy-sdk", |_| deprecated::removed("embassy-sdk"));
|
||||||
println!("start-cli (container)");
|
self
|
||||||
#[cfg(feature = "daemon")]
|
}
|
||||||
println!("startd");
|
pub fn enable_start_container(&mut self) -> &mut Self {
|
||||||
#[cfg(feature = "registry")]
|
self.0.insert("start-container", container_cli::main);
|
||||||
println!("registry");
|
self
|
||||||
}),
|
}
|
||||||
_ => None,
|
pub fn enable_start_registryd(&mut self) -> &mut Self {
|
||||||
|
self.0.insert("start-registryd", registry::main);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn enable_start_registry(&mut self) -> &mut Self {
|
||||||
|
self.0.insert("start-registry", registry::cli);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn enable_start_tunneld(&mut self) -> &mut Self {
|
||||||
|
self.0.insert("start-tunneld", tunnel::main);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn enable_start_tunnel(&mut self) -> &mut Self {
|
||||||
|
self.0.insert("start-tunnel", tunnel::cli);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn startbox() {
|
fn select_executable(&self, name: &str) -> Option<fn(VecDeque<OsString>)> {
|
||||||
let mut args = std::env::args_os().collect::<VecDeque<_>>();
|
self.0.get(&name).copied()
|
||||||
for _ in 0..2 {
|
}
|
||||||
if let Some(s) = args.pop_front() {
|
|
||||||
if let Some(x) = Path::new(&*s)
|
pub fn execute(&self) {
|
||||||
.file_name()
|
let mut args = std::env::args_os().collect::<VecDeque<_>>();
|
||||||
.and_then(|s| s.to_str())
|
for _ in 0..2 {
|
||||||
.and_then(|s| select_executable(&s))
|
if let Some(s) = args.pop_front() {
|
||||||
{
|
if let Some(name) = Path::new(&*s).file_name().and_then(|s| s.to_str()) {
|
||||||
args.push_front(s);
|
if name == "--contents" {
|
||||||
return x(args);
|
for name in self.0.keys() {
|
||||||
|
println!("{name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(x) = self.select_executable(&name) {
|
||||||
|
args.push_front(s);
|
||||||
|
return x(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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")
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
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")
|
|
||||||
);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,26 @@ use std::ffi::OsString;
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
use rpc_toolkit::CliApp;
|
||||||
use tokio::signal::unix::signal;
|
use tokio::signal::unix::signal;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::context::CliContext;
|
||||||
|
use crate::context::config::ClientConfig;
|
||||||
use crate::net::web_server::{Acceptor, WebServer};
|
use crate::net::web_server::{Acceptor, WebServer};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::context::{RegistryConfig, RegistryContext};
|
use crate::registry::context::{RegistryConfig, RegistryContext};
|
||||||
|
use crate::registry::registry_router;
|
||||||
use crate::util::logger::LOGGER;
|
use crate::util::logger::LOGGER;
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn inner_main(config: &RegistryConfig) -> Result<(), Error> {
|
async fn inner_main(config: &RegistryConfig) -> Result<(), Error> {
|
||||||
let server = async {
|
let server = async {
|
||||||
let ctx = RegistryContext::init(config).await?;
|
let ctx = RegistryContext::init(config).await?;
|
||||||
let mut server = WebServer::new(Acceptor::bind([ctx.listen]).await?);
|
let server = WebServer::new(
|
||||||
server.serve_registry(ctx.clone());
|
Acceptor::bind([ctx.listen]).await?,
|
||||||
|
registry_router(ctx.clone()),
|
||||||
|
);
|
||||||
|
|
||||||
let mut shutdown_recv = ctx.shutdown.subscribe();
|
let mut shutdown_recv = ctx.shutdown.subscribe();
|
||||||
|
|
||||||
@@ -85,3 +91,30 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cli(args: impl IntoIterator<Item = OsString>) {
|
||||||
|
LOGGER.enable();
|
||||||
|
|
||||||
|
if let Err(e) = CliApp::new(
|
||||||
|
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||||
|
crate::registry::registry_api(),
|
||||||
|
)
|
||||||
|
.run(args)
|
||||||
|
{
|
||||||
|
match e.data {
|
||||||
|
Some(serde_json::Value::String(s)) => eprintln!("{}: {}", e.message, s),
|
||||||
|
Some(serde_json::Value::Object(o)) => {
|
||||||
|
if let Some(serde_json::Value::String(s)) = o.get("details") {
|
||||||
|
eprintln!("{}: {}", e.message, s);
|
||||||
|
if let Some(serde_json::Value::String(s)) = o.get("debug") {
|
||||||
|
tracing::debug!("{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(a) => eprintln!("{}: {}", e.message, a),
|
||||||
|
None => eprintln!("{}", e.message),
|
||||||
|
}
|
||||||
|
|
||||||
|
std::process::exit(e.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use std::ffi::OsString;
|
|||||||
use rpc_toolkit::CliApp;
|
use rpc_toolkit::CliApp;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::context::config::ClientConfig;
|
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
|
use crate::context::config::ClientConfig;
|
||||||
use crate::util::logger::LOGGER;
|
use crate::util::logger::LOGGER;
|
||||||
use crate::version::{Current, VersionT};
|
use crate::version::{Current, VersionT};
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
|||||||
|
|
||||||
if let Err(e) = CliApp::new(
|
if let Err(e) = CliApp::new(
|
||||||
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||||
crate::expanded_api(),
|
crate::main_api(),
|
||||||
)
|
)
|
||||||
.run(args)
|
.run(args)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
@@ -7,12 +6,13 @@ use tracing::instrument;
|
|||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::context::rpc::InitRpcContextPhases;
|
use crate::context::rpc::InitRpcContextPhases;
|
||||||
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
||||||
|
use crate::disk::REPAIR_DISK_PATH;
|
||||||
use crate::disk::fsck::RepairStrategy;
|
use crate::disk::fsck::RepairStrategy;
|
||||||
use crate::disk::main::DEFAULT_PASSWORD;
|
use crate::disk::main::DEFAULT_PASSWORD;
|
||||||
use crate::disk::REPAIR_DISK_PATH;
|
|
||||||
use crate::firmware::{check_for_firmware_update, update_firmware};
|
use crate::firmware::{check_for_firmware_update, update_firmware};
|
||||||
use crate::init::{InitPhases, STANDBY_MODE_PATH};
|
use crate::init::{InitPhases, STANDBY_MODE_PATH};
|
||||||
use crate::net::web_server::{UpgradableListener, WebServer};
|
use crate::net::gateway::UpgradableListener;
|
||||||
|
use crate::net::web_server::WebServer;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::FullProgressTracker;
|
use crate::progress::FullProgressTracker;
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
@@ -38,7 +38,7 @@ async fn setup_or_init(
|
|||||||
let mut update_phase = handle.add_phase("Updating Firmware".into(), Some(10));
|
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 reboot_phase = handle.add_phase("Rebooting".into(), Some(1));
|
||||||
|
|
||||||
server.serve_init(init_ctx);
|
server.serve_ui_for(init_ctx);
|
||||||
|
|
||||||
update_phase.start();
|
update_phase.start();
|
||||||
if let Err(e) = update_firmware(firmware).await {
|
if let Err(e) = update_firmware(firmware).await {
|
||||||
@@ -48,7 +48,7 @@ async fn setup_or_init(
|
|||||||
update_phase.complete();
|
update_phase.complete();
|
||||||
reboot_phase.start();
|
reboot_phase.start();
|
||||||
return Ok(Err(Shutdown {
|
return Ok(Err(Shutdown {
|
||||||
export_args: None,
|
disk_guid: None,
|
||||||
restart: true,
|
restart: true,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ async fn setup_or_init(
|
|||||||
|
|
||||||
let ctx = InstallContext::init().await?;
|
let ctx = InstallContext::init().await?;
|
||||||
|
|
||||||
server.serve_install(ctx.clone());
|
server.serve_ui_for(ctx.clone());
|
||||||
|
|
||||||
ctx.shutdown
|
ctx.shutdown
|
||||||
.subscribe()
|
.subscribe()
|
||||||
@@ -103,7 +103,7 @@ async fn setup_or_init(
|
|||||||
.expect("context dropped");
|
.expect("context dropped");
|
||||||
|
|
||||||
return Ok(Err(Shutdown {
|
return Ok(Err(Shutdown {
|
||||||
export_args: None,
|
disk_guid: None,
|
||||||
restart: true,
|
restart: true,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -114,10 +114,12 @@ async fn setup_or_init(
|
|||||||
{
|
{
|
||||||
let ctx = SetupContext::init(server, config)?;
|
let ctx = SetupContext::init(server, config)?;
|
||||||
|
|
||||||
server.serve_setup(ctx.clone());
|
server.serve_ui_for(ctx.clone());
|
||||||
|
|
||||||
let mut shutdown = ctx.shutdown.subscribe();
|
let mut shutdown = ctx.shutdown.subscribe();
|
||||||
shutdown.recv().await.expect("context dropped");
|
if let Some(shutdown) = shutdown.recv().await.expect("context dropped") {
|
||||||
|
return Ok(Err(shutdown));
|
||||||
|
}
|
||||||
|
|
||||||
tokio::task::yield_now().await;
|
tokio::task::yield_now().await;
|
||||||
if let Err(e) = Command::new("killall")
|
if let Err(e) = Command::new("killall")
|
||||||
@@ -136,7 +138,7 @@ async fn setup_or_init(
|
|||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("Setup mode exited before setup completed"),
|
eyre!("Setup mode exited before setup completed"),
|
||||||
ErrorKind::Unknown,
|
ErrorKind::Unknown,
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
@@ -148,7 +150,7 @@ async fn setup_or_init(
|
|||||||
let init_phases = InitPhases::new(&handle);
|
let init_phases = InitPhases::new(&handle);
|
||||||
let rpc_ctx_phases = InitRpcContextPhases::new(&handle);
|
let rpc_ctx_phases = InitRpcContextPhases::new(&handle);
|
||||||
|
|
||||||
server.serve_init(init_ctx);
|
server.serve_ui_for(init_ctx);
|
||||||
|
|
||||||
async {
|
async {
|
||||||
disk_phase.start();
|
disk_phase.start();
|
||||||
@@ -183,7 +185,7 @@ async fn setup_or_init(
|
|||||||
let mut reboot_phase = handle.add_phase("Rebooting".into(), Some(1));
|
let mut reboot_phase = handle.add_phase("Rebooting".into(), Some(1));
|
||||||
reboot_phase.start();
|
reboot_phase.start();
|
||||||
return Ok(Err(Shutdown {
|
return Ok(Err(Shutdown {
|
||||||
export_args: Some((disk_guid, Path::new(DATA_DIR).to_owned())),
|
disk_guid: Some(disk_guid),
|
||||||
restart: true,
|
restart: true,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -246,7 +248,7 @@ pub async fn main(
|
|||||||
e,
|
e,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
server.serve_diagnostic(ctx.clone());
|
server.serve_ui_for(ctx.clone());
|
||||||
|
|
||||||
let shutdown = ctx.shutdown.subscribe().recv().await.unwrap();
|
let shutdown = ctx.shutdown.subscribe().recv().await.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::net::IpAddr;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -13,9 +12,9 @@ use tracing::instrument;
|
|||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::context::rpc::InitRpcContextPhases;
|
use crate::context::rpc::InitRpcContextPhases;
|
||||||
use crate::context::{DiagnosticContext, InitContext, RpcContext};
|
use crate::context::{DiagnosticContext, InitContext, RpcContext};
|
||||||
use crate::net::network_interface::SelfContainedNetworkInterfaceListener;
|
use crate::net::gateway::{BindTcp, SelfContainedNetworkInterfaceListener, UpgradableListener};
|
||||||
use crate::net::utils::ipv6_is_local;
|
use crate::net::static_server::refresher;
|
||||||
use crate::net::web_server::{Acceptor, UpgradableListener, WebServer};
|
use crate::net::web_server::{Acceptor, WebServer};
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
use crate::system::launch_metrics_task;
|
use crate::system::launch_metrics_task;
|
||||||
use crate::util::io::append_file;
|
use crate::util::io::append_file;
|
||||||
@@ -40,7 +39,7 @@ async fn inner_main(
|
|||||||
};
|
};
|
||||||
tokio::fs::write("/run/startos/initialized", "").await?;
|
tokio::fs::write("/run/startos/initialized", "").await?;
|
||||||
|
|
||||||
server.serve_main(ctx.clone());
|
server.serve_ui_for(ctx.clone());
|
||||||
LOGGER.set_logfile(None);
|
LOGGER.set_logfile(None);
|
||||||
handle.complete();
|
handle.complete();
|
||||||
|
|
||||||
@@ -49,7 +48,7 @@ async fn inner_main(
|
|||||||
let init_ctx = InitContext::init(config).await?;
|
let init_ctx = InitContext::init(config).await?;
|
||||||
let handle = init_ctx.progress.clone();
|
let handle = init_ctx.progress.clone();
|
||||||
let rpc_ctx_phases = InitRpcContextPhases::new(&handle);
|
let rpc_ctx_phases = InitRpcContextPhases::new(&handle);
|
||||||
server.serve_init(init_ctx);
|
server.serve_ui_for(init_ctx);
|
||||||
|
|
||||||
let ctx = RpcContext::init(
|
let ctx = RpcContext::init(
|
||||||
&server.acceptor_setter(),
|
&server.acceptor_setter(),
|
||||||
@@ -65,14 +64,14 @@ async fn inner_main(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
server.serve_main(ctx.clone());
|
server.serve_ui_for(ctx.clone());
|
||||||
handle.complete();
|
handle.complete();
|
||||||
|
|
||||||
ctx
|
ctx
|
||||||
};
|
};
|
||||||
|
|
||||||
let (rpc_ctx, shutdown) = async {
|
let (rpc_ctx, shutdown) = async {
|
||||||
crate::hostname::sync_hostname(&rpc_ctx.account.read().await.hostname).await?;
|
crate::hostname::sync_hostname(&rpc_ctx.account.peek(|a| a.hostname.clone())).await?;
|
||||||
|
|
||||||
let mut shutdown_recv = rpc_ctx.shutdown.subscribe();
|
let mut shutdown_recv = rpc_ctx.shutdown.subscribe();
|
||||||
|
|
||||||
@@ -134,8 +133,6 @@ async fn inner_main(
|
|||||||
.await?;
|
.await?;
|
||||||
rpc_ctx.shutdown().await?;
|
rpc_ctx.shutdown().await?;
|
||||||
|
|
||||||
tracing::info!("RPC Context is dropped");
|
|
||||||
|
|
||||||
Ok(shutdown)
|
Ok(shutdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,14 +143,15 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
|||||||
|
|
||||||
let res = {
|
let res = {
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.worker_threads(max(4, num_cpus::get()))
|
.worker_threads(max(1, num_cpus::get()))
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.expect("failed to initialize runtime");
|
.expect("failed to initialize runtime");
|
||||||
let res = rt.block_on(async {
|
let res = rt.block_on(async {
|
||||||
let mut server = WebServer::new(Acceptor::bind_upgradable(
|
let mut server = WebServer::new(
|
||||||
SelfContainedNetworkInterfaceListener::bind(80),
|
Acceptor::bind_upgradable(SelfContainedNetworkInterfaceListener::bind(BindTcp, 80)),
|
||||||
));
|
refresher(),
|
||||||
|
);
|
||||||
match inner_main(&mut server, &config).await {
|
match inner_main(&mut server, &config).await {
|
||||||
Ok(a) => {
|
Ok(a) => {
|
||||||
server.shutdown().await;
|
server.shutdown().await;
|
||||||
@@ -181,7 +179,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
|||||||
e,
|
e,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
server.serve_diagnostic(ctx.clone());
|
server.serve_ui_for(ctx.clone());
|
||||||
|
|
||||||
let mut shutdown = ctx.shutdown.subscribe();
|
let mut shutdown = ctx.shutdown.subscribe();
|
||||||
|
|
||||||
|
|||||||
200
core/startos/src/bins/tunnel.rs
Normal file
200
core/startos/src/bins/tunnel.rs
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
use std::ffi::OsString;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use futures::FutureExt;
|
||||||
|
use rpc_toolkit::CliApp;
|
||||||
|
use tokio::signal::unix::signal;
|
||||||
|
use tracing::instrument;
|
||||||
|
use visit_rs::Visit;
|
||||||
|
|
||||||
|
use crate::context::CliContext;
|
||||||
|
use crate::context::config::ClientConfig;
|
||||||
|
use crate::net::gateway::{Bind, BindTcp};
|
||||||
|
use crate::net::tls::TlsListener;
|
||||||
|
use crate::net::web_server::{Accept, Acceptor, MetadataVisitor, WebServer};
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::tunnel::context::{TunnelConfig, TunnelContext};
|
||||||
|
use crate::tunnel::tunnel_router;
|
||||||
|
use crate::tunnel::web::TunnelCertHandler;
|
||||||
|
use crate::util::future::NonDetachingJoinHandle;
|
||||||
|
use crate::util::logger::LOGGER;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
enum WebserverListener {
|
||||||
|
Http,
|
||||||
|
Https(SocketAddr),
|
||||||
|
}
|
||||||
|
impl<V: MetadataVisitor> Visit<V> for WebserverListener {
|
||||||
|
fn visit(&self, visitor: &mut V) -> <V as visit_rs::Visitor>::Result {
|
||||||
|
visitor.visit(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
async fn inner_main(config: &TunnelConfig) -> Result<(), Error> {
|
||||||
|
let server = async {
|
||||||
|
let ctx = TunnelContext::init(config).await?;
|
||||||
|
let listen = ctx.listen;
|
||||||
|
let server = WebServer::new(
|
||||||
|
Acceptor::bind_map_dyn([(WebserverListener::Http, listen)]).await?,
|
||||||
|
tunnel_router(ctx.clone()),
|
||||||
|
);
|
||||||
|
let acceptor_setter = server.acceptor_setter();
|
||||||
|
let https_db = ctx.db.clone();
|
||||||
|
let https_thread: NonDetachingJoinHandle<()> = tokio::spawn(async move {
|
||||||
|
let mut sub = https_db.subscribe("/webserver".parse().unwrap()).await;
|
||||||
|
while {
|
||||||
|
while let Err(e) = async {
|
||||||
|
let webserver = https_db.peek().await.into_webserver();
|
||||||
|
if webserver.as_enabled().de()? {
|
||||||
|
let addr = webserver.as_listen().de()?.or_not_found("listen address")?;
|
||||||
|
acceptor_setter.send_if_modified(|a| {
|
||||||
|
let key = WebserverListener::Https(addr);
|
||||||
|
if !a.contains_key(&key) {
|
||||||
|
match (|| {
|
||||||
|
Ok::<_, Error>(TlsListener::new(
|
||||||
|
BindTcp.bind(addr)?,
|
||||||
|
TunnelCertHandler {
|
||||||
|
db: https_db.clone(),
|
||||||
|
crypto_provider: Arc::new(tokio_rustls::rustls::crypto::ring::default_provider()),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
})() {
|
||||||
|
Ok(l) => {
|
||||||
|
a.retain(|k, _| *k == WebserverListener::Http);
|
||||||
|
a.insert(key, l.into_dyn());
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("error adding ssl listener: {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
acceptor_setter.send_if_modified(|a| {
|
||||||
|
let before = a.len();
|
||||||
|
a.retain(|k, _| *k == WebserverListener::Http);
|
||||||
|
a.len() != before
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<_, Error>(())
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!("error updating webserver bind: {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||||
|
}
|
||||||
|
sub.recv().await.is_some()
|
||||||
|
} {}
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let mut shutdown_recv = ctx.shutdown.subscribe();
|
||||||
|
|
||||||
|
let sig_handler_ctx = ctx;
|
||||||
|
let sig_handler: NonDetachingJoinHandle<()> = tokio::spawn(async move {
|
||||||
|
use tokio::signal::unix::SignalKind;
|
||||||
|
futures::future::select_all(
|
||||||
|
[
|
||||||
|
SignalKind::interrupt(),
|
||||||
|
SignalKind::quit(),
|
||||||
|
SignalKind::terminate(),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.map(|s| {
|
||||||
|
async move {
|
||||||
|
signal(*s)
|
||||||
|
.unwrap_or_else(|_| panic!("register {:?} handler", s))
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
sig_handler_ctx
|
||||||
|
.shutdown
|
||||||
|
.send(())
|
||||||
|
.map_err(|_| ())
|
||||||
|
.expect("send shutdown signal");
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
|
shutdown_recv
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
.with_kind(crate::ErrorKind::Unknown)?;
|
||||||
|
|
||||||
|
sig_handler.wait_for_abort().await.with_kind(ErrorKind::Unknown)?;
|
||||||
|
https_thread.wait_for_abort().await.with_kind(ErrorKind::Unknown)?;
|
||||||
|
|
||||||
|
Ok::<_, Error>(server)
|
||||||
|
}
|
||||||
|
.await?;
|
||||||
|
server.shutdown().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||||
|
LOGGER.enable();
|
||||||
|
|
||||||
|
let config = TunnelConfig::parse_from(args).load().unwrap();
|
||||||
|
|
||||||
|
let res = {
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("failed to initialize runtime");
|
||||||
|
rt.block_on(inner_main(&config))
|
||||||
|
};
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("{}", e.source);
|
||||||
|
tracing::debug!("{:?}", e.source);
|
||||||
|
drop(e.source);
|
||||||
|
std::process::exit(e.kind as i32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cli(args: impl IntoIterator<Item = OsString>) {
|
||||||
|
LOGGER.enable();
|
||||||
|
|
||||||
|
if let Err(e) = CliApp::new(
|
||||||
|
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||||
|
crate::tunnel::api::tunnel_api(),
|
||||||
|
)
|
||||||
|
.run(args)
|
||||||
|
{
|
||||||
|
match e.data {
|
||||||
|
Some(serde_json::Value::String(s)) => eprintln!("{}: {}", e.message, s),
|
||||||
|
Some(serde_json::Value::Object(o)) => {
|
||||||
|
if let Some(serde_json::Value::String(s)) = o.get("details") {
|
||||||
|
eprintln!("{}: {}", e.message, s);
|
||||||
|
if let Some(serde_json::Value::String(s)) = o.get("debug") {
|
||||||
|
tracing::debug!("{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(a) => eprintln!("{}: {}", e.message, a),
|
||||||
|
None => eprintln!("{}", e.message),
|
||||||
|
}
|
||||||
|
|
||||||
|
std::process::exit(e.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
core/startos/src/config/hook.rs
Normal file
53
core/startos/src/config/hook.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
use helpers::Callback;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use jsonpath_lib::Compiled;
|
||||||
|
use crate::PackageId;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::context::RpcContext;
|
||||||
|
|
||||||
|
pub struct ConfigHook {
|
||||||
|
pub path: Compiled,
|
||||||
|
pub prev: Vec<Value>,
|
||||||
|
pub callback: Callback,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpcContext {
|
||||||
|
pub async fn add_config_hook(&self, id: PackageId, hook: ConfigHook) {
|
||||||
|
let mut hooks = self.config_hooks.lock().await;
|
||||||
|
let prev = hooks.remove(&id).unwrap_or_default();
|
||||||
|
hooks.insert(
|
||||||
|
id,
|
||||||
|
prev.into_iter()
|
||||||
|
.filter(|h| h.callback.is_listening())
|
||||||
|
.chain(std::iter::once(hook))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn call_config_hooks(&self, id: PackageId, config: &Value) {
|
||||||
|
let mut hooks = self.config_hooks.lock().await;
|
||||||
|
let mut prev = hooks.remove(&id).unwrap_or_default();
|
||||||
|
for hook in &mut prev {
|
||||||
|
let new = hook
|
||||||
|
.path
|
||||||
|
.select(config)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.cloned()
|
||||||
|
.collect_vec();
|
||||||
|
if new != hook.prev {
|
||||||
|
hook.callback
|
||||||
|
.call(vec![Value::Array(new.clone())])
|
||||||
|
.unwrap_or_default();
|
||||||
|
hook.prev = new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hooks.insert(
|
||||||
|
id,
|
||||||
|
prev.into_iter()
|
||||||
|
.filter(|h| h.callback.is_listening())
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +1,33 @@
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cookie_store::{CookieStore, RawCookie};
|
use cookie::{Cookie, Expiration, SameSite};
|
||||||
|
use cookie_store::CookieStore;
|
||||||
|
use http::HeaderMap;
|
||||||
|
use imbl_value::InternedString;
|
||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use reqwest::Proxy;
|
use reqwest::Proxy;
|
||||||
use reqwest_cookie_store::CookieStoreMutex;
|
use reqwest_cookie_store::CookieStoreMutex;
|
||||||
use rpc_toolkit::reqwest::{Client, Url};
|
use rpc_toolkit::reqwest::{Client, Url};
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{call_remote_http, CallRemote, Context, Empty};
|
use rpc_toolkit::{CallRemote, Context, Empty};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::setup::CURRENT_SECRET;
|
use super::setup::CURRENT_SECRET;
|
||||||
use crate::context::config::{local_config_path, ClientConfig};
|
use crate::context::config::{ClientConfig, local_config_path};
|
||||||
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
||||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
use crate::developer::{OS_DEVELOPER_KEY_PATH, default_developer_key_path};
|
||||||
|
use crate::middleware::auth::local::LocalAuthContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::rpc_continuations::Guid;
|
use crate::rpc_continuations::Guid;
|
||||||
|
use crate::util::io::read_file_to_string;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CliContextSeed {
|
pub struct CliContextSeed {
|
||||||
@@ -29,6 +35,10 @@ pub struct CliContextSeed {
|
|||||||
pub base_url: Url,
|
pub base_url: Url,
|
||||||
pub rpc_url: Url,
|
pub rpc_url: Url,
|
||||||
pub registry_url: Option<Url>,
|
pub registry_url: Option<Url>,
|
||||||
|
pub registry_hostname: Vec<InternedString>,
|
||||||
|
pub registry_listen: Option<SocketAddr>,
|
||||||
|
pub tunnel_addr: Option<SocketAddr>,
|
||||||
|
pub tunnel_listen: Option<SocketAddr>,
|
||||||
pub client: Client,
|
pub client: Client,
|
||||||
pub cookie_store: Arc<CookieStoreMutex>,
|
pub cookie_store: Arc<CookieStoreMutex>,
|
||||||
pub cookie_path: PathBuf,
|
pub cookie_path: PathBuf,
|
||||||
@@ -37,6 +47,11 @@ pub struct CliContextSeed {
|
|||||||
}
|
}
|
||||||
impl Drop for CliContextSeed {
|
impl Drop for CliContextSeed {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
|
if let Some(rt) = self.runtime.take() {
|
||||||
|
if let Ok(rt) = Arc::try_unwrap(rt) {
|
||||||
|
rt.shutdown_background();
|
||||||
|
}
|
||||||
|
}
|
||||||
let tmp = format!("{}.tmp", self.cookie_path.display());
|
let tmp = format!("{}.tmp", self.cookie_path.display());
|
||||||
let parent_dir = self.cookie_path.parent().unwrap_or(Path::new("/"));
|
let parent_dir = self.cookie_path.parent().unwrap_or(Path::new("/"));
|
||||||
if !parent_dir.exists() {
|
if !parent_dir.exists() {
|
||||||
@@ -50,9 +65,8 @@ impl Drop for CliContextSeed {
|
|||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut store = self.cookie_store.lock().unwrap();
|
let store = self.cookie_store.lock().unwrap();
|
||||||
store.remove("localhost", "", "local");
|
cookie_store::serde::json::save(&store, &mut *writer).unwrap();
|
||||||
store.save_json(&mut *writer).unwrap();
|
|
||||||
writer.sync_all().unwrap();
|
writer.sync_all().unwrap();
|
||||||
std::fs::rename(tmp, &self.cookie_path).unwrap();
|
std::fs::rename(tmp, &self.cookie_path).unwrap();
|
||||||
}
|
}
|
||||||
@@ -80,26 +94,14 @@ impl CliContext {
|
|||||||
.unwrap_or(Path::new("/"))
|
.unwrap_or(Path::new("/"))
|
||||||
.join(".cookies.json")
|
.join(".cookies.json")
|
||||||
});
|
});
|
||||||
let cookie_store = Arc::new(CookieStoreMutex::new({
|
let cookie_store = Arc::new(CookieStoreMutex::new(if cookie_path.exists() {
|
||||||
let mut store = if cookie_path.exists() {
|
cookie_store::serde::json::load(BufReader::new(
|
||||||
CookieStore::load_json(BufReader::new(
|
File::open(&cookie_path)
|
||||||
File::open(&cookie_path)
|
.with_ctx(|_| (ErrorKind::Filesystem, cookie_path.display()))?,
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, cookie_path.display()))?,
|
))
|
||||||
))
|
.unwrap_or_default()
|
||||||
.map_err(|e| eyre!("{}", e))
|
} else {
|
||||||
.with_kind(crate::ErrorKind::Deserialization)?
|
CookieStore::default()
|
||||||
} else {
|
|
||||||
CookieStore::default()
|
|
||||||
};
|
|
||||||
if let Ok(local) = std::fs::read_to_string(LOCAL_AUTH_COOKIE_PATH) {
|
|
||||||
store
|
|
||||||
.insert_raw(
|
|
||||||
&RawCookie::new("local", local),
|
|
||||||
&"http://localhost".parse()?,
|
|
||||||
)
|
|
||||||
.with_kind(crate::ErrorKind::Network)?;
|
|
||||||
}
|
|
||||||
store
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Ok(CliContext(Arc::new(CliContextSeed {
|
Ok(CliContext(Arc::new(CliContextSeed {
|
||||||
@@ -124,9 +126,17 @@ impl CliContext {
|
|||||||
Ok::<_, Error>(registry)
|
Ok::<_, Error>(registry)
|
||||||
})
|
})
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
|
registry_hostname: config.registry_hostname.unwrap_or_default(),
|
||||||
|
registry_listen: config.registry_listen,
|
||||||
|
tunnel_addr: config.tunnel,
|
||||||
|
tunnel_listen: config.tunnel_listen,
|
||||||
client: {
|
client: {
|
||||||
let mut builder = Client::builder().cookie_provider(cookie_store.clone());
|
let mut builder = Client::builder().cookie_provider(cookie_store.clone());
|
||||||
if let Some(proxy) = config.proxy {
|
if let Some(proxy) = config.proxy.or_else(|| {
|
||||||
|
config
|
||||||
|
.socks_listen
|
||||||
|
.and_then(|socks| format!("socks5h://{socks}").parse::<Url>().log_err())
|
||||||
|
}) {
|
||||||
builder =
|
builder =
|
||||||
builder.proxy(Proxy::all(proxy).with_kind(crate::ErrorKind::ParseUrl)?)
|
builder.proxy(Proxy::all(proxy).with_kind(crate::ErrorKind::ParseUrl)?)
|
||||||
}
|
}
|
||||||
@@ -134,14 +144,9 @@ impl CliContext {
|
|||||||
},
|
},
|
||||||
cookie_store,
|
cookie_store,
|
||||||
cookie_path,
|
cookie_path,
|
||||||
developer_key_path: config.developer_key_path.unwrap_or_else(|| {
|
developer_key_path: config
|
||||||
local_config_path()
|
.developer_key_path
|
||||||
.as_deref()
|
.unwrap_or_else(default_developer_key_path),
|
||||||
.unwrap_or_else(|| Path::new(super::config::CONFIG_PATH))
|
|
||||||
.parent()
|
|
||||||
.unwrap_or(Path::new("/"))
|
|
||||||
.join("developer.key.pem")
|
|
||||||
}),
|
|
||||||
developer_key: OnceCell::new(),
|
developer_key: OnceCell::new(),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
@@ -150,20 +155,26 @@ impl CliContext {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub fn developer_key(&self) -> Result<&ed25519_dalek::SigningKey, Error> {
|
pub fn developer_key(&self) -> Result<&ed25519_dalek::SigningKey, Error> {
|
||||||
self.developer_key.get_or_try_init(|| {
|
self.developer_key.get_or_try_init(|| {
|
||||||
if !self.developer_key_path.exists() {
|
for path in [Path::new(OS_DEVELOPER_KEY_PATH), &self.developer_key_path] {
|
||||||
return Err(Error::new(eyre!("Developer Key does not exist! Please run `start-cli init` before running this command."), crate::ErrorKind::Uninitialized));
|
if !path.exists() {
|
||||||
}
|
continue;
|
||||||
let pair = <ed25519::KeypairBytes as ed25519::pkcs8::DecodePrivateKey>::from_pkcs8_pem(
|
}
|
||||||
&std::fs::read_to_string(&self.developer_key_path)?,
|
let pair = <ed25519::KeypairBytes as ed25519::pkcs8::DecodePrivateKey>::from_pkcs8_pem(
|
||||||
)
|
&std::fs::read_to_string(path)?,
|
||||||
.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"),
|
|
||||||
ErrorKind::OpenSsl,
|
|
||||||
)
|
)
|
||||||
})?;
|
.with_kind(crate::ErrorKind::Pem)?;
|
||||||
Ok(secret.into())
|
let secret = ed25519_dalek::SecretKey::try_from(&pair.secret_key[..]).map_err(|_| {
|
||||||
|
Error::new(
|
||||||
|
eyre!("pkcs8 key is of 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."),
|
||||||
|
crate::ErrorKind::Uninitialized
|
||||||
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +191,7 @@ impl CliContext {
|
|||||||
eyre!("Cannot parse scheme from base URL"),
|
eyre!("Cannot parse scheme from base URL"),
|
||||||
crate::ErrorKind::ParseUrl,
|
crate::ErrorKind::ParseUrl,
|
||||||
)
|
)
|
||||||
.into())
|
.into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
url.set_scheme(ws_scheme)
|
url.set_scheme(ws_scheme)
|
||||||
@@ -223,23 +234,28 @@ impl CliContext {
|
|||||||
&self,
|
&self,
|
||||||
method: &str,
|
method: &str,
|
||||||
params: Value,
|
params: Value,
|
||||||
) -> Result<Value, RpcError>
|
) -> Result<Value, Error>
|
||||||
where
|
where
|
||||||
Self: CallRemote<RemoteContext>,
|
Self: CallRemote<RemoteContext>,
|
||||||
{
|
{
|
||||||
<Self as CallRemote<RemoteContext, Empty>>::call_remote(&self, method, params, Empty {})
|
<Self as CallRemote<RemoteContext, Empty>>::call_remote(&self, method, params, Empty {})
|
||||||
.await
|
.await
|
||||||
|
.map_err(Error::from)
|
||||||
|
.with_ctx(|e| (e.kind, method))
|
||||||
}
|
}
|
||||||
pub async fn call_remote_with<RemoteContext, T>(
|
pub async fn call_remote_with<RemoteContext, T>(
|
||||||
&self,
|
&self,
|
||||||
method: &str,
|
method: &str,
|
||||||
params: Value,
|
params: Value,
|
||||||
extra: T,
|
extra: T,
|
||||||
) -> Result<Value, RpcError>
|
) -> Result<Value, Error>
|
||||||
where
|
where
|
||||||
Self: CallRemote<RemoteContext, T>,
|
Self: CallRemote<RemoteContext, T>,
|
||||||
{
|
{
|
||||||
<Self as CallRemote<RemoteContext, T>>::call_remote(&self, method, params, extra).await
|
<Self as CallRemote<RemoteContext, T>>::call_remote(&self, method, params, extra)
|
||||||
|
.await
|
||||||
|
.map_err(Error::from)
|
||||||
|
.with_ctx(|e| (e.kind, method))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl AsRef<Jwk> for CliContext {
|
impl AsRef<Jwk> for CliContext {
|
||||||
@@ -269,40 +285,88 @@ impl Context for CliContext {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AsRef<Client> for CliContext {
|
||||||
|
fn as_ref(&self) -> &Client {
|
||||||
|
&self.client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CallRemote<RpcContext> for CliContext {
|
impl CallRemote<RpcContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
if let Ok(local) = read_file_to_string(RpcContext::LOCAL_AUTH_COOKIE_PATH).await {
|
||||||
|
self.cookie_store
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert_raw(
|
||||||
|
&Cookie::build(("local", local))
|
||||||
|
.domain("localhost")
|
||||||
|
.expires(Expiration::Session)
|
||||||
|
.same_site(SameSite::Strict)
|
||||||
|
.build(),
|
||||||
|
&"http://localhost".parse()?,
|
||||||
|
)
|
||||||
|
.with_kind(crate::ErrorKind::Network)?;
|
||||||
|
}
|
||||||
|
crate::middleware::auth::signature::call_remote(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
HeaderMap::new(),
|
||||||
|
self.rpc_url.host_str(),
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<DiagnosticContext> for CliContext {
|
impl CallRemote<DiagnosticContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
crate::middleware::auth::signature::call_remote(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
HeaderMap::new(),
|
||||||
|
self.rpc_url.host_str(),
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<InitContext> for CliContext {
|
impl CallRemote<InitContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
crate::middleware::auth::signature::call_remote(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
HeaderMap::new(),
|
||||||
|
self.rpc_url.host_str(),
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<SetupContext> for CliContext {
|
impl CallRemote<SetupContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
crate::middleware::auth::signature::call_remote(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
HeaderMap::new(),
|
||||||
|
self.rpc_url.host_str(),
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<InstallContext> for CliContext {
|
impl CallRemote<InstallContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
crate::middleware::auth::signature::call_remote(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
HeaderMap::new(),
|
||||||
|
self.rpc_url.host_str(),
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test() {
|
|
||||||
let ctx = CliContext::init(ClientConfig::default()).unwrap();
|
|
||||||
ctx.runtime().unwrap().block_on(async {
|
|
||||||
reqwest::Client::new()
|
|
||||||
.get("http://example.com")
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,18 +3,16 @@ use std::net::SocketAddr;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use imbl_value::InternedString;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::postgres::PgConnectOptions;
|
|
||||||
use sqlx::PgPool;
|
|
||||||
|
|
||||||
|
use crate::MAIN_DATA;
|
||||||
use crate::disk::OsPartitionInfo;
|
use crate::disk::OsPartitionInfo;
|
||||||
use crate::init::init_postgres;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::IoFormat;
|
use crate::util::serde::IoFormat;
|
||||||
use crate::version::VersionT;
|
use crate::version::VersionT;
|
||||||
use crate::MAIN_DATA;
|
|
||||||
|
|
||||||
pub const DEVICE_CONFIG_PATH: &str = "/media/startos/config/config.yaml"; // "/media/startos/config/config.yaml";
|
pub const DEVICE_CONFIG_PATH: &str = "/media/startos/config/config.yaml"; // "/media/startos/config/config.yaml";
|
||||||
pub const CONFIG_PATH: &str = "/etc/startos/config.yaml";
|
pub const CONFIG_PATH: &str = "/etc/startos/config.yaml";
|
||||||
@@ -58,7 +56,6 @@ pub trait ContextConfig: DeserializeOwned + Default {
|
|||||||
#[derive(Debug, Default, Deserialize, Serialize, Parser)]
|
#[derive(Debug, Default, Deserialize, Serialize, Parser)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
#[command(rename_all = "kebab-case")]
|
#[command(rename_all = "kebab-case")]
|
||||||
#[command(name = "start-cli")]
|
|
||||||
#[command(version = crate::version::Current::default().semver().to_string())]
|
#[command(version = crate::version::Current::default().semver().to_string())]
|
||||||
pub struct ClientConfig {
|
pub struct ClientConfig {
|
||||||
#[arg(short = 'c', long)]
|
#[arg(short = 'c', long)]
|
||||||
@@ -67,8 +64,18 @@ pub struct ClientConfig {
|
|||||||
pub host: Option<Url>,
|
pub host: Option<Url>,
|
||||||
#[arg(short = 'r', long)]
|
#[arg(short = 'r', long)]
|
||||||
pub registry: Option<Url>,
|
pub registry: Option<Url>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub registry_hostname: Option<Vec<InternedString>>,
|
||||||
|
#[arg(skip)]
|
||||||
|
pub registry_listen: Option<SocketAddr>,
|
||||||
|
#[arg(short = 't', long)]
|
||||||
|
pub tunnel: Option<SocketAddr>,
|
||||||
|
#[arg(skip)]
|
||||||
|
pub tunnel_listen: Option<SocketAddr>,
|
||||||
#[arg(short = 'p', long)]
|
#[arg(short = 'p', long)]
|
||||||
pub proxy: Option<Url>,
|
pub proxy: Option<Url>,
|
||||||
|
#[arg(skip)]
|
||||||
|
pub socks_listen: Option<SocketAddr>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub cookie_path: Option<PathBuf>,
|
pub cookie_path: Option<PathBuf>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
@@ -81,6 +88,8 @@ impl ContextConfig for ClientConfig {
|
|||||||
fn merge_with(&mut self, other: Self) {
|
fn merge_with(&mut self, other: Self) {
|
||||||
self.host = self.host.take().or(other.host);
|
self.host = self.host.take().or(other.host);
|
||||||
self.registry = self.registry.take().or(other.registry);
|
self.registry = self.registry.take().or(other.registry);
|
||||||
|
self.registry_hostname = self.registry_hostname.take().or(other.registry_hostname);
|
||||||
|
self.tunnel = self.tunnel.take().or(other.tunnel);
|
||||||
self.proxy = self.proxy.take().or(other.proxy);
|
self.proxy = self.proxy.take().or(other.proxy);
|
||||||
self.cookie_path = self.cookie_path.take().or(other.cookie_path);
|
self.cookie_path = self.cookie_path.take().or(other.cookie_path);
|
||||||
self.developer_key_path = self.developer_key_path.take().or(other.developer_key_path);
|
self.developer_key_path = self.developer_key_path.take().or(other.developer_key_path);
|
||||||
@@ -107,15 +116,15 @@ pub struct ServerConfig {
|
|||||||
#[arg(skip)]
|
#[arg(skip)]
|
||||||
pub os_partitions: Option<OsPartitionInfo>,
|
pub os_partitions: Option<OsPartitionInfo>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub tor_control: Option<SocketAddr>,
|
pub socks_listen: Option<SocketAddr>,
|
||||||
#[arg(long)]
|
|
||||||
pub tor_socks: Option<SocketAddr>,
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub revision_cache_size: Option<usize>,
|
pub revision_cache_size: Option<usize>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub disable_encryption: Option<bool>,
|
pub disable_encryption: Option<bool>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub multi_arch_s9pks: Option<bool>,
|
pub multi_arch_s9pks: Option<bool>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub developer_key_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
impl ContextConfig for ServerConfig {
|
impl ContextConfig for ServerConfig {
|
||||||
fn next(&mut self) -> Option<PathBuf> {
|
fn next(&mut self) -> Option<PathBuf> {
|
||||||
@@ -124,14 +133,14 @@ impl ContextConfig for ServerConfig {
|
|||||||
fn merge_with(&mut self, other: Self) {
|
fn merge_with(&mut self, other: Self) {
|
||||||
self.ethernet_interface = self.ethernet_interface.take().or(other.ethernet_interface);
|
self.ethernet_interface = self.ethernet_interface.take().or(other.ethernet_interface);
|
||||||
self.os_partitions = self.os_partitions.take().or(other.os_partitions);
|
self.os_partitions = self.os_partitions.take().or(other.os_partitions);
|
||||||
self.tor_control = self.tor_control.take().or(other.tor_control);
|
self.socks_listen = self.socks_listen.take().or(other.socks_listen);
|
||||||
self.tor_socks = self.tor_socks.take().or(other.tor_socks);
|
|
||||||
self.revision_cache_size = self
|
self.revision_cache_size = self
|
||||||
.revision_cache_size
|
.revision_cache_size
|
||||||
.take()
|
.take()
|
||||||
.or(other.revision_cache_size);
|
.or(other.revision_cache_size);
|
||||||
self.disable_encryption = self.disable_encryption.take().or(other.disable_encryption);
|
self.disable_encryption = self.disable_encryption.take().or(other.disable_encryption);
|
||||||
self.multi_arch_s9pks = self.multi_arch_s9pks.take().or(other.multi_arch_s9pks);
|
self.multi_arch_s9pks = self.multi_arch_s9pks.take().or(other.multi_arch_s9pks);
|
||||||
|
self.developer_key_path = self.developer_key_path.take().or(other.developer_key_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,16 +160,4 @@ impl ServerConfig {
|
|||||||
|
|
||||||
Ok(db)
|
Ok(db)
|
||||||
}
|
}
|
||||||
#[instrument(skip_all)]
|
|
||||||
pub async fn secret_store(&self) -> Result<PgPool, Error> {
|
|
||||||
init_postgres("/media/startos/data").await?;
|
|
||||||
let secret_store =
|
|
||||||
PgPool::connect_with(PgConnectOptions::new().database("secrets").username("root"))
|
|
||||||
.await?;
|
|
||||||
sqlx::migrate!()
|
|
||||||
.run(&secret_store)
|
|
||||||
.await
|
|
||||||
.with_kind(crate::ErrorKind::Database)?;
|
|
||||||
Ok(secret_store)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
|
||||||
use rpc_toolkit::Context;
|
use rpc_toolkit::Context;
|
||||||
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use tokio::sync::broadcast::Sender;
|
use tokio::sync::broadcast::Sender;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::rpc_continuations::RpcContinuations;
|
use crate::rpc_continuations::RpcContinuations;
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
pub struct DiagnosticContextSeed {
|
pub struct DiagnosticContextSeed {
|
||||||
pub shutdown: Sender<Shutdown>,
|
pub shutdown: Sender<Shutdown>,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user