mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 18:31:52 +00:00
Compare commits
373 Commits
next/minor
...
feature/de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc9db9f2b7 | ||
|
|
7210f43f50 | ||
|
|
df636b7a78 | ||
|
|
10c14b4d0a | ||
|
|
1bf610a853 | ||
|
|
b4d82b82a9 | ||
|
|
7c5ba45f6a | ||
|
|
f83df5682c | ||
|
|
bfdab897ab | ||
|
|
29c97fcbb0 | ||
|
|
e7847d0e88 | ||
|
|
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 |
22
.github/workflows/startos-iso.yaml
vendored
22
.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,6 +63,7 @@ jobs:
|
|||||||
"aarch64": ["aarch64"],
|
"aarch64": ["aarch64"],
|
||||||
"aarch64-nonfree": ["aarch64"],
|
"aarch64-nonfree": ["aarch64"],
|
||||||
"raspberrypi": ["aarch64"],
|
"raspberrypi": ["aarch64"],
|
||||||
|
"riscv64": ["riscv64"],
|
||||||
"ALL": ["x86_64", "aarch64"]
|
"ALL": ["x86_64", "aarch64"]
|
||||||
}')[github.event.inputs.platform || 'ALL']
|
}')[github.event.inputs.platform || 'ALL']
|
||||||
}}
|
}}
|
||||||
@@ -93,8 +95,24 @@ 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: Use Beta Toolchain
|
||||||
|
run: rustup default beta
|
||||||
|
|
||||||
|
- name: Setup Cross
|
||||||
|
run: cargo install cross --git https://github.com/cross-rs/cross
|
||||||
|
|
||||||
- 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:
|
||||||
@@ -129,6 +147,7 @@ jobs:
|
|||||||
"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,6 +161,7 @@ jobs:
|
|||||||
"aarch64": "aarch64",
|
"aarch64": "aarch64",
|
||||||
"aarch64-nonfree": "aarch64",
|
"aarch64-nonfree": "aarch64",
|
||||||
"raspberrypi": "aarch64",
|
"raspberrypi": "aarch64",
|
||||||
|
"riscv64": "riscv64",
|
||||||
}')[matrix.platform]
|
}')[matrix.platform]
|
||||||
}}
|
}}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
8
.github/workflows/test.yaml
vendored
8
.github/workflows/test.yaml
vendored
@@ -11,7 +11,7 @@ on:
|
|||||||
- next/*
|
- next/*
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODEJS_VERSION: "20.16.0"
|
NODEJS_VERSION: "24.11.0"
|
||||||
ENVIRONMENT: dev-unstable
|
ENVIRONMENT: dev-unstable
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -27,5 +27,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODEJS_VERSION }}
|
node-version: ${{ env.NODEJS_VERSION }}
|
||||||
|
|
||||||
|
- name: Use Beta Toolchain
|
||||||
|
run: rustup default beta
|
||||||
|
|
||||||
|
- name: Setup Cross
|
||||||
|
run: cargo install cross --git https://github.com/cross-rs/cross
|
||||||
|
|
||||||
- name: Build And Run Tests
|
- name: Build And Run Tests
|
||||||
run: make test
|
run: make test
|
||||||
|
|||||||
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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
210
Makefile
210
Makefile
@@ -1,31 +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/containerbox 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) \
|
||||||
|
$(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
|
||||||
REBUILD_TYPES = 1
|
REBUILD_TYPES = 1
|
||||||
|
|
||||||
ifeq ($(REMOTE),)
|
ifeq ($(REMOTE),)
|
||||||
@@ -49,21 +63,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 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
|
||||||
|
|
||||||
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 +107,60 @@ 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:
|
cli:
|
||||||
cd core && ./install-cli.sh
|
./core/install-cli.sh
|
||||||
|
|
||||||
registry:
|
registry: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox
|
||||||
cd core && ./build-registrybox.sh
|
|
||||||
|
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)
|
||||||
|
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 +170,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 +200,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 +211,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 +226,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 +252,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,10 +266,14 @@ 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
|
||||||
|
|
||||||
@@ -234,53 +282,48 @@ sdk/base/lib/osBindings/index.ts: $(shell if [ "$(REBUILD_TYPES)" -ne 0 ]; then
|
|||||||
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
|
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/containerbox
|
||||||
ARCH=$(ARCH) ./container-runtime/update-image.sh
|
ARCH=$(ARCH) REQUIRES=linux ./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/containerbox: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||||
cd system-images/utils && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
|
||||||
|
|
||||||
system-images/binfmt/docker-images/$(ARCH).tar: $(BINFMT_SRC)
|
|
||||||
cd system-images/binfmt && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
|
||||||
|
|
||||||
core/target/$(ARCH)-unknown-linux-musl/release/startbox: $(CORE_SRC) $(COMPRESSED_WEB_UIS) web/patchdb-ui-seed.json $(ENVIRONMENT_FILE)
|
|
||||||
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
|
ARCH=$(ARCH) ./core/build-containerbox.sh
|
||||||
touch core/target/$(ARCH)-unknown-linux-musl/release/containerbox
|
touch core/target/$(RUST_ARCH)-unknown-linux-musl/release/containerbox
|
||||||
|
|
||||||
web/node_modules/.package-lock.json: web/package.json sdk/baseDist/package.json
|
web/package-lock.json: web/package.json sdk/baseDist/package.json
|
||||||
|
npm --prefix web i
|
||||||
|
touch web/package-lock.json
|
||||||
|
|
||||||
|
web/node_modules/.package-lock.json: web/package-lock.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 +341,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
|
||||||
@@ -329,8 +376,11 @@ ui: web/dist/raw/ui
|
|||||||
cargo-deps/aarch64-unknown-linux-musl/release/pi-beep:
|
cargo-deps/aarch64-unknown-linux-musl/release/pi-beep:
|
||||||
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:
|
||||||
ARCH=$(ARCH) PREINSTALL="apk add musl-dev pkgconfig" ./build-cargo-dep.sh tokio-console
|
ARCH=$(ARCH) PREINSTALL="apk add musl-dev pkgconfig" ./build-cargo-dep.sh tokio-console
|
||||||
|
|
||||||
cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs:
|
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs:
|
||||||
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) 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
|
||||||
|
|
||||||
|
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph:
|
||||||
|
ARCH=$(ARCH) PREINSTALL="apk add musl-dev pkgconfig" ./build-cargo-dep.sh flamegraph
|
||||||
@@ -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>
|
||||||
|
|||||||
63
START-TUNNEL.md
Normal file
63
START-TUNNEL.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# StartTunnel
|
||||||
|
|
||||||
|
A self-hosted Wiregaurd 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 (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. Install StartTunnel:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
TMP_DIR=$(mktemp -d) && (cd $TMP_DIR && wget https://github.com/Start9Labs/start-os/releases/download/v0.4.0-alpha.12/start-tunnel-0.4.0-alpha.12-68f401b_$(uname -m).deb && apt-get install -y ./start-tunnel-0.4.0-alpha.12-68f401b_$(uname -m).deb) && rm -rf $TMP_DIR && systemctl start start-tunneld && echo "Installation Succeeded"
|
||||||
|
```
|
||||||
|
|
||||||
|
5. [Initialize the web interface](#web-interface) (recommended)
|
||||||
|
|
||||||
|
## CLI
|
||||||
|
|
||||||
|
By default, StartTunnel is managed via the `start-tunnel` command line interface, which is self-documented.
|
||||||
|
|
||||||
|
```
|
||||||
|
start-tunnel --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Web Interface
|
||||||
|
|
||||||
|
If you choose to enable the web interface (recommended in most cases), StartTunnel can be accessed as a website from the browser, or programmatically via API.
|
||||||
|
|
||||||
|
1. Initialize the web interface.
|
||||||
|
|
||||||
|
start-tunnel web init
|
||||||
|
|
||||||
|
1. When prompted, select the IP address at which to host the web interface. In many cases, there will be only one IP address.
|
||||||
|
|
||||||
|
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 conflicts.
|
||||||
|
|
||||||
|
1. Select whether to autogenerate a self-signed certificate or provide your own certificate and key. If you choose to autogenerate, you will be asked to list all IP addresses and domains for which to sign the certificate. For example, if you intend to access your StartTunnel web UI at a domain, include the domain in the list.
|
||||||
|
|
||||||
|
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 using the CLI.
|
||||||
|
- Root Certificate Authority: the Root CA of your StartTunnel instance. If not already, trust it in your browser or system keychain.
|
||||||
@@ -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}"
|
||||||
@@ -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
|
||||||
|
|||||||
11
build/dpkg-deps/raspberrypi.depends
Normal file
11
build/dpkg-deps/raspberrypi.depends
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
- grub-common
|
||||||
|
- 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(.status.main == "running")] | 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
|
||||||
@@ -88,10 +96,10 @@ if [ "$CHROOT_RES" -eq 0 ]; then
|
|||||||
echo 'Upgrading...'
|
echo 'Upgrading...'
|
||||||
|
|
||||||
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 +111,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
|
||||||
|
|||||||
29
build/lib/scripts/forward-port
Executable file
29
build/lib/scripts/forward-port
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ -z "$sip" ] || [ -z "$dip" ] || [ -z "$sport" ] || [ -z "$dport" ]; then
|
||||||
|
>&2 echo 'missing required env var'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rule_exists() {
|
||||||
|
iptables -t nat -C "$@" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_rule() {
|
||||||
|
if [ "$UNDO" = "1" ]; then
|
||||||
|
if rule_exists "$@"; then
|
||||||
|
iptables -t nat -D "$@"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! rule_exists "$@"; then
|
||||||
|
iptables -t nat -A "$@"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_rule PREROUTING -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport
|
||||||
|
apply_rule OUTPUT -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport
|
||||||
|
|
||||||
|
if [ "$UNDO" = 1 ]; then
|
||||||
|
conntrack -D -p tcp -d $sip --dport $sport
|
||||||
|
fi
|
||||||
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
|
||||||
|
|||||||
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 dpkg -s grub-common 2>&1 > /dev/null; then
|
||||||
|
grub-install /dev/$(eval $(lsblk -o MOUNTPOINT,PKNAME -P | grep 'MOUNTPOINT="/media/startos/root"') && echo $PKNAME)
|
||||||
|
update-grub
|
||||||
|
fi
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sync
|
||||||
|
|
||||||
|
umount -R /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...'
|
||||||
56
build/os-compat/buildenv.Dockerfile
Normal file
56
build/os-compat/buildenv.Dockerfile
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
FROM debian:bookworm
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
ENV NVM_DIR=~/.nvm
|
||||||
|
ENV NODE_VERSION=22
|
||||||
|
RUN mkdir -p $NVM_DIR && \
|
||||||
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash && \
|
||||||
|
. $NVM_DIR/nvm.sh \
|
||||||
|
nvm install $NODE_VERSION && \
|
||||||
|
nvm use $NODE_VERSION && \
|
||||||
|
nvm alias default $NODE_VERSION && \
|
||||||
|
ln -s $(which node) /usr/bin/node && \
|
||||||
|
ln -s $(which npm) /usr/bin/npm
|
||||||
|
|
||||||
|
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 ); 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
|
||||||
|
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,11 @@
|
|||||||
[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
|
ExecStart=/usr/bin/node --experimental-detect-module --trace-warnings --unhandled-rejections=warn /usr/lib/startos/init/index.js
|
||||||
Restart=always
|
Restart=no
|
||||||
RestartSec=3
|
|
||||||
|
|
||||||
[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(
|
||||||
@@ -293,15 +298,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 +309,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)
|
||||||
|
|
||||||
@@ -171,6 +154,8 @@ 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)
|
||||||
|
|
||||||
this.unixSocketServer.on("connection", (s) => {
|
this.unixSocketServer.on("connection", (s) => {
|
||||||
@@ -238,21 +223,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 +242,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 +256,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 +272,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 +284,35 @@ export class RpcListener {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(stopType, async ({ id }) => {
|
.when(stopType, async ({ id }) => {
|
||||||
this.removeCallbackHolderFor("main")
|
this.callbacks?.removeChild("main")
|
||||||
return handleRpc(
|
return handleRpc(
|
||||||
id,
|
id,
|
||||||
this.system.stop().then((result) => ({ result })),
|
this.system.stop().then((result) => ({ 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,
|
||||||
|
)
|
||||||
|
}
|
||||||
})().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 +320,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 +365,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 +399,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 +408,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 +418,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 +439,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"],
|
filetype: "directory",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.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()
|
||||||
|
|||||||
@@ -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(
|
||||||
@@ -441,7 +494,7 @@ export class SystemForEmbassy implements System {
|
|||||||
masked: false,
|
masked: false,
|
||||||
path: "",
|
path: "",
|
||||||
schemeOverride: null,
|
schemeOverride: null,
|
||||||
search: {},
|
query: {},
|
||||||
username: null,
|
username: null,
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
@@ -456,13 +509,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 +601,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 +635,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 +668,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 +724,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 +778,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 +796,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 +820,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,7 +882,7 @@ export class SystemForEmbassy implements System {
|
|||||||
})) as any
|
})) as any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { configured: true }
|
return null
|
||||||
}
|
}
|
||||||
async properties(
|
async properties(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
@@ -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: {
|
filetype: "directory",
|
||||||
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,
|
||||||
|
filetype: "directory",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
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!.localHostnames[0] ||
|
||||||
filled.addressInfo!.onionHostnames[0]
|
filled.addressInfo!.onionHostnames[0]
|
||||||
: filled.addressInfo!.onionHostnames[0] ||
|
: filled.addressInfo!.onionHostnames[0] ||
|
||||||
filled.addressInfo!.localHostnames[0],
|
filled.addressInfo!.localHostnames[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,31 @@ 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(() => effects.restart())
|
||||||
await effects.setMainStatus({ status: "running" })
|
let mainOnTerm: () => Promise<void> | undefined
|
||||||
mainOnTerm = onTerm
|
const started = async (onTerm: () => Promise<void>) => {
|
||||||
return null
|
await effects.setMainStatus({ status: "running" })
|
||||||
}
|
mainOnTerm = onTerm
|
||||||
const daemons = await (
|
return null
|
||||||
await this.abi.main({
|
}
|
||||||
effects,
|
const daemons = await (
|
||||||
started,
|
await this.abi.main({
|
||||||
})
|
effects,
|
||||||
).build()
|
started,
|
||||||
this.runningMain = {
|
})
|
||||||
stop: async () => {
|
).build()
|
||||||
if (mainOnTerm) await mainOnTerm()
|
this.runningMain = {
|
||||||
await daemons.term()
|
stop: async () => {
|
||||||
},
|
if (mainOnTerm) await mainOnTerm()
|
||||||
|
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
|
||||||
|
|||||||
@@ -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,7 +18,11 @@ 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
|
||||||
@@ -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/containerbox 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
|
||||||
|
|||||||
7246
core/Cargo.lock
generated
7246
core/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
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,65 @@
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
PROFILE=${PROFILE:-release}
|
||||||
ARCH=$(uname -m)
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
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')"
|
|
||||||
RUSTFLAGS=""
|
|
||||||
|
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
# Ensure GIT_HASH.txt exists if not created by higher-level build steps
|
||||||
RUSTFLAGS="--cfg tokio_unstable"
|
if [ ! -f GIT_HASH.txt ] && command -v git >/dev/null 2>&1; then
|
||||||
|
git rev-parse HEAD > GIT_HASH.txt || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if which zig > /dev/null && [ "$ENFORCE_USE_DOCKER" != 1 ]; do
|
FEATURES="$(echo "${ENVIRONMENT:-}" | sed 's/-/,/g')"
|
||||||
echo "FEATURES=\"$FEATURES\""
|
FEATURE_ARGS="cli"
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
if [ -n "$FEATURES" ]; then
|
||||||
RUSTFLAGS=$RUSTFLAGS sh -c "cd core && cargo zigbuild --release --no-default-features --features cli,$FEATURES --locked --bin start-cli --target=$TARGET"
|
FEATURE_ARGS="$FEATURE_ARGS,$FEATURES"
|
||||||
else
|
fi
|
||||||
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'
|
|
||||||
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
|
RUSTFLAGS=""
|
||||||
rust-zig-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
if [[ "${ENVIRONMENT:-}" =~ (^|-)console($|-) ]]; then
|
||||||
fi
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "FEATURES=\"$FEATURES\""
|
||||||
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
|
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features $FEATURE_ARGS --locked --bin start-cli --target=$TARGET
|
||||||
|
if [ "$(ls -nd "core/target/$TARGET/release/start-cli" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
|
rust-zig-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
||||||
fi
|
fi
|
||||||
@@ -2,9 +2,16 @@
|
|||||||
|
|
||||||
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"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
@@ -13,24 +20,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 container-runtime,$FEATURES --locked --bin containerbox --target=$ARCH-unknown-linux-musl"
|
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-container,$FEATURES --locked --bin containerbox --target=$RUST_ARCH-unknown-linux-musl
|
||||||
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/containerbox | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd "core/target/$RUST_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"
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||||
fi
|
fi
|
||||||
@@ -2,9 +2,16 @@
|
|||||||
|
|
||||||
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"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
@@ -13,24 +20,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 --no-default-features --features cli-registry,registry,$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/release/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 /root/.cargo"
|
||||||
fi
|
fi
|
||||||
@@ -2,9 +2,16 @@
|
|||||||
|
|
||||||
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"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
@@ -13,24 +20,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 --no-default-features --features cli,startd,$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/release/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 /root/.cargo"
|
||||||
fi
|
fi
|
||||||
@@ -2,35 +2,38 @@
|
|||||||
|
|
||||||
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"
|
||||||
|
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 --no-default-features --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 /root/.cargo"
|
||||||
fi
|
fi
|
||||||
41
core/build-tunnelbox.sh
Executable file
41
core/build-tunnelbox.sh
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/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"
|
||||||
|
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 --no-default-features --features cli-tunnel,tunnel,$FEATURES --locked --bin tunnelbox --target=$RUST_ARCH-unknown-linux-musl
|
||||||
|
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/release/tunnelbox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.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 "CFLAGS=-D_FORTIFY_SOURCE=2" -e "CXXFLAGS=-D_FORTIFY_SOURCE=2" -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":/root/.cargo/git -v "$HOME/.cache/sccache":/root/.cache/sccache -v "$(pwd)":/workdir -w /workdir -P start9/cargo-zigbuild'
|
||||||
82
core/helpers/src/os_api.rs
Normal file
82
core/helpers/src/os_api.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use color_eyre::Report;
|
||||||
|
use models::InterfaceId;
|
||||||
|
use models::PackageId;
|
||||||
|
use serde_json::Value;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
pub struct RuntimeDropped;
|
||||||
|
|
||||||
|
pub struct Callback {
|
||||||
|
id: Arc<String>,
|
||||||
|
sender: mpsc::UnboundedSender<(Arc<String>, Vec<Value>)>,
|
||||||
|
}
|
||||||
|
impl Callback {
|
||||||
|
pub fn new(id: String, sender: mpsc::UnboundedSender<(Arc<String>, Vec<Value>)>) -> Self {
|
||||||
|
Self {
|
||||||
|
id: Arc::new(id),
|
||||||
|
sender,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn is_listening(&self) -> bool {
|
||||||
|
self.sender.is_closed()
|
||||||
|
}
|
||||||
|
pub fn call(&self, args: Vec<Value>) -> Result<(), RuntimeDropped> {
|
||||||
|
self.sender
|
||||||
|
.send((self.id.clone(), args))
|
||||||
|
.map_err(|_| RuntimeDropped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct AddressSchemaOnion {
|
||||||
|
pub id: InterfaceId,
|
||||||
|
pub external_port: u16,
|
||||||
|
}
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct AddressSchemaLocal {
|
||||||
|
pub id: InterfaceId,
|
||||||
|
pub external_port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Address(pub String);
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Domain;
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Name;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub trait OsApi: Send + Sync + 'static {
|
||||||
|
async fn get_service_config(
|
||||||
|
&self,
|
||||||
|
id: PackageId,
|
||||||
|
path: &str,
|
||||||
|
callback: Option<Callback>,
|
||||||
|
) -> Result<Vec<Value>, Report>;
|
||||||
|
|
||||||
|
async fn bind_local(
|
||||||
|
&self,
|
||||||
|
internal_port: u16,
|
||||||
|
address_schema: AddressSchemaLocal,
|
||||||
|
) -> Result<Address, Report>;
|
||||||
|
async fn bind_onion(
|
||||||
|
&self,
|
||||||
|
internal_port: u16,
|
||||||
|
address_schema: AddressSchemaOnion,
|
||||||
|
) -> Result<Address, Report>;
|
||||||
|
|
||||||
|
async fn unbind_local(&self, id: InterfaceId, external: u16) -> Result<(), Report>;
|
||||||
|
async fn unbind_onion(&self, id: InterfaceId, external: u16) -> Result<(), Report>;
|
||||||
|
fn set_started(&self) -> Result<(), Report>;
|
||||||
|
async fn restart(&self) -> Result<(), Report>;
|
||||||
|
async fn start(&self) -> Result<(), Report>;
|
||||||
|
async fn stop(&self) -> Result<(), Report>;
|
||||||
|
}
|
||||||
@@ -16,4 +16,4 @@ if [ "$PLATFORM" = "arm64" ]; then
|
|||||||
PLATFORM="aarch64"
|
PLATFORM="aarch64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cargo install --path=./startos --no-default-features --features=cli,docker,registry --bin start-cli --locked
|
cargo install --path=./startos --no-default-features --features=cli,docker --bin start-cli --locked
|
||||||
|
|||||||
@@ -1,43 +1,46 @@
|
|||||||
[package]
|
[package]
|
||||||
|
edition = "2021"
|
||||||
name = "models"
|
name = "models"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
arti = ["arti-client"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = "0.7.5"
|
arti-client = { version = "0.33", default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
base64 = "0.21.4"
|
axum = "0.8.4"
|
||||||
|
base64 = "0.22.1"
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
ed25519-dalek = { version = "2.0.0", features = ["serde"] }
|
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 = [
|
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
|
gpt = "4.1.0"
|
||||||
ipnet = "2.8.0"
|
ipnet = "2.8.0"
|
||||||
|
lazy_static = "1.4"
|
||||||
|
lettre = { version = "0.11", default-features = false }
|
||||||
|
mbrman = "0.6.0"
|
||||||
|
miette = "7.6.0"
|
||||||
num_enum = "0.7.1"
|
num_enum = "0.7.1"
|
||||||
openssl = { version = "0.10.57", features = ["vendored"] }
|
openssl = { version = "0.10.57", features = ["vendored"] }
|
||||||
patch-db = { version = "*", path = "../../patch-db/patch-db", features = [
|
patch-db = { version = "*", path = "../../patch-db/patch-db", features = [
|
||||||
"trace",
|
"trace",
|
||||||
] }
|
] }
|
||||||
rand = "0.8.5"
|
rand = "0.9.1"
|
||||||
regex = "1.10.2"
|
regex = "1.10.2"
|
||||||
reqwest = "0.12"
|
reqwest = "0.12"
|
||||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" }
|
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" }
|
||||||
rustls = "0.23"
|
rustls = "0.23"
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
sqlx = { version = "0.7.2", features = [
|
|
||||||
"chrono",
|
|
||||||
"runtime-tokio-rustls",
|
|
||||||
"postgres",
|
|
||||||
] }
|
|
||||||
ssh-key = "0.6.2"
|
ssh-key = "0.6.2"
|
||||||
ts-rs = { git = "https://github.com/dr-bonez/ts-rs.git", branch = "feature/top-level-as" } # "8"
|
thiserror = "2.0"
|
||||||
thiserror = "1.0"
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
torut = { git = "https://github.com/Start9Labs/torut.git", branch = "update/dependencies" }
|
torut = "0.2.1"
|
||||||
tracing = "0.1.39"
|
tracing = "0.1.39"
|
||||||
yasi = "0.1.5"
|
ts-rs = "9"
|
||||||
|
typeid = "1"
|
||||||
|
yasi = { version = "0.1.6", features = ["serde", "ts-rs"] }
|
||||||
zbus = "5"
|
zbus = "5"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type ServiceInterfaceId = string;
|
export type ServiceInterfaceId = string;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
@@ -14,28 +15,26 @@ use crate::{mime, Error, ErrorKind, ResultExt};
|
|||||||
#[derive(Clone, TS)]
|
#[derive(Clone, TS)]
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub struct DataUrl<'a> {
|
pub struct DataUrl<'a> {
|
||||||
mime: InternedString,
|
pub mime: InternedString,
|
||||||
data: Cow<'a, [u8]>,
|
pub data: Cow<'a, [u8]>,
|
||||||
}
|
}
|
||||||
impl<'a> DataUrl<'a> {
|
impl<'a> DataUrl<'a> {
|
||||||
pub const DEFAULT_MIME: &'static str = "application/octet-stream";
|
pub const DEFAULT_MIME: &'static str = "application/octet-stream";
|
||||||
pub const MAX_SIZE: u64 = 100 * 1024;
|
pub const MAX_SIZE: u64 = 100 * 1024;
|
||||||
|
|
||||||
// data:{mime};base64,{data}
|
fn to_string(&self) -> String {
|
||||||
pub fn to_string(&self) -> String {
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let mut res = String::with_capacity(self.data_url_len_without_mime() + self.mime.len());
|
let mut res = String::with_capacity(self.len());
|
||||||
let _ = write!(res, "data:{};base64,", self.mime);
|
write!(&mut res, "{self}").unwrap();
|
||||||
base64::engine::general_purpose::STANDARD.encode_string(&self.data, &mut res);
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_url_len_without_mime(&self) -> usize {
|
fn len_without_mime(&self) -> usize {
|
||||||
5 + 8 + (4 * self.data.len() / 3) + 3
|
5 + 8 + (4 * self.data.len() / 3) + 3
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data_url_len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.data_url_len_without_mime() + self.mime.len()
|
self.len_without_mime() + self.mime.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_slice(mime: &str, data: &'a [u8]) -> Self {
|
pub fn from_slice(mime: &str, data: &'a [u8]) -> Self {
|
||||||
@@ -44,6 +43,10 @@ impl<'a> DataUrl<'a> {
|
|||||||
data: Cow::Borrowed(data),
|
data: Cow::Borrowed(data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn canonical_ext(&self) -> Option<&'static str> {
|
||||||
|
mime::unmime(&self.mime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl DataUrl<'static> {
|
impl DataUrl<'static> {
|
||||||
pub async fn from_reader(
|
pub async fn from_reader(
|
||||||
@@ -109,12 +112,57 @@ impl DataUrl<'static> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> std::fmt::Display for DataUrl<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"data:{};base64,{}",
|
||||||
|
self.mime,
|
||||||
|
base64::display::Base64Display::new(
|
||||||
|
&*self.data,
|
||||||
|
&base64::engine::general_purpose::STANDARD
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<'a> std::fmt::Debug for DataUrl<'a> {
|
impl<'a> std::fmt::Debug for DataUrl<'a> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(&self.to_string())
|
std::fmt::Display::fmt(self, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DataUrlParseError;
|
||||||
|
impl std::fmt::Display for DataUrlParseError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "invalid base64 url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for DataUrlParseError {}
|
||||||
|
impl From<DataUrlParseError> for Error {
|
||||||
|
fn from(e: DataUrlParseError) -> Self {
|
||||||
|
Error::new(e, ErrorKind::ParseUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for DataUrl<'static> {
|
||||||
|
type Err = DataUrlParseError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
s.strip_prefix("data:")
|
||||||
|
.and_then(|v| v.split_once(";base64,"))
|
||||||
|
.and_then(|(mime, data)| {
|
||||||
|
Some(DataUrl {
|
||||||
|
mime: InternedString::intern(mime),
|
||||||
|
data: Cow::Owned(
|
||||||
|
base64::engine::general_purpose::STANDARD
|
||||||
|
.decode(data)
|
||||||
|
.ok()?,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.ok_or(DataUrlParseError)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<'de> Deserialize<'de> for DataUrl<'static> {
|
impl<'de> Deserialize<'de> for DataUrl<'static> {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
@@ -130,21 +178,9 @@ impl<'de> Deserialize<'de> for DataUrl<'static> {
|
|||||||
where
|
where
|
||||||
E: serde::de::Error,
|
E: serde::de::Error,
|
||||||
{
|
{
|
||||||
v.strip_prefix("data:")
|
v.parse().map_err(|_| {
|
||||||
.and_then(|v| v.split_once(";base64,"))
|
E::invalid_value(serde::de::Unexpected::Str(v), &"a valid base64 data url")
|
||||||
.and_then(|(mime, data)| {
|
})
|
||||||
Some(DataUrl {
|
|
||||||
mime: InternedString::intern(mime),
|
|
||||||
data: Cow::Owned(
|
|
||||||
base64::engine::general_purpose::STANDARD
|
|
||||||
.decode(data)
|
|
||||||
.ok()?,
|
|
||||||
),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.ok_or_else(|| {
|
|
||||||
E::invalid_value(serde::de::Unexpected::Str(v), &"a valid base64 data url")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deserializer.deserialize_any(Visitor)
|
deserializer.deserialize_any(Visitor)
|
||||||
@@ -168,6 +204,6 @@ fn doesnt_reallocate() {
|
|||||||
mime: InternedString::intern("png"),
|
mime: InternedString::intern("png"),
|
||||||
data: Cow::Borrowed(&random[..i]),
|
data: Cow::Borrowed(&random[..i]),
|
||||||
};
|
};
|
||||||
assert_eq!(icon.to_string().capacity(), icon.data_url_len());
|
assert_eq!(icon.to_string().capacity(), icon.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use rpc_toolkit::yajrc::{
|
|||||||
RpcError, INVALID_PARAMS_ERROR, INVALID_REQUEST_ERROR, METHOD_NOT_FOUND_ERROR, PARSE_ERROR,
|
RpcError, INVALID_PARAMS_ERROR, INVALID_REQUEST_ERROR, METHOD_NOT_FOUND_ERROR, PARSE_ERROR,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
use crate::InvalidId;
|
use crate::InvalidId;
|
||||||
|
|
||||||
@@ -91,6 +92,9 @@ pub enum ErrorKind {
|
|||||||
Cancelled = 73,
|
Cancelled = 73,
|
||||||
Git = 74,
|
Git = 74,
|
||||||
DBus = 75,
|
DBus = 75,
|
||||||
|
InstallFailed = 76,
|
||||||
|
UpdateFailed = 77,
|
||||||
|
Smtp = 78,
|
||||||
}
|
}
|
||||||
impl ErrorKind {
|
impl ErrorKind {
|
||||||
pub fn as_str(&self) -> &'static str {
|
pub fn as_str(&self) -> &'static str {
|
||||||
@@ -171,6 +175,9 @@ impl ErrorKind {
|
|||||||
Cancelled => "Cancelled",
|
Cancelled => "Cancelled",
|
||||||
Git => "Git Error",
|
Git => "Git Error",
|
||||||
DBus => "DBus Error",
|
DBus => "DBus Error",
|
||||||
|
InstallFailed => "Install Failed",
|
||||||
|
UpdateFailed => "Update Failed",
|
||||||
|
Smtp => "SMTP Error",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,37 +187,64 @@ impl Display for ErrorKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
pub source: color_eyre::eyre::Error,
|
pub source: color_eyre::eyre::Error,
|
||||||
|
pub debug: Option<color_eyre::eyre::Error>,
|
||||||
pub kind: ErrorKind,
|
pub kind: ErrorKind,
|
||||||
pub revision: Option<Revision>,
|
pub revision: Option<Revision>,
|
||||||
|
pub task: Option<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Error {
|
impl Display for Error {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}: {}", self.kind.as_str(), self.source)
|
write!(f, "{}: {:#}", self.kind.as_str(), self.source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Debug for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}: {:?}",
|
||||||
|
self.kind.as_str(),
|
||||||
|
self.debug.as_ref().unwrap_or(&self.source)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Error {
|
impl Error {
|
||||||
pub fn new<E: Into<color_eyre::eyre::Error>>(source: E, kind: ErrorKind) -> Self {
|
pub fn new<E: Into<color_eyre::eyre::Error> + std::fmt::Debug + 'static>(
|
||||||
|
source: E,
|
||||||
|
kind: ErrorKind,
|
||||||
|
) -> Self {
|
||||||
|
let debug = (typeid::of::<E>() == typeid::of::<color_eyre::eyre::Error>())
|
||||||
|
.then(|| eyre!("{source:?}"));
|
||||||
Error {
|
Error {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
debug,
|
||||||
kind,
|
kind,
|
||||||
revision: None,
|
revision: None,
|
||||||
|
task: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn clone_output(&self) -> Self {
|
pub fn clone_output(&self) -> Self {
|
||||||
Error {
|
Error {
|
||||||
source: ErrorData {
|
source: eyre!("{}", self.source),
|
||||||
details: format!("{}", self.source),
|
debug: self.debug.as_ref().map(|e| eyre!("{e}")),
|
||||||
debug: format!("{:?}", self.source),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
kind: self.kind,
|
kind: self.kind,
|
||||||
revision: self.revision.clone(),
|
revision: self.revision.clone(),
|
||||||
|
task: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn with_task(mut self, task: JoinHandle<()>) -> Self {
|
||||||
|
self.task = Some(task);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub async fn wait(mut self) -> Self {
|
||||||
|
if let Some(task) = &mut self.task {
|
||||||
|
task.await.log_err();
|
||||||
|
}
|
||||||
|
self.task.take();
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl axum::response::IntoResponse for Error {
|
impl axum::response::IntoResponse for Error {
|
||||||
fn into_response(self) -> axum::response::Response {
|
fn into_response(self) -> axum::response::Response {
|
||||||
@@ -269,11 +303,6 @@ impl From<patch_db::Error> for Error {
|
|||||||
Error::new(e, ErrorKind::Database)
|
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 {
|
impl From<ed25519_dalek::SignatureError> for Error {
|
||||||
fn from(e: ed25519_dalek::SignatureError) -> Self {
|
fn from(e: ed25519_dalek::SignatureError) -> Self {
|
||||||
Error::new(e, ErrorKind::InvalidSignature)
|
Error::new(e, ErrorKind::InvalidSignature)
|
||||||
@@ -284,11 +313,6 @@ impl From<std::net::AddrParseError> for Error {
|
|||||||
Error::new(e, ErrorKind::ParseNetAddress)
|
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 {
|
impl From<ipnet::AddrParseError> for Error {
|
||||||
fn from(e: ipnet::AddrParseError) -> Self {
|
fn from(e: ipnet::AddrParseError) -> Self {
|
||||||
Error::new(e, ErrorKind::ParseNetAddress)
|
Error::new(e, ErrorKind::ParseNetAddress)
|
||||||
@@ -304,6 +328,16 @@ impl From<mbrman::Error> for Error {
|
|||||||
Error::new(e, ErrorKind::DiskManagement)
|
Error::new(e, ErrorKind::DiskManagement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl From<gpt::GptError> for Error {
|
||||||
|
fn from(e: gpt::GptError) -> Self {
|
||||||
|
Error::new(e, ErrorKind::DiskManagement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<gpt::mbr::MBRError> for Error {
|
||||||
|
fn from(e: gpt::mbr::MBRError) -> Self {
|
||||||
|
Error::new(e, ErrorKind::DiskManagement)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl From<InvalidUri> for Error {
|
impl From<InvalidUri> for Error {
|
||||||
fn from(e: InvalidUri) -> Self {
|
fn from(e: InvalidUri) -> Self {
|
||||||
Error::new(eyre!("{}", e), ErrorKind::ParseUrl)
|
Error::new(eyre!("{}", e), ErrorKind::ParseUrl)
|
||||||
@@ -324,8 +358,14 @@ impl From<reqwest::Error> for Error {
|
|||||||
Error::new(e, kind)
|
Error::new(e, kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<torut::onion::OnionAddressParseError> for Error {
|
#[cfg(feature = "arti")]
|
||||||
fn from(e: torut::onion::OnionAddressParseError) -> Self {
|
impl From<arti_client::Error> for Error {
|
||||||
|
fn from(e: arti_client::Error) -> Self {
|
||||||
|
Error::new(e, ErrorKind::Tor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<torut::control::ConnError> for Error {
|
||||||
|
fn from(e: torut::control::ConnError) -> Self {
|
||||||
Error::new(e, ErrorKind::Tor)
|
Error::new(e, ErrorKind::Tor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,6 +379,21 @@ impl From<rustls::Error> for Error {
|
|||||||
Error::new(e, ErrorKind::OpenSsl)
|
Error::new(e, ErrorKind::OpenSsl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl From<lettre::error::Error> for Error {
|
||||||
|
fn from(e: lettre::error::Error) -> Self {
|
||||||
|
Error::new(e, ErrorKind::Smtp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<lettre::transport::smtp::Error> for Error {
|
||||||
|
fn from(e: lettre::transport::smtp::Error) -> Self {
|
||||||
|
Error::new(e, ErrorKind::Smtp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<lettre::address::AddressError> for Error {
|
||||||
|
fn from(e: lettre::address::AddressError) -> Self {
|
||||||
|
Error::new(e, ErrorKind::Smtp)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl From<patch_db::value::Error> for Error {
|
impl From<patch_db::value::Error> for Error {
|
||||||
fn from(value: patch_db::value::Error) -> Self {
|
fn from(value: patch_db::value::Error) -> Self {
|
||||||
match value.kind {
|
match value.kind {
|
||||||
@@ -520,25 +575,26 @@ where
|
|||||||
impl<T, E> ResultExt<T, E> for Result<T, E>
|
impl<T, E> ResultExt<T, E> for Result<T, E>
|
||||||
where
|
where
|
||||||
color_eyre::eyre::Error: From<E>,
|
color_eyre::eyre::Error: From<E>,
|
||||||
|
E: std::fmt::Debug + 'static,
|
||||||
{
|
{
|
||||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
||||||
self.map_err(|e| Error {
|
self.map_err(|e| Error::new(e, kind))
|
||||||
source: e.into(),
|
|
||||||
kind,
|
|
||||||
revision: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_ctx<F: FnOnce(&E) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
fn with_ctx<F: FnOnce(&E) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
||||||
self.map_err(|e| {
|
self.map_err(|e| {
|
||||||
let (kind, ctx) = f(&e);
|
let (kind, ctx) = f(&e);
|
||||||
|
let debug = (typeid::of::<E>() == typeid::of::<color_eyre::eyre::Error>())
|
||||||
|
.then(|| eyre!("{ctx}: {e:?}"));
|
||||||
let source = color_eyre::eyre::Error::from(e);
|
let source = color_eyre::eyre::Error::from(e);
|
||||||
let ctx = format!("{}: {}", ctx, source);
|
let with_ctx = format!("{ctx}: {source}");
|
||||||
let source = source.wrap_err(ctx);
|
let source = source.wrap_err(with_ctx);
|
||||||
Error {
|
Error {
|
||||||
kind,
|
kind,
|
||||||
source,
|
source,
|
||||||
|
debug,
|
||||||
revision: None,
|
revision: None,
|
||||||
|
task: None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -557,23 +613,24 @@ where
|
|||||||
}
|
}
|
||||||
impl<T> ResultExt<T, Error> for Result<T, Error> {
|
impl<T> ResultExt<T, Error> for Result<T, Error> {
|
||||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
||||||
self.map_err(|e| Error {
|
self.map_err(|e| Error { kind, ..e })
|
||||||
source: e.source,
|
|
||||||
kind,
|
|
||||||
revision: e.revision,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_ctx<F: FnOnce(&Error) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
fn with_ctx<F: FnOnce(&Error) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
||||||
self.map_err(|e| {
|
self.map_err(|e| {
|
||||||
let (kind, ctx) = f(&e);
|
let (kind, ctx) = f(&e);
|
||||||
let source = e.source;
|
let source = e.source;
|
||||||
let ctx = format!("{}: {}", ctx, source);
|
let with_ctx = format!("{ctx}: {source}");
|
||||||
let source = source.wrap_err(ctx);
|
let source = source.wrap_err(with_ctx);
|
||||||
|
let debug = e.debug.map(|e| {
|
||||||
|
let with_ctx = format!("{ctx}: {e}");
|
||||||
|
e.wrap_err(with_ctx)
|
||||||
|
});
|
||||||
Error {
|
Error {
|
||||||
kind,
|
kind,
|
||||||
source,
|
source,
|
||||||
revision: e.revision,
|
debug,
|
||||||
|
..e
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
60
core/models/src/id/gateway.rs
Normal file
60
core/models/src/id/gateway.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use std::convert::Infallible;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ts_rs::TS;
|
||||||
|
use yasi::InternedString;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)]
|
||||||
|
#[ts(type = "string")]
|
||||||
|
pub struct GatewayId(InternedString);
|
||||||
|
impl GatewayId {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&*self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<InternedString> for GatewayId {
|
||||||
|
fn from(value: InternedString) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<GatewayId> for InternedString {
|
||||||
|
fn from(value: GatewayId) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FromStr for GatewayId {
|
||||||
|
type Err = Infallible;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(GatewayId(InternedString::intern(s)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<GatewayId> for GatewayId {
|
||||||
|
fn as_ref(&self) -> &GatewayId {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for GatewayId {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", &self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<str> for GatewayId {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<Path> for GatewayId {
|
||||||
|
fn as_ref(&self) -> &Path {
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'de> Deserialize<'de> for GatewayId {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Ok(GatewayId(serde::Deserialize::deserialize(deserializer)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,20 +60,3 @@ impl AsRef<Path> for HostId {
|
|||||||
self.0.as_ref().as_ref()
|
self.0.as_ref().as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for HostId {
|
|
||||||
fn encode_by_ref(
|
|
||||||
&self,
|
|
||||||
buf: &mut <sqlx::Postgres as sqlx::database::HasArguments<'q>>::ArgumentBuffer,
|
|
||||||
) -> sqlx::encode::IsNull {
|
|
||||||
<&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl sqlx::Type<sqlx::Postgres> for HostId {
|
|
||||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::type_info()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::compatible(ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|||||||
use yasi::InternedString;
|
use yasi::InternedString;
|
||||||
|
|
||||||
mod action;
|
mod action;
|
||||||
|
mod gateway;
|
||||||
mod health_check;
|
mod health_check;
|
||||||
mod host;
|
mod host;
|
||||||
mod image;
|
mod image;
|
||||||
@@ -16,6 +17,7 @@ mod service_interface;
|
|||||||
mod volume;
|
mod volume;
|
||||||
|
|
||||||
pub use action::ActionId;
|
pub use action::ActionId;
|
||||||
|
pub use gateway::GatewayId;
|
||||||
pub use health_check::HealthCheckId;
|
pub use health_check::HealthCheckId;
|
||||||
pub use host::HostId;
|
pub use host::HostId;
|
||||||
pub use image::ImageId;
|
pub use image::ImageId;
|
||||||
@@ -116,20 +118,3 @@ impl Serialize for Id {
|
|||||||
serializer.serialize_str(self)
|
serializer.serialize_str(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for Id {
|
|
||||||
fn encode_by_ref(
|
|
||||||
&self,
|
|
||||||
buf: &mut <sqlx::Postgres as sqlx::database::HasArguments<'q>>::ArgumentBuffer,
|
|
||||||
) -> sqlx::encode::IsNull {
|
|
||||||
<&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl sqlx::Type<sqlx::Postgres> for Id {
|
|
||||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::type_info()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::compatible(ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -87,20 +87,3 @@ impl Serialize for PackageId {
|
|||||||
Serialize::serialize(&self.0, serializer)
|
Serialize::serialize(&self.0, serializer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for PackageId {
|
|
||||||
fn encode_by_ref(
|
|
||||||
&self,
|
|
||||||
buf: &mut <sqlx::Postgres as sqlx::database::HasArguments<'q>>::ArgumentBuffer,
|
|
||||||
) -> sqlx::encode::IsNull {
|
|
||||||
<&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl sqlx::Type<sqlx::Postgres> for PackageId {
|
|
||||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::type_info()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::compatible(ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ use yasi::InternedString;
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)]
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub struct ReplayId(InternedString);
|
pub struct ReplayId(InternedString);
|
||||||
|
impl<T> From<T> for ReplayId
|
||||||
|
where
|
||||||
|
T: Into<InternedString>,
|
||||||
|
{
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
impl FromStr for ReplayId {
|
impl FromStr for ReplayId {
|
||||||
type Err = Infallible;
|
type Err = Infallible;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
|||||||
@@ -44,23 +44,6 @@ impl AsRef<Path> for ServiceInterfaceId {
|
|||||||
self.0.as_ref().as_ref()
|
self.0.as_ref().as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for ServiceInterfaceId {
|
|
||||||
fn encode_by_ref(
|
|
||||||
&self,
|
|
||||||
buf: &mut <sqlx::Postgres as sqlx::database::HasArguments<'q>>::ArgumentBuffer,
|
|
||||||
) -> sqlx::encode::IsNull {
|
|
||||||
<&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl sqlx::Type<sqlx::Postgres> for ServiceInterfaceId {
|
|
||||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::type_info()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::compatible(ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FromStr for ServiceInterfaceId {
|
impl FromStr for ServiceInterfaceId {
|
||||||
type Err = <Id as FromStr>::Err;
|
type Err = <Id as FromStr>::Err;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::Id;
|
use crate::{Id, InvalidId};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, TS)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, TS)]
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
@@ -12,6 +13,15 @@ pub enum VolumeId {
|
|||||||
Backup,
|
Backup,
|
||||||
Custom(Id),
|
Custom(Id),
|
||||||
}
|
}
|
||||||
|
impl FromStr for VolumeId {
|
||||||
|
type Err = InvalidId;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"BACKUP" => VolumeId::Backup,
|
||||||
|
s => VolumeId::Custom(Id::try_from(s.to_owned())?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
impl std::fmt::Display for VolumeId {
|
impl std::fmt::Display for VolumeId {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,25 +4,15 @@ use crate::ActionId;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum ProcedureName {
|
pub enum ProcedureName {
|
||||||
GetConfig,
|
|
||||||
SetConfig,
|
|
||||||
CreateBackup,
|
CreateBackup,
|
||||||
RestoreBackup,
|
|
||||||
GetActionInput(ActionId),
|
GetActionInput(ActionId),
|
||||||
RunAction(ActionId),
|
RunAction(ActionId),
|
||||||
PackageInit,
|
|
||||||
PackageUninit,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcedureName {
|
impl ProcedureName {
|
||||||
pub fn js_function_name(&self) -> String {
|
pub fn js_function_name(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
ProcedureName::PackageInit => "/packageInit".to_string(),
|
|
||||||
ProcedureName::PackageUninit => "/packageUninit".to_string(),
|
|
||||||
ProcedureName::SetConfig => "/config/set".to_string(),
|
|
||||||
ProcedureName::GetConfig => "/config/get".to_string(),
|
|
||||||
ProcedureName::CreateBackup => "/backup/create".to_string(),
|
ProcedureName::CreateBackup => "/backup/create".to_string(),
|
||||||
ProcedureName::RestoreBackup => "/backup/restore".to_string(),
|
|
||||||
ProcedureName::RunAction(id) => format!("/actions/{}/run", id),
|
ProcedureName::RunAction(id) => format!("/actions/{}/run", id),
|
||||||
ProcedureName::GetActionInput(id) => format!("/actions/{}/getInput", id),
|
ProcedureName::GetActionInput(id) => format!("/actions/{}/getInput", id),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
|
|||||||
set -ea
|
set -ea
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
|
PROFILE=${PROFILE:-release}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
@@ -22,15 +27,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'
|
source ./core/builder-alias.sh
|
||||||
|
|
||||||
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"
|
cross test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=test,$FEATURES --workspace --locked --target=$ARCH-unknown-linux-musl -- --skip export_bindings_
|
||||||
if [ "$(ls -nd core/target | 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,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.12" # VERSION_BUMP
|
||||||
license = "MIT"
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "startos"
|
name = "startos"
|
||||||
@@ -37,33 +37,65 @@ path = "src/main.rs"
|
|||||||
name = "registrybox"
|
name = "registrybox"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tunnelbox"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
cli = []
|
arti = [
|
||||||
container-runtime = ["procfs", "tty-spawn"]
|
"arti-client",
|
||||||
daemon = ["mail-send"]
|
"models/arti",
|
||||||
registry = []
|
"safelog",
|
||||||
default = ["cli", "daemon", "registry", "container-runtime"]
|
"tor-cell",
|
||||||
dev = []
|
"tor-hscrypto",
|
||||||
unstable = ["console-subscriber", "tokio/tracing"]
|
"tor-hsservice",
|
||||||
|
"tor-keymgr",
|
||||||
|
"tor-llcrypto",
|
||||||
|
"tor-proto",
|
||||||
|
"tor-rtcompat",
|
||||||
|
]
|
||||||
|
cli = ["cli-registry", "cli-startd", "cli-tunnel"]
|
||||||
|
cli-container = ["procfs", "pty-process"]
|
||||||
|
cli-registry = []
|
||||||
|
cli-startd = []
|
||||||
|
cli-tunnel = []
|
||||||
|
console = ["console-subscriber", "tokio/tracing"]
|
||||||
|
default = ["cli", "cli-container", "registry", "startd", "tunnel"]
|
||||||
|
dev = ["backtrace-on-stack-overflow"]
|
||||||
docker = []
|
docker = []
|
||||||
|
registry = []
|
||||||
|
startd = []
|
||||||
test = []
|
test = []
|
||||||
|
tunnel = []
|
||||||
|
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"] }
|
||||||
|
backtrace-on-stack-overflow = { version = "0.3.0", optional = true }
|
||||||
barrage = "0.2.3"
|
barrage = "0.2.3"
|
||||||
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"
|
||||||
@@ -74,20 +106,23 @@ 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.15.7"
|
||||||
console-subscriber = { version = "0.3.0", optional = true }
|
console-subscriber = { version = "0.4.1", 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.21.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 = "2.1.0"
|
||||||
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 +131,63 @@ 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" }
|
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.17.7", 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" }
|
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 +196,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 +211,84 @@ 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 }
|
procfs = { version = "0.17.0", optional = true }
|
||||||
proptest = "1.3.1"
|
proptest = "1.3.1"
|
||||||
proptest-derive = "0.5.0"
|
proptest-derive = "0.5.0"
|
||||||
|
pty-process = { version = "0.5.1", optional = true }
|
||||||
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.4", features = ["json", "socks", "stream"] }
|
||||||
reqwest_cookie_store = "0.8.0"
|
reqwest_cookie_store = "0.8.0"
|
||||||
rpassword = "7.2.0"
|
rpassword = "7.2.0"
|
||||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" }
|
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", rev = "068db90" }
|
||||||
rust-argon2 = "2.0.0"
|
rust-argon2 = "2.0.0"
|
||||||
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.8.2" }
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
serde_with = { version = "3.4.0", features = ["macros", "json"] }
|
serde_with = { version = "3.4.0", features = ["json", "macros"] }
|
||||||
serde_yaml = { package = "serde_yml", version = "0.0.10" }
|
serde_yaml = { package = "serde_yml", version = "0.0.12" }
|
||||||
|
sha-crypt = "0.5.0"
|
||||||
sha2 = "0.10.2"
|
sha2 = "0.10.2"
|
||||||
shell-words = "1"
|
shell-words = "1"
|
||||||
signal-hook = "0.3.17"
|
signal-hook = "0.3.17"
|
||||||
simple-logging = "2.0.2"
|
simple-logging = "2.0.2"
|
||||||
socket2 = "0.5.7"
|
socket2 = { version = "0.6.0", features = ["all"] }
|
||||||
sqlx = { version = "0.7.2", features = [
|
socks5-impl = { version = "0.7.2", features = ["client", "server"] }
|
||||||
"chrono",
|
sqlx = { version = "0.8.6", features = [
|
||||||
"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-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.21.0"
|
||||||
tty-spawn = { version = "0.4.0", optional = true }
|
|
||||||
typed-builder = "0.18.0"
|
|
||||||
unix-named-pipe = "0.2.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"
|
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"
|
zeroize = "1.6.0"
|
||||||
mail-send = { git = "https://github.com/dr-bonez/mail-send.git", branch = "main", optional = true }
|
|
||||||
rustls = "0.23.20"
|
|
||||||
rustls-pki-types = { version = "1.10.1", features = ["alloc"] }
|
|
||||||
|
|
||||||
[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::{generate_key, 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 = generate_key()?;
|
||||||
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::generate_key()?;
|
||||||
|
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())),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,19 @@ use std::fmt;
|
|||||||
|
|
||||||
use clap::{CommandFactory, FromArgMatches, Parser};
|
use clap::{CommandFactory, FromArgMatches, Parser};
|
||||||
pub use models::ActionId;
|
pub use models::ActionId;
|
||||||
use models::PackageId;
|
use models::{PackageId, ReplayId};
|
||||||
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;
|
||||||
|
|
||||||
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,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn action_api<C: Context>() -> ParentHandler<C> {
|
pub fn action_api<C: Context>() -> ParentHandler<C> {
|
||||||
@@ -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::{
|
use crate::middleware::auth::{
|
||||||
AsLogoutSessionId, HasLoggedOutSessions, HashSessionToken, LoginRes,
|
AsLogoutSessionId, AuthContext, 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: AuthContext>() -> 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: AuthContext>(
|
||||||
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: AuthContext>(
|
||||||
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: AuthContext>(
|
||||||
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: AuthContext>() -> 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: AuthContext>(
|
||||||
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,7 @@ 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: AuthContext>(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 +474,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)]
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ 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::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,7 +23,8 @@ 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::AuthContext;
|
||||||
|
use crate::notifications::{NotificationLevel, notify};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::io::dir_copy;
|
use crate::util::io::dir_copy;
|
||||||
use crate::util::serde::IoFormat;
|
use crate::util::serde::IoFormat;
|
||||||
@@ -170,7 +170,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 +223,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 +276,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(),
|
||||||
@@ -312,7 +317,7 @@ async fn perform_backup(
|
|||||||
.with_kind(ErrorKind::Filesystem)?;
|
.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?;
|
||||||
@@ -337,7 +342,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);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::collections::BTreeMap;
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use models::{HostId, PackageId};
|
use models::{HostId, PackageId};
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
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 crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
|
|||||||
@@ -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,7 +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 models::PackageId;
|
||||||
use patch_db::json_ptr::ROOT;
|
use patch_db::json_ptr::ROOT;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -11,6 +11,7 @@ use tracing::instrument;
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use super::target::BackupTargetId;
|
use super::target::BackupTargetId;
|
||||||
|
use crate::PLATFORM;
|
||||||
use crate::backup::os::OsBackup;
|
use crate::backup::os::OsBackup;
|
||||||
use crate::context::setup::SetupResult;
|
use crate::context::setup::SetupResult;
|
||||||
use crate::context::{RpcContext, SetupContext};
|
use crate::context::{RpcContext, SetupContext};
|
||||||
@@ -20,9 +21,11 @@ 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;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
@@ -80,6 +83,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 +109,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 +137,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 +175,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,15 @@ 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 models::{FromStrParser, PackageId};
|
||||||
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 sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@@ -27,10 +27,10 @@ use crate::disk::mount::filesystem::{FileSystem, MountType, ReadWrite};
|
|||||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
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::{
|
|
||||||
deserialize_from_str, display_serializable, serialize_display, HandlerExtSerde, WithIoFormat,
|
|
||||||
};
|
|
||||||
use crate::util::VersionString;
|
use crate::util::VersionString;
|
||||||
|
use crate::util::serde::{
|
||||||
|
HandlerExtSerde, WithIoFormat, deserialize_from_str, display_serializable, serialize_display,
|
||||||
|
};
|
||||||
|
|
||||||
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,7 +297,7 @@ 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,8 +306,11 @@ lazy_static::lazy_static! {
|
|||||||
#[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)]
|
||||||
|
server_id: Option<String>,
|
||||||
password: String,
|
password: String,
|
||||||
|
#[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?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,41 +2,64 @@ use std::collections::VecDeque;
|
|||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[cfg(feature = "container-runtime")]
|
#[cfg(feature = "cli-container")]
|
||||||
pub mod container_cli;
|
pub mod container_cli;
|
||||||
pub mod deprecated;
|
pub mod deprecated;
|
||||||
#[cfg(feature = "registry")]
|
#[cfg(any(feature = "registry", feature = "cli-registry"))]
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
pub mod start_cli;
|
pub mod start_cli;
|
||||||
#[cfg(feature = "daemon")]
|
#[cfg(feature = "startd")]
|
||||||
pub mod start_init;
|
pub mod start_init;
|
||||||
#[cfg(feature = "daemon")]
|
#[cfg(feature = "startd")]
|
||||||
pub mod startd;
|
pub mod startd;
|
||||||
|
#[cfg(any(feature = "tunnel", feature = "cli-tunnel"))]
|
||||||
|
pub mod tunnel;
|
||||||
|
|
||||||
fn select_executable(name: &str) -> Option<fn(VecDeque<OsString>)> {
|
fn select_executable(name: &str) -> Option<fn(VecDeque<OsString>)> {
|
||||||
match name {
|
match name {
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "startd")]
|
||||||
"start-cli" => Some(start_cli::main),
|
|
||||||
#[cfg(feature = "container-runtime")]
|
|
||||||
"start-cli" => Some(container_cli::main),
|
|
||||||
#[cfg(feature = "daemon")]
|
|
||||||
"startd" => Some(startd::main),
|
"startd" => Some(startd::main),
|
||||||
#[cfg(feature = "registry")]
|
#[cfg(feature = "startd")]
|
||||||
"registry" => Some(registry::main),
|
|
||||||
"embassy-cli" => Some(|_| deprecated::renamed("embassy-cli", "start-cli")),
|
|
||||||
"embassy-sdk" => Some(|_| deprecated::renamed("embassy-sdk", "start-sdk")),
|
|
||||||
"embassyd" => Some(|_| deprecated::renamed("embassyd", "startd")),
|
"embassyd" => Some(|_| deprecated::renamed("embassyd", "startd")),
|
||||||
|
#[cfg(feature = "startd")]
|
||||||
"embassy-init" => Some(|_| deprecated::removed("embassy-init")),
|
"embassy-init" => Some(|_| deprecated::removed("embassy-init")),
|
||||||
|
|
||||||
|
#[cfg(feature = "cli-startd")]
|
||||||
|
"start-cli" => Some(start_cli::main),
|
||||||
|
#[cfg(feature = "cli-startd")]
|
||||||
|
"embassy-cli" => Some(|_| deprecated::renamed("embassy-cli", "start-cli")),
|
||||||
|
#[cfg(feature = "cli-startd")]
|
||||||
|
"embassy-sdk" => Some(|_| deprecated::removed("embassy-sdk")),
|
||||||
|
|
||||||
|
#[cfg(feature = "cli-container")]
|
||||||
|
"start-container" => Some(container_cli::main),
|
||||||
|
|
||||||
|
#[cfg(feature = "registry")]
|
||||||
|
"start-registryd" => Some(registry::main),
|
||||||
|
#[cfg(feature = "cli-registry")]
|
||||||
|
"start-registry" => Some(registry::cli),
|
||||||
|
|
||||||
|
#[cfg(feature = "tunnel")]
|
||||||
|
"start-tunneld" => Some(tunnel::main),
|
||||||
|
#[cfg(feature = "cli-tunnel")]
|
||||||
|
"start-tunnel" => Some(tunnel::cli),
|
||||||
|
|
||||||
"contents" => Some(|_| {
|
"contents" => Some(|_| {
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "startd")]
|
||||||
println!("start-cli");
|
|
||||||
#[cfg(feature = "container-runtime")]
|
|
||||||
println!("start-cli (container)");
|
|
||||||
#[cfg(feature = "daemon")]
|
|
||||||
println!("startd");
|
println!("startd");
|
||||||
|
#[cfg(feature = "cli-startd")]
|
||||||
|
println!("start-cli");
|
||||||
|
#[cfg(feature = "cli-container")]
|
||||||
|
println!("start-container");
|
||||||
#[cfg(feature = "registry")]
|
#[cfg(feature = "registry")]
|
||||||
println!("registry");
|
println!("start-registryd");
|
||||||
|
#[cfg(feature = "cli-registry")]
|
||||||
|
println!("start-registry");
|
||||||
|
#[cfg(feature = "tunnel")]
|
||||||
|
println!("start-tunneld");
|
||||||
|
#[cfg(feature = "cli-tunnel")]
|
||||||
|
println!("start-tunnel");
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 helpers::NonDetachingJoinHandle;
|
||||||
|
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::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 models::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::AuthContext;
|
||||||
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: Option<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,
|
||||||
|
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::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::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::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::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::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<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>,
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ use tokio::sync::broadcast::Sender;
|
|||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::progress::FullProgressTracker;
|
use crate::progress::FullProgressTracker;
|
||||||
use crate::rpc_continuations::RpcContinuations;
|
use crate::rpc_continuations::RpcContinuations;
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
pub struct InitContextSeed {
|
pub struct InitContextSeed {
|
||||||
pub config: ServerConfig,
|
pub config: ServerConfig,
|
||||||
@@ -25,10 +25,12 @@ impl InitContext {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn init(cfg: &ServerConfig) -> Result<Self, Error> {
|
pub async fn init(cfg: &ServerConfig) -> Result<Self, Error> {
|
||||||
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
||||||
|
let mut progress = FullProgressTracker::new();
|
||||||
|
progress.enable_logging(true);
|
||||||
Ok(Self(Arc::new(InitContextSeed {
|
Ok(Self(Arc::new(InitContextSeed {
|
||||||
config: cfg.clone(),
|
config: cfg.clone(),
|
||||||
error: watch::channel(None).0,
|
error: watch::channel(None).0,
|
||||||
progress: FullProgressTracker::new(),
|
progress,
|
||||||
shutdown,
|
shutdown,
|
||||||
rpc_continuations: RpcContinuations::new(),
|
rpc_continuations: RpcContinuations::new(),
|
||||||
})))
|
})))
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ use rpc_toolkit::Context;
|
|||||||
use tokio::sync::broadcast::Sender;
|
use tokio::sync::broadcast::Sender;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
use crate::net::utils::find_eth_iface;
|
use crate::net::utils::find_eth_iface;
|
||||||
use crate::rpc_continuations::RpcContinuations;
|
use crate::rpc_continuations::RpcContinuations;
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
pub struct InstallContextSeed {
|
pub struct InstallContextSeed {
|
||||||
pub ethernet_interface: String,
|
pub ethernet_interface: String,
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use chrono::{TimeDelta, Utc};
|
use chrono::{TimeDelta, Utc};
|
||||||
@@ -16,31 +17,37 @@ use models::{ActionId, PackageId};
|
|||||||
use reqwest::{Client, Proxy};
|
use reqwest::{Client, Proxy};
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{CallRemote, Context, Empty};
|
use rpc_toolkit::{CallRemote, Context, Empty};
|
||||||
use tokio::sync::{broadcast, watch, Mutex, RwLock};
|
use tokio::sync::{RwLock, broadcast, oneshot, watch};
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::setup::CURRENT_SECRET;
|
use super::setup::CURRENT_SECRET;
|
||||||
|
use crate::DATA_DIR;
|
||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::auth::Sessions;
|
use crate::auth::Sessions;
|
||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
|
use crate::db::model::package::TaskSeverity;
|
||||||
use crate::disk::OsPartitionInfo;
|
use crate::disk::OsPartitionInfo;
|
||||||
use crate::init::{check_time_is_synchronized, InitResult};
|
use crate::init::{InitResult, check_time_is_synchronized};
|
||||||
use crate::lxc::{ContainerId, LxcContainer, LxcManager};
|
use crate::install::PKG_ARCHIVE_DIR;
|
||||||
|
use crate::lxc::LxcManager;
|
||||||
|
use crate::net::gateway::UpgradableListener;
|
||||||
use crate::net::net_controller::{NetController, NetService};
|
use crate::net::net_controller::{NetController, NetService};
|
||||||
|
use crate::net::socks::DEFAULT_SOCKS_LISTEN;
|
||||||
use crate::net::utils::{find_eth_iface, find_wifi_iface};
|
use crate::net::utils::{find_eth_iface, find_wifi_iface};
|
||||||
use crate::net::web_server::{UpgradableListener, WebServerAcceptorSetter};
|
use crate::net::web_server::WebServerAcceptorSetter;
|
||||||
use crate::net::wifi::WpaCli;
|
use crate::net::wifi::WpaCli;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{FullProgressTracker, PhaseProgressTrackerHandle};
|
use crate::progress::{FullProgressTracker, PhaseProgressTrackerHandle};
|
||||||
use crate::rpc_continuations::{Guid, OpenAuthedContinuations, RpcContinuations};
|
use crate::rpc_continuations::{Guid, OpenAuthedContinuations, RpcContinuations};
|
||||||
use crate::service::action::update_requested_actions;
|
|
||||||
use crate::service::effects::callbacks::ServiceCallbacks;
|
|
||||||
use crate::service::ServiceMap;
|
use crate::service::ServiceMap;
|
||||||
|
use crate::service::action::update_tasks;
|
||||||
|
use crate::service::effects::callbacks::ServiceCallbacks;
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
|
use crate::util::io::delete_file;
|
||||||
use crate::util::lshw::LshwDevice;
|
use crate::util::lshw::LshwDevice;
|
||||||
use crate::util::sync::SyncMutex;
|
use crate::util::sync::{SyncMutex, SyncRwLock, Watch};
|
||||||
|
|
||||||
pub struct RpcContextSeed {
|
pub struct RpcContextSeed {
|
||||||
is_closed: AtomicBool,
|
is_closed: AtomicBool,
|
||||||
@@ -51,29 +58,28 @@ pub struct RpcContextSeed {
|
|||||||
pub ephemeral_sessions: SyncMutex<Sessions>,
|
pub ephemeral_sessions: SyncMutex<Sessions>,
|
||||||
pub db: TypedPatchDb<Database>,
|
pub db: TypedPatchDb<Database>,
|
||||||
pub sync_db: watch::Sender<u64>,
|
pub sync_db: watch::Sender<u64>,
|
||||||
pub account: RwLock<AccountInfo>,
|
pub account: SyncRwLock<AccountInfo>,
|
||||||
pub net_controller: Arc<NetController>,
|
pub net_controller: Arc<NetController>,
|
||||||
pub os_net_service: NetService,
|
pub os_net_service: NetService,
|
||||||
pub s9pk_arch: Option<&'static str>,
|
pub s9pk_arch: Option<&'static str>,
|
||||||
pub services: ServiceMap,
|
pub services: ServiceMap,
|
||||||
pub metrics_cache: RwLock<Option<crate::system::Metrics>>,
|
pub cancellable_installs: SyncMutex<BTreeMap<PackageId, oneshot::Sender<()>>>,
|
||||||
|
pub metrics_cache: Watch<Option<crate::system::Metrics>>,
|
||||||
pub shutdown: broadcast::Sender<Option<Shutdown>>,
|
pub shutdown: broadcast::Sender<Option<Shutdown>>,
|
||||||
pub tor_socks: SocketAddr,
|
|
||||||
pub lxc_manager: Arc<LxcManager>,
|
pub lxc_manager: Arc<LxcManager>,
|
||||||
pub open_authed_continuations: OpenAuthedContinuations<Option<InternedString>>,
|
pub open_authed_continuations: OpenAuthedContinuations<Option<InternedString>>,
|
||||||
pub rpc_continuations: RpcContinuations,
|
pub rpc_continuations: RpcContinuations,
|
||||||
pub callbacks: Arc<ServiceCallbacks>,
|
pub callbacks: Arc<ServiceCallbacks>,
|
||||||
pub wifi_manager: Option<Arc<RwLock<WpaCli>>>,
|
pub wifi_manager: Arc<RwLock<Option<WpaCli>>>,
|
||||||
pub current_secret: Arc<Jwk>,
|
pub current_secret: Arc<Jwk>,
|
||||||
pub client: Client,
|
pub client: Client,
|
||||||
pub start_time: Instant,
|
pub start_time: Instant,
|
||||||
pub crons: SyncMutex<BTreeMap<Guid, NonDetachingJoinHandle<()>>>,
|
pub crons: SyncMutex<BTreeMap<Guid, NonDetachingJoinHandle<()>>>,
|
||||||
// #[cfg(feature = "dev")]
|
|
||||||
pub dev: Dev,
|
|
||||||
}
|
}
|
||||||
|
impl Drop for RpcContextSeed {
|
||||||
pub struct Dev {
|
fn drop(&mut self) {
|
||||||
pub lxc: Mutex<BTreeMap<ContainerId, LxcContainer>>,
|
tracing::info!("RpcContext is dropped");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Hardware {
|
pub struct Hardware {
|
||||||
@@ -101,14 +107,16 @@ impl InitRpcContextPhases {
|
|||||||
pub struct CleanupInitPhases {
|
pub struct CleanupInitPhases {
|
||||||
cleanup_sessions: PhaseProgressTrackerHandle,
|
cleanup_sessions: PhaseProgressTrackerHandle,
|
||||||
init_services: PhaseProgressTrackerHandle,
|
init_services: PhaseProgressTrackerHandle,
|
||||||
check_requested_actions: PhaseProgressTrackerHandle,
|
prune_s9pks: PhaseProgressTrackerHandle,
|
||||||
|
check_tasks: PhaseProgressTrackerHandle,
|
||||||
}
|
}
|
||||||
impl CleanupInitPhases {
|
impl CleanupInitPhases {
|
||||||
pub fn new(handle: &FullProgressTracker) -> Self {
|
pub fn new(handle: &FullProgressTracker) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cleanup_sessions: handle.add_phase("Cleaning up sessions".into(), Some(1)),
|
cleanup_sessions: handle.add_phase("Cleaning up sessions".into(), Some(1)),
|
||||||
init_services: handle.add_phase("Initializing services".into(), Some(10)),
|
init_services: handle.add_phase("Initializing services".into(), Some(10)),
|
||||||
check_requested_actions: handle.add_phase("Checking action requests".into(), Some(1)),
|
prune_s9pks: handle.add_phase("Pruning S9PKs".into(), Some(1)),
|
||||||
|
check_tasks: handle.add_phase("Checking action requests".into(), Some(1)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,10 +137,7 @@ impl RpcContext {
|
|||||||
run_migrations,
|
run_migrations,
|
||||||
}: InitRpcContextPhases,
|
}: InitRpcContextPhases,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let tor_proxy = config.tor_socks.unwrap_or(SocketAddr::V4(SocketAddrV4::new(
|
let socks_proxy = config.socks_listen.unwrap_or(DEFAULT_SOCKS_LISTEN);
|
||||||
Ipv4Addr::new(127, 0, 0, 1),
|
|
||||||
9050,
|
|
||||||
)));
|
|
||||||
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
||||||
|
|
||||||
load_db.start();
|
load_db.start();
|
||||||
@@ -154,18 +159,9 @@ impl RpcContext {
|
|||||||
{
|
{
|
||||||
(net_ctrl, os_net_service)
|
(net_ctrl, os_net_service)
|
||||||
} else {
|
} else {
|
||||||
let net_ctrl = Arc::new(
|
let net_ctrl =
|
||||||
NetController::init(
|
Arc::new(NetController::init(db.clone(), &account.hostname, socks_proxy).await?);
|
||||||
db.clone(),
|
webserver.try_upgrade(|a| net_ctrl.net_iface.watcher.upgrade_listener(a))?;
|
||||||
config
|
|
||||||
.tor_control
|
|
||||||
.unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))),
|
|
||||||
tor_proxy,
|
|
||||||
&account.hostname,
|
|
||||||
)
|
|
||||||
.await?,
|
|
||||||
);
|
|
||||||
webserver.try_upgrade(|a| net_ctrl.net_iface.upgrade_listener(a))?;
|
|
||||||
let os_net_service = net_ctrl.os_bindings().await?;
|
let os_net_service = net_ctrl.os_bindings().await?;
|
||||||
(net_ctrl, os_net_service)
|
(net_ctrl, os_net_service)
|
||||||
};
|
};
|
||||||
@@ -173,8 +169,8 @@ impl RpcContext {
|
|||||||
tracing::info!("Initialized Net Controller");
|
tracing::info!("Initialized Net Controller");
|
||||||
|
|
||||||
let services = ServiceMap::default();
|
let services = ServiceMap::default();
|
||||||
let metrics_cache = RwLock::<Option<crate::system::Metrics>>::new(None);
|
let metrics_cache = Watch::<Option<crate::system::Metrics>>::new(None);
|
||||||
let tor_proxy_url = format!("socks5h://{tor_proxy}");
|
let socks_proxy_url = format!("socks5h://{socks_proxy}");
|
||||||
|
|
||||||
let crons = SyncMutex::new(BTreeMap::new());
|
let crons = SyncMutex::new(BTreeMap::new());
|
||||||
|
|
||||||
@@ -229,7 +225,7 @@ impl RpcContext {
|
|||||||
ephemeral_sessions: SyncMutex::new(Sessions::new()),
|
ephemeral_sessions: SyncMutex::new(Sessions::new()),
|
||||||
sync_db: watch::Sender::new(db.sequence().await),
|
sync_db: watch::Sender::new(db.sequence().await),
|
||||||
db,
|
db,
|
||||||
account: RwLock::new(account),
|
account: SyncRwLock::new(account),
|
||||||
callbacks: net_controller.callbacks.clone(),
|
callbacks: net_controller.callbacks.clone(),
|
||||||
net_controller,
|
net_controller,
|
||||||
os_net_service,
|
os_net_service,
|
||||||
@@ -239,15 +235,13 @@ impl RpcContext {
|
|||||||
Some(crate::ARCH)
|
Some(crate::ARCH)
|
||||||
},
|
},
|
||||||
services,
|
services,
|
||||||
|
cancellable_installs: SyncMutex::new(BTreeMap::new()),
|
||||||
metrics_cache,
|
metrics_cache,
|
||||||
shutdown,
|
shutdown,
|
||||||
tor_socks: tor_proxy,
|
|
||||||
lxc_manager: Arc::new(LxcManager::new()),
|
lxc_manager: Arc::new(LxcManager::new()),
|
||||||
open_authed_continuations: OpenAuthedContinuations::new(),
|
open_authed_continuations: OpenAuthedContinuations::new(),
|
||||||
rpc_continuations: RpcContinuations::new(),
|
rpc_continuations: RpcContinuations::new(),
|
||||||
wifi_manager: wifi_interface
|
wifi_manager: Arc::new(RwLock::new(wifi_interface.clone().map(|i| WpaCli::init(i)))),
|
||||||
.clone()
|
|
||||||
.map(|i| Arc::new(RwLock::new(WpaCli::init(i)))),
|
|
||||||
current_secret: Arc::new(
|
current_secret: Arc::new(
|
||||||
Jwk::generate_ec_key(josekit::jwk::alg::ec::EcCurve::P256).map_err(|e| {
|
Jwk::generate_ec_key(josekit::jwk::alg::ec::EcCurve::P256).map_err(|e| {
|
||||||
tracing::debug!("{:?}", e);
|
tracing::debug!("{:?}", e);
|
||||||
@@ -259,21 +253,11 @@ impl RpcContext {
|
|||||||
})?,
|
})?,
|
||||||
),
|
),
|
||||||
client: Client::builder()
|
client: Client::builder()
|
||||||
.proxy(Proxy::custom(move |url| {
|
.proxy(Proxy::all(socks_proxy_url)?)
|
||||||
if url.host_str().map_or(false, |h| h.ends_with(".onion")) {
|
|
||||||
Some(tor_proxy_url.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.build()
|
.build()
|
||||||
.with_kind(crate::ErrorKind::ParseUrl)?,
|
.with_kind(crate::ErrorKind::ParseUrl)?,
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
crons,
|
crons,
|
||||||
// #[cfg(feature = "dev")]
|
|
||||||
dev: Dev {
|
|
||||||
lxc: Mutex::new(BTreeMap::new()),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let res = Self(seed.clone());
|
let res = Self(seed.clone());
|
||||||
@@ -290,7 +274,7 @@ impl RpcContext {
|
|||||||
self.crons.mutate(|c| std::mem::take(c));
|
self.crons.mutate(|c| std::mem::take(c));
|
||||||
self.services.shutdown_all().await?;
|
self.services.shutdown_all().await?;
|
||||||
self.is_closed.store(true, Ordering::SeqCst);
|
self.is_closed.store(true, Ordering::SeqCst);
|
||||||
tracing::info!("RPC Context is shutdown");
|
tracing::info!("RpcContext is shutdown");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,8 +290,9 @@ impl RpcContext {
|
|||||||
&self,
|
&self,
|
||||||
CleanupInitPhases {
|
CleanupInitPhases {
|
||||||
mut cleanup_sessions,
|
mut cleanup_sessions,
|
||||||
init_services,
|
mut init_services,
|
||||||
mut check_requested_actions,
|
mut prune_s9pks,
|
||||||
|
mut check_tasks,
|
||||||
}: CleanupInitPhases,
|
}: CleanupInitPhases,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
cleanup_sessions.start();
|
cleanup_sessions.start();
|
||||||
@@ -365,39 +350,63 @@ impl RpcContext {
|
|||||||
});
|
});
|
||||||
cleanup_sessions.complete();
|
cleanup_sessions.complete();
|
||||||
|
|
||||||
self.services.init(&self, init_services).await?;
|
init_services.start();
|
||||||
tracing::info!("Initialized Services");
|
self.services.init(&self).await?;
|
||||||
|
init_services.complete();
|
||||||
|
|
||||||
// TODO
|
prune_s9pks.start();
|
||||||
check_requested_actions.start();
|
|
||||||
let peek = self.db.peek().await;
|
let peek = self.db.peek().await;
|
||||||
|
let keep = peek
|
||||||
|
.as_public()
|
||||||
|
.as_package_data()
|
||||||
|
.as_entries()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, pde)| pde.as_s9pk().de())
|
||||||
|
.collect::<Result<BTreeSet<PathBuf>, Error>>()?;
|
||||||
|
let installed_dir = &Path::new(DATA_DIR).join(PKG_ARCHIVE_DIR).join("installed");
|
||||||
|
if tokio::fs::metadata(&installed_dir).await.is_ok() {
|
||||||
|
let mut dir = tokio::fs::read_dir(&installed_dir)
|
||||||
|
.await
|
||||||
|
.with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("dir {installed_dir:?}")))?;
|
||||||
|
while let Some(file) = dir
|
||||||
|
.next_entry()
|
||||||
|
.await
|
||||||
|
.with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("dir {installed_dir:?}")))?
|
||||||
|
{
|
||||||
|
let path = file.path();
|
||||||
|
if path.extension() == Some(OsStr::new("s9pk")) && !keep.contains(&path) {
|
||||||
|
delete_file(path).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prune_s9pks.complete();
|
||||||
|
|
||||||
|
check_tasks.start();
|
||||||
let mut action_input: OrdMap<PackageId, BTreeMap<ActionId, Value>> = OrdMap::new();
|
let mut action_input: OrdMap<PackageId, BTreeMap<ActionId, Value>> = OrdMap::new();
|
||||||
let requested_actions: BTreeSet<_> = peek
|
let tasks: BTreeSet<_> = peek
|
||||||
.as_public()
|
.as_public()
|
||||||
.as_package_data()
|
.as_package_data()
|
||||||
.as_entries()?
|
.as_entries()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(_, pde)| {
|
.map(|(_, pde)| {
|
||||||
Ok(pde
|
Ok(pde.as_tasks().as_entries()?.into_iter().map(|(_, r)| {
|
||||||
.as_requested_actions()
|
Ok::<_, Error>((
|
||||||
.as_entries()?
|
r.as_task().as_package_id().de()?,
|
||||||
.into_iter()
|
r.as_task().as_action_id().de()?,
|
||||||
.map(|(_, r)| {
|
))
|
||||||
Ok::<_, Error>((
|
}))
|
||||||
r.as_request().as_package_id().de()?,
|
|
||||||
r.as_request().as_action_id().de()?,
|
|
||||||
))
|
|
||||||
}))
|
|
||||||
})
|
})
|
||||||
.flatten_ok()
|
.flatten_ok()
|
||||||
.map(|a| a.and_then(|a| a))
|
.map(|a| a.and_then(|a| a))
|
||||||
.try_collect()?;
|
.try_collect()?;
|
||||||
let procedure_id = Guid::new();
|
let procedure_id = Guid::new();
|
||||||
for (package_id, action_id) in requested_actions {
|
for (package_id, action_id) in tasks {
|
||||||
if let Some(service) = self.services.get(&package_id).await.as_ref() {
|
if let Some(service) = self.services.get(&package_id).await.as_ref() {
|
||||||
if let Some(input) = service
|
if let Some(input) = service
|
||||||
.get_action_input(procedure_id.clone(), action_id.clone())
|
.get_action_input(procedure_id.clone(), action_id.clone())
|
||||||
.await?
|
.await
|
||||||
|
.log_err()
|
||||||
|
.flatten()
|
||||||
.and_then(|i| i.value)
|
.and_then(|i| i.value)
|
||||||
{
|
{
|
||||||
action_input
|
action_input
|
||||||
@@ -407,28 +416,47 @@ impl RpcContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.db
|
for id in
|
||||||
.mutate(|db| {
|
self.db
|
||||||
for (package_id, action_input) in &action_input {
|
.mutate::<Vec<PackageId>>(|db| {
|
||||||
for (action_id, input) in action_input {
|
for (package_id, action_input) in &action_input {
|
||||||
for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? {
|
for (action_id, input) in action_input {
|
||||||
pde.as_requested_actions_mut().mutate(|requested_actions| {
|
for (_, pde) in
|
||||||
Ok(update_requested_actions(
|
db.as_public_mut().as_package_data_mut().as_entries_mut()?
|
||||||
requested_actions,
|
{
|
||||||
package_id,
|
pde.as_tasks_mut().mutate(|tasks| {
|
||||||
action_id,
|
Ok(update_tasks(tasks, package_id, action_id, input, false))
|
||||||
input,
|
})?;
|
||||||
false,
|
}
|
||||||
))
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
db.as_public()
|
||||||
Ok(())
|
.as_package_data()
|
||||||
})
|
.as_entries()?
|
||||||
.await
|
.into_iter()
|
||||||
.result?;
|
.filter_map(|(id, pkg)| {
|
||||||
check_requested_actions.complete();
|
(|| {
|
||||||
|
if pkg.as_tasks().de()?.into_iter().any(|(_, t)| {
|
||||||
|
t.active && t.task.severity == TaskSeverity::Critical
|
||||||
|
}) {
|
||||||
|
Ok(Some(id))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.transpose()
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result?
|
||||||
|
{
|
||||||
|
let svc = self.services.get(&id).await;
|
||||||
|
if let Some(svc) = &*svc {
|
||||||
|
svc.stop(procedure_id.clone(), false).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check_tasks.complete();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -455,6 +483,11 @@ impl RpcContext {
|
|||||||
<Self as CallRemote<RemoteContext, T>>::call_remote(&self, method, params, extra).await
|
<Self as CallRemote<RemoteContext, T>>::call_remote(&self, method, params, extra).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AsRef<Client> for RpcContext {
|
||||||
|
fn as_ref(&self) -> &Client {
|
||||||
|
&self.client
|
||||||
|
}
|
||||||
|
}
|
||||||
impl AsRef<Jwk> for RpcContext {
|
impl AsRef<Jwk> for RpcContext {
|
||||||
fn as_ref(&self) -> &Jwk {
|
fn as_ref(&self) -> &Jwk {
|
||||||
&CURRENT_SECRET
|
&CURRENT_SECRET
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user