Compare commits
281 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
871f78b570 | ||
|
|
753fbc0c5c | ||
|
|
748277aa0e | ||
|
|
bf40a9ef6d | ||
|
|
733000eaa2 | ||
|
|
6a399a7250 | ||
|
|
7ba22f1a09 | ||
|
|
f54f950f81 | ||
|
|
4625711606 | ||
|
|
5735ea2b3c | ||
|
|
b597d0366a | ||
|
|
9c6dcc4a43 | ||
|
|
27c5464cb6 | ||
|
|
1dad7965d2 | ||
|
|
c14ca1d7fd | ||
|
|
2b9e7432b8 | ||
|
|
547747ff74 | ||
|
|
e5b137b331 | ||
|
|
9e554bdecd | ||
|
|
765b542264 | ||
|
|
182a095420 | ||
|
|
0865cffddf | ||
|
|
5a312b9900 | ||
|
|
af2b2f33c2 | ||
|
|
9aa08dfb9b | ||
|
|
b28c673133 | ||
|
|
9a545f176d | ||
|
|
65728eb6ab | ||
|
|
531e037974 | ||
|
|
a96467cb3e | ||
|
|
6e92a7d93d | ||
|
|
740e63da2b | ||
|
|
a69cae22dd | ||
|
|
8ea3c3c29e | ||
|
|
63ab739b3d | ||
|
|
58bb788034 | ||
|
|
9e633b37e7 | ||
|
|
bb6a4842bd | ||
|
|
246727995d | ||
|
|
202695096a | ||
|
|
afbab293a8 | ||
|
|
78faf888af | ||
|
|
5164c21923 | ||
|
|
edcd1a3c5b | ||
|
|
532ab9128f | ||
|
|
a3072aacc2 | ||
|
|
27296d8880 | ||
|
|
8549b9bc37 | ||
|
|
7632373097 | ||
|
|
23b0674ac0 | ||
|
|
01f0484a0e | ||
|
|
3ca9035fdb | ||
|
|
caaf9d26db | ||
|
|
eb521b2332 | ||
|
|
68c29ab99e | ||
|
|
f12b7f4319 | ||
|
|
7db331320a | ||
|
|
97ad8a85c3 | ||
|
|
6f588196cb | ||
|
|
20241c27ee | ||
|
|
05d6aea37f | ||
|
|
7e0e7860cd | ||
|
|
a0afd7b8ed | ||
|
|
500369ab2b | ||
|
|
dc26d5c0c8 | ||
|
|
0def02f604 | ||
|
|
0ffa9167da | ||
|
|
a110e8f241 | ||
|
|
491f363392 | ||
|
|
33a67bf7b4 | ||
|
|
1e6f583431 | ||
|
|
5e3412d735 | ||
|
|
e6e4cd63f3 | ||
|
|
f5da5f4ef0 | ||
|
|
9a202cc124 | ||
|
|
c305deab52 | ||
|
|
0daaf3b1ec | ||
|
|
8e21504bdb | ||
|
|
fcf1be52ac | ||
|
|
394bc9ceb8 | ||
|
|
e3786592b2 | ||
|
|
d6eaf8d3d9 | ||
|
|
b1c23336e3 | ||
|
|
44c5073dea | ||
|
|
b7593fac44 | ||
|
|
af116794c4 | ||
|
|
88c85e1d8a | ||
|
|
9322b3d07e | ||
|
|
55f5329817 | ||
|
|
79d92c30f8 | ||
|
|
73229501c2 | ||
|
|
32ca91a7c9 | ||
|
|
9e03ac084e | ||
|
|
082c51109d | ||
|
|
8f44c75dc3 | ||
|
|
234f0d75e8 | ||
|
|
564186a1f9 | ||
|
|
ccdb477dbb | ||
|
|
5f92f9e965 | ||
|
|
c2db4390bb | ||
|
|
11c21b5259 | ||
|
|
3cd9e17e3f | ||
|
|
1982ce796f | ||
|
|
825e18a551 | ||
|
|
9ff0128fb1 | ||
|
|
36c3617204 | ||
|
|
90a9db3a91 | ||
|
|
59d6795d9e | ||
|
|
2c07cf50fa | ||
|
|
cc0e525dc5 | ||
|
|
73bd973109 | ||
|
|
a7e501d874 | ||
|
|
4676f0595c | ||
|
|
1d3d70e8d6 | ||
|
|
bada88157e | ||
|
|
13f3137701 | ||
|
|
d3316ff6ff | ||
|
|
1b384e61b4 | ||
|
|
addea20cab | ||
|
|
fac23f2f57 | ||
|
|
bffe1ccb3d | ||
|
|
e577434fe6 | ||
|
|
5d1d9827e4 | ||
|
|
dd28ad20ef | ||
|
|
ef416ef60b | ||
|
|
95b3b55971 | ||
|
|
b3f32ae03e | ||
|
|
c7472174e5 | ||
|
|
2ad749354d | ||
|
|
4ed9d2ea22 | ||
|
|
280eb47de7 | ||
|
|
324a12b0ff | ||
|
|
a2543ccddc | ||
|
|
22666412c3 | ||
|
|
dd58044cdf | ||
|
|
10312d89d7 | ||
|
|
b4c0d877cb | ||
|
|
e95d56a5d0 | ||
|
|
90424e8329 | ||
|
|
1bfeb42a06 | ||
|
|
a936f92954 | ||
|
|
0bc514ec17 | ||
|
|
a2cf4001af | ||
|
|
cb4e12a68c | ||
|
|
a7f5124dfe | ||
|
|
ccbf71c5e7 | ||
|
|
04bf5f58d9 | ||
|
|
ab3f5956d4 | ||
|
|
c1fe8e583f | ||
|
|
fd166c4433 | ||
|
|
f29c7ba4f2 | ||
|
|
88869e9710 | ||
|
|
f8404ab043 | ||
|
|
9fa5d1ff9e | ||
|
|
483f353fd0 | ||
|
|
a11bf5b5c7 | ||
|
|
d4113ff753 | ||
|
|
1969f036fa | ||
|
|
8c90e01016 | ||
|
|
756c5c9b99 | ||
|
|
ee54b355af | ||
|
|
26cbbc0c56 | ||
|
|
f4f719d52a | ||
|
|
f2071d8b7e | ||
|
|
df88a55784 | ||
|
|
3ccbc626ff | ||
|
|
71a15cf222 | ||
|
|
26ddf769b1 | ||
|
|
3137387c0c | ||
|
|
fc142cfde8 | ||
|
|
b0503fa507 | ||
|
|
b86a97c9c0 | ||
|
|
eb6cd23772 | ||
|
|
efae1e7e6c | ||
|
|
19d55b840e | ||
|
|
cc0c1d05ab | ||
|
|
f088f65d5a | ||
|
|
5441b5a06b | ||
|
|
efc56c0a88 | ||
|
|
321fca2c0a | ||
|
|
bbd66e9cb0 | ||
|
|
eb0277146c | ||
|
|
10ee32ec48 | ||
|
|
bdb4be89ff | ||
|
|
61445e0b56 | ||
|
|
f15a010e0e | ||
|
|
58747004fe | ||
|
|
e7ff1eb66b | ||
|
|
4a00bd4797 | ||
|
|
2e6fc7e4a0 | ||
|
|
4a8f323be7 | ||
|
|
c7d82102ed | ||
|
|
068b861edc | ||
|
|
3c908c6a09 | ||
|
|
ba3805786c | ||
|
|
70afb197f1 | ||
|
|
d966e35054 | ||
|
|
1675570291 | ||
|
|
9b88de656e | ||
|
|
3d39b5653d | ||
|
|
eb5f7f64ad | ||
|
|
9fc0164c4d | ||
|
|
65eb520cca | ||
|
|
f7f07932b4 | ||
|
|
de52494039 | ||
|
|
4d87ee2bb6 | ||
|
|
d0ba0936ca | ||
|
|
b08556861f | ||
|
|
c96628ad49 | ||
|
|
a615882b3f | ||
|
|
2bcc8e0d30 | ||
|
|
de519edf78 | ||
|
|
caf47943c3 | ||
|
|
427ab12724 | ||
|
|
eba16c0cc3 | ||
|
|
a485de6359 | ||
|
|
1a985f7e82 | ||
|
|
7867411095 | ||
|
|
2f6ebd16c1 | ||
|
|
878b235614 | ||
|
|
75f9c6b0fb | ||
|
|
7c1e2bf96f | ||
|
|
181b44e117 | ||
|
|
f7793976fb | ||
|
|
8ffcd9b60a | ||
|
|
52d3c4d62d | ||
|
|
0fb3e75253 | ||
|
|
2c40e403c4 | ||
|
|
d1c519ed0d | ||
|
|
27470ef934 | ||
|
|
8a1da87702 | ||
|
|
c8d89f805b | ||
|
|
c9fceafc16 | ||
|
|
bbb9980941 | ||
|
|
da55d6f7cd | ||
|
|
eeacdc1359 | ||
|
|
ee1e92e1cb | ||
|
|
705802e584 | ||
|
|
b2e509f055 | ||
|
|
cca70764d4 | ||
|
|
3ac94710fb | ||
|
|
ca73a47785 | ||
|
|
1ef67fc8e9 | ||
|
|
8f3c2f4f3d | ||
|
|
e42b98ec17 | ||
|
|
efb318a979 | ||
|
|
3c0a82293c | ||
|
|
e867f31c31 | ||
|
|
aeb6da111b | ||
|
|
2736fa5202 | ||
|
|
4d3df867da | ||
|
|
62f78e4312 | ||
|
|
d223ac4675 | ||
|
|
c16404bb2d | ||
|
|
cf70933e21 | ||
|
|
46222e9352 | ||
|
|
212e94756b | ||
|
|
b42abbd4a2 | ||
|
|
730a55e721 | ||
|
|
06cf83b901 | ||
|
|
673e5af030 | ||
|
|
a0bc16c255 | ||
|
|
76b5234f7b | ||
|
|
928de47d1d | ||
|
|
274db6f606 | ||
|
|
89ca0ca927 | ||
|
|
8047008fa5 | ||
|
|
f914110626 | ||
|
|
5656fd0b96 | ||
|
|
c3d8c72302 | ||
|
|
1eefff9025 | ||
|
|
1dc7c7b0a4 | ||
|
|
011bac7b4f | ||
|
|
dc2d6e60d8 | ||
|
|
7809b6e50f | ||
|
|
f7f0370bf5 | ||
|
|
6300fc5364 | ||
|
|
16270cbd1a | ||
|
|
3b226dd2c0 | ||
|
|
4ac61d18ff | ||
|
|
fd7abdb8a4 |
34
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: 🐛 Bug Report
|
||||
description: Create a report to help us improve embassyOS
|
||||
title: '[bug]: '
|
||||
description: Create a report to help us improve StartOS
|
||||
title: "[bug]: "
|
||||
labels: [Bug, Needs Triage]
|
||||
assignees:
|
||||
- MattDHill
|
||||
@@ -10,27 +10,25 @@ body:
|
||||
label: Prerequisites
|
||||
description: Please confirm you have completed the following.
|
||||
options:
|
||||
- label: I have searched for [existing issues](https://github.com/start9labs/embassy-os/issues) that already report this problem.
|
||||
- label: I have searched for [existing issues](https://github.com/start9labs/start-os/issues) that already report this problem.
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: embassyOS Version
|
||||
description: What version of embassyOS are you running?
|
||||
placeholder: e.g. 0.3.0
|
||||
label: Server Hardware
|
||||
description: On what hardware are you running StartOS? Please be as detailed as possible!
|
||||
placeholder: Pi (8GB) w/ 32GB microSD & Samsung T7 SSD
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: StartOS Version
|
||||
description: What version of StartOS are you running?
|
||||
placeholder: e.g. 0.3.4.3
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Device
|
||||
description: What device are you using to connect to Embassy?
|
||||
options:
|
||||
- Phone/tablet
|
||||
- Laptop/Desktop
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Device OS
|
||||
label: Client OS
|
||||
description: What operating system is your device running?
|
||||
options:
|
||||
- MacOS
|
||||
@@ -45,14 +43,14 @@ body:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Device OS Version
|
||||
label: Client OS Version
|
||||
description: What version is your device OS?
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Browser
|
||||
description: What browser are you using to connect to Embassy?
|
||||
description: What browser are you using to connect to your server?
|
||||
options:
|
||||
- Firefox
|
||||
- Brave
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: 💡 Feature Request
|
||||
description: Suggest an idea for embassyOS
|
||||
title: '[feat]: '
|
||||
description: Suggest an idea for StartOS
|
||||
title: "[feat]: "
|
||||
labels: [Enhancement]
|
||||
assignees:
|
||||
- MattDHill
|
||||
@@ -10,7 +10,7 @@ body:
|
||||
label: Prerequisites
|
||||
description: Please confirm you have completed the following.
|
||||
options:
|
||||
- label: I have searched for [existing issues](https://github.com/start9labs/embassy-os/issues) that already suggest this feature.
|
||||
- label: I have searched for [existing issues](https://github.com/start9labs/start-os/issues) that already suggest this feature.
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
@@ -27,7 +27,7 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe Preferred Solution
|
||||
description: How you want this feature added to embassyOS?
|
||||
description: How you want this feature added to StartOS?
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe Alternatives
|
||||
|
||||
29
.github/workflows/README.md
vendored
@@ -1,29 +0,0 @@
|
||||
# This folder contains GitHub Actions workflows for building the project
|
||||
|
||||
## backend
|
||||
Runs: manually (on: workflow_dispatch) or called by product-pipeline (on: workflow_call)
|
||||
|
||||
This workflow uses the actions and docker/setup-buildx-action@v1 to prepare the environment for aarch64 cross complilation using docker buildx.
|
||||
When execution of aarch64 containers is required the action docker/setup-qemu-action@v1 is added.
|
||||
A matrix-strategy has been used to build for both x86_64 and aarch64 platforms in parallel.
|
||||
|
||||
### Running unittests
|
||||
|
||||
Unittests are run using [cargo-nextest]( https://nexte.st/). First the sources are (cross-)compiled and archived. The archive is then run on the correct platform.
|
||||
|
||||
## frontend
|
||||
Runs: manually (on: workflow_dispatch) or called by product-pipeline (on: workflow_call)
|
||||
|
||||
This workflow builds the frontends.
|
||||
|
||||
## product
|
||||
Runs: when a pull request targets the master or next branch and when a change to the master or next branch is made
|
||||
|
||||
This workflow builds everything, re-using the backend and frontend workflows.
|
||||
The download and extraction order of artifacts is relevant to `make`, as it checks the file timestamps to decide which targets need to be executed.
|
||||
|
||||
Result: eos.img
|
||||
|
||||
## a note on uploading artifacts
|
||||
|
||||
Artifacts are used to share data between jobs. File permissions are not maintained during artifact upload. Where file permissions are relevant, the workaround using tar has been used. See (here)[https://github.com/actions/upload-artifact#maintaining-file-permissions-and-case-sensitive-files].
|
||||
231
.github/workflows/backend.yaml
vendored
@@ -1,231 +0,0 @@
|
||||
name: Backend
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
RUST_VERSION: "1.62.1"
|
||||
ENVIRONMENT: "dev"
|
||||
|
||||
jobs:
|
||||
build_libs:
|
||||
name: Build libs
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [x86_64, aarch64]
|
||||
include:
|
||||
- target: x86_64
|
||||
snapshot_command: ./build-v8-snapshot.sh
|
||||
artifact_name: js_snapshot
|
||||
artifact_path: libs/js_engine/src/artifacts/JS_SNAPSHOT.bin
|
||||
- target: aarch64
|
||||
snapshot_command: ./build-arm-v8-snapshot.sh
|
||||
artifact_name: arm_js_snapshot
|
||||
artifact_path: libs/js_engine/src/artifacts/ARM_JS_SNAPSHOT.bin
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
if: ${{ matrix.target == 'aarch64' }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
if: ${{ matrix.target == 'aarch64' }}
|
||||
|
||||
- name: "Install Rust"
|
||||
run: |
|
||||
rustup toolchain install ${{ env.RUST_VERSION }} --profile minimal --no-self-update
|
||||
rustup default ${{ inputs.rust }}
|
||||
shell: bash
|
||||
if: ${{ matrix.target == 'x86_64' }}
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
libs/target/
|
||||
key: ${{ runner.os }}-cargo-libs-${{ matrix.target }}-${{ hashFiles('libs/Cargo.lock') }}
|
||||
|
||||
- name: Build v8 snapshot
|
||||
run: ${{ matrix.snapshot_command }}
|
||||
working-directory: libs
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ matrix.artifact_path }}
|
||||
|
||||
build_backend:
|
||||
name: Build backend
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [x86_64, aarch64]
|
||||
include:
|
||||
- target: x86_64
|
||||
snapshot_download: js_snapshot
|
||||
- target: aarch64
|
||||
snapshot_download: arm_js_snapshot
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
needs: build_libs
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download ${{ matrix.snapshot_download }} artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.snapshot_download }}
|
||||
path: libs/js_engine/src/artifacts/
|
||||
|
||||
- name: "Install Rust"
|
||||
run: |
|
||||
rustup toolchain install ${{ env.RUST_VERSION }} --profile minimal --no-self-update
|
||||
rustup default ${{ inputs.rust }}
|
||||
shell: bash
|
||||
if: ${{ matrix.target == 'x86_64' }}
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
backend/target/
|
||||
key: ${{ runner.os }}-cargo-backend-${{ matrix.target }}-${{ hashFiles('backend/Cargo.lock') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install libavahi-client-dev
|
||||
if: ${{ matrix.target == 'x86_64' }}
|
||||
|
||||
- name: Check Git Hash
|
||||
run: ./check-git-hash.sh
|
||||
|
||||
- name: Check Environment
|
||||
run: ./check-environment.sh
|
||||
|
||||
- name: Build backend
|
||||
run: make ARCH=${{ matrix.target }} backend
|
||||
|
||||
- name: 'Tar files to preserve file permissions'
|
||||
run: make ARCH=${{ matrix.target }} backend-${{ matrix.target }}.tar
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: backend-${{ matrix.target }}
|
||||
path: backend-${{ matrix.target }}.tar
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Build and archive tests
|
||||
run: cargo nextest archive --archive-file nextest-archive-${{ matrix.target }}.tar.zst --target ${{ matrix.target }}-unknown-linux-gnu
|
||||
working-directory: backend
|
||||
if: ${{ matrix.target == 'x86_64' }}
|
||||
|
||||
- name: Build and archive tests
|
||||
run: |
|
||||
docker run --rm \
|
||||
-v "$HOME/.cargo/registry":/root/.cargo/registry \
|
||||
-v "$(pwd)":/home/rust/src \
|
||||
-P start9/rust-arm-cross:aarch64 \
|
||||
sh -c 'cd /home/rust/src/backend &&
|
||||
rustup install ${{ env.RUST_VERSION }} &&
|
||||
rustup override set ${{ env.RUST_VERSION }} &&
|
||||
rustup target add aarch64-unknown-linux-gnu &&
|
||||
curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin &&
|
||||
cargo nextest archive --archive-file nextest-archive-${{ matrix.target }}.tar.zst --target ${{ matrix.target }}-unknown-linux-gnu'
|
||||
if: ${{ matrix.target == 'aarch64' }}
|
||||
|
||||
- name: Reset permissions
|
||||
run: sudo chown -R $USER target
|
||||
working-directory: backend
|
||||
if: ${{ matrix.target == 'aarch64' }}
|
||||
|
||||
- name: Upload archive to workflow
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: nextest-archive-${{ matrix.target }}
|
||||
path: backend/nextest-archive-${{ matrix.target }}.tar.zst
|
||||
|
||||
run_tests_backend:
|
||||
name: Test backend
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [x86_64, aarch64]
|
||||
include:
|
||||
- target: x86_64
|
||||
- target: aarch64
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
needs: build_backend
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
if: ${{ matrix.target == 'aarch64' }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
if: ${{ matrix.target == 'aarch64' }}
|
||||
|
||||
- run: mkdir -p ~/.cargo/bin
|
||||
if: ${{ matrix.target == 'x86_64' }}
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@nextest
|
||||
if: ${{ matrix.target == 'x86_64' }}
|
||||
|
||||
- name: Download archive
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: nextest-archive-${{ matrix.target }}
|
||||
|
||||
- name: Download nextest (aarch64)
|
||||
run: wget -O nextest-aarch64.tar.gz https://get.nexte.st/latest/linux-arm
|
||||
if: ${{ matrix.target == 'aarch64' }}
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
${CARGO_HOME:-~/.cargo}/bin/cargo-nextest nextest run --no-fail-fast --archive-file nextest-archive-${{ matrix.target }}.tar.zst \
|
||||
--filter-expr 'not (test(system::test_get_temp) | test(net::tor::test) | test(system::test_get_disk_usage) | test(net::ssl::certificate_details_persist) | test(net::ssl::ca_details_persist))'
|
||||
if: ${{ matrix.target == 'x86_64' }}
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
docker run --rm --platform linux/arm64/v8 \
|
||||
-v "/home/runner/.cargo/registry":/usr/local/cargo/registry \
|
||||
-v "$(pwd)":/home/rust/src \
|
||||
-e CARGO_TERM_COLOR=${{ env.CARGO_TERM_COLOR }} \
|
||||
-P ubuntu:20.04 \
|
||||
sh -c '
|
||||
apt-get update &&
|
||||
apt-get install -y ca-certificates &&
|
||||
apt-get install -y rsync &&
|
||||
cd /home/rust/src &&
|
||||
mkdir -p ~/.cargo/bin &&
|
||||
tar -zxvf nextest-aarch64.tar.gz -C ${CARGO_HOME:-~/.cargo}/bin &&
|
||||
${CARGO_HOME:-~/.cargo}/bin/cargo-nextest nextest run --archive-file nextest-archive-${{ matrix.target }}.tar.zst \
|
||||
--filter-expr "not (test(system::test_get_temp) | test(net::tor::test) | test(system::test_get_disk_usage) | test(net::ssl::certificate_details_persist) | test(net::ssl::ca_details_persist))"'
|
||||
if: ${{ matrix.target == 'aarch64' }}
|
||||
63
.github/workflows/debian.yaml
vendored
@@ -1,63 +0,0 @@
|
||||
name: Debian Package
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
NODEJS_VERSION: '16'
|
||||
ENVIRONMENT: "dev"
|
||||
|
||||
jobs:
|
||||
dpkg:
|
||||
name: Build dpkg
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: Start9Labs/embassy-os-deb
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
path: embassyos-0.3.x
|
||||
- run: |
|
||||
cp -r debian embassyos-0.3.x/
|
||||
VERSION=0.3.x ./control.sh
|
||||
cp embassyos-0.3.x/backend/embassyd.service embassyos-0.3.x/debian/embassyos.embassyd.service
|
||||
cp embassyos-0.3.x/backend/embassy-init.service embassyos-0.3.x/debian/embassyos.embassy-init.service
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODEJS_VERSION }}
|
||||
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache-dir
|
||||
run: |
|
||||
echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v3
|
||||
id: npm-cache
|
||||
with:
|
||||
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install debmake debhelper-compat
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Run build
|
||||
run: "make VERSION=0.3.x TAG=${{ github.ref_name }}"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: deb
|
||||
path: embassyos_0.3.x-1_amd64.deb
|
||||
46
.github/workflows/frontend.yaml
vendored
@@ -1,46 +0,0 @@
|
||||
name: Frontend
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
NODEJS_VERSION: '16'
|
||||
ENVIRONMENT: "dev"
|
||||
|
||||
jobs:
|
||||
frontend:
|
||||
name: Build frontend
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODEJS_VERSION }}
|
||||
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache-dir
|
||||
run: |
|
||||
echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v3
|
||||
id: npm-cache
|
||||
with:
|
||||
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Build frontends
|
||||
run: make frontends
|
||||
|
||||
- name: 'Tar files to preserve file permissions'
|
||||
run: tar -cvf frontend.tar ENVIRONMENT.txt GIT_HASH.txt VERSION.txt frontend/dist frontend/config.json
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: frontend
|
||||
path: frontend.tar
|
||||
142
.github/workflows/product.yaml
vendored
@@ -1,142 +0,0 @@
|
||||
name: Build Pipeline
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
|
||||
env:
|
||||
ENVIRONMENT: "dev"
|
||||
|
||||
jobs:
|
||||
compat:
|
||||
uses: ./.github/workflows/reusable-workflow.yaml
|
||||
with:
|
||||
build_command: make system-images/compat/docker-images/aarch64.tar
|
||||
artifact_name: compat.tar
|
||||
artifact_path: system-images/compat/docker-images/aarch64.tar
|
||||
|
||||
utils:
|
||||
uses: ./.github/workflows/reusable-workflow.yaml
|
||||
with:
|
||||
build_command: make system-images/utils/docker-images/aarch64.tar
|
||||
artifact_name: utils.tar
|
||||
artifact_path: system-images/utils/docker-images/aarch64.tar
|
||||
|
||||
binfmt:
|
||||
uses: ./.github/workflows/reusable-workflow.yaml
|
||||
with:
|
||||
build_command: make system-images/binfmt/docker-images/aarch64.tar
|
||||
artifact_name: binfmt.tar
|
||||
artifact_path: system-images/binfmt/docker-images/aarch64.tar
|
||||
|
||||
nc-broadcast:
|
||||
uses: ./.github/workflows/reusable-workflow.yaml
|
||||
with:
|
||||
build_command: make cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast
|
||||
artifact_name: nc-broadcast.tar
|
||||
artifact_path: cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast
|
||||
|
||||
backend:
|
||||
uses: ./.github/workflows/backend.yaml
|
||||
|
||||
frontend:
|
||||
uses: ./.github/workflows/frontend.yaml
|
||||
|
||||
image:
|
||||
name: Build image
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
needs: [compat,utils,binfmt,nc-broadcast,backend,frontend]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download compat.tar artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: compat.tar
|
||||
path: system-images/compat/docker-images/
|
||||
|
||||
- name: Download utils.tar artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: utils.tar
|
||||
path: system-images/utils/docker-images/
|
||||
|
||||
- name: Download binfmt.tar artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: binfmt.tar
|
||||
path: system-images/binfmt/docker-images/
|
||||
|
||||
- name: Download nc-broadcast.tar artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: nc-broadcast.tar
|
||||
path: cargo-deps/aarch64-unknown-linux-gnu/release
|
||||
|
||||
- name: Download js_snapshot artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: js_snapshot
|
||||
path: libs/js_engine/src/artifacts/
|
||||
|
||||
- name: Download arm_js_snapshot artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: arm_js_snapshot
|
||||
path: libs/js_engine/src/artifacts/
|
||||
|
||||
- name: Download backend artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: backend-aarch64
|
||||
|
||||
- name: 'Extract backend'
|
||||
run:
|
||||
tar -mxvf backend-aarch64.tar
|
||||
|
||||
- name: Download frontend artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: frontend
|
||||
|
||||
- name: Skip frontend build
|
||||
run: |
|
||||
mkdir frontend/node_modules
|
||||
mkdir frontend/dist
|
||||
mkdir patch-db/client/node_modules
|
||||
mkdir patch-db/client/dist
|
||||
|
||||
- name: 'Extract frontend'
|
||||
run: |
|
||||
tar -mxvf frontend.tar frontend/config.json
|
||||
tar -mxvf frontend.tar frontend/dist
|
||||
tar -xvf frontend.tar GIT_HASH.txt
|
||||
tar -xvf frontend.tar ENVIRONMENT.txt
|
||||
tar -xvf frontend.tar VERSION.txt
|
||||
rm frontend.tar
|
||||
|
||||
- name: Cache raspiOS
|
||||
id: cache-raspios
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: raspios.img
|
||||
key: cache-raspios
|
||||
|
||||
- name: Build image
|
||||
run: |
|
||||
make V=1 embassyos-raspi.img --debug
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: image
|
||||
path: embassyos-raspi.img
|
||||
70
.github/workflows/pureos-iso.yaml
vendored
@@ -1,70 +0,0 @@
|
||||
name: PureOS Based ISO
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
|
||||
env:
|
||||
ENVIRONMENT: "dev"
|
||||
|
||||
jobs:
|
||||
dpkg:
|
||||
uses: ./.github/workflows/debian.yaml
|
||||
|
||||
iso:
|
||||
name: Build iso
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [dpkg]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: Start9Labs/eos-image-recipes
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
wget http://ftp.us.debian.org/debian/pool/main/d/debspawn/debspawn_0.6.0-1.1_all.deb
|
||||
sha256sum ./debspawn_0.6.0-1.1_all.deb | grep 6cdb444844825b82ef388378f2c508fd8886f79a49800d4547a353ea6772d493
|
||||
sudo apt-get install -y ./debspawn_0.6.0-1.1_all.deb
|
||||
wget https://repo.pureos.net/pureos/pool/main/d/debootstrap/debootstrap_1.0.125pureos1_all.deb
|
||||
sudo apt-get install -y --allow-downgrades ./debootstrap_1.0.125pureos1_all.deb
|
||||
wget https://repo.pureos.net/pureos/pool/main/p/pureos-archive-keyring/pureos-archive-keyring_2021.11.0_all.deb
|
||||
sudo apt-get install -y ./pureos-archive-keyring_2021.11.0_all.deb
|
||||
|
||||
- name: Configure debspawn
|
||||
run: |
|
||||
sudo mkdir -p /etc/debspawn/
|
||||
echo "AllowUnsafePermissions=true" | sudo tee /etc/debspawn/global.toml
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: /var/lib/debspawn
|
||||
key: ${{ runner.os }}-debspawn-init-byzantium
|
||||
|
||||
- name: Make build container
|
||||
run: "debspawn list | grep byzantium || debspawn create --with-init byzantium"
|
||||
|
||||
- run: "mkdir -p overlays/vendor/root"
|
||||
|
||||
- name: Download dpkg
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: deb
|
||||
path: overlays/vendor/root
|
||||
|
||||
- name: Run build
|
||||
run: |
|
||||
./run-local-build.sh --no-fakemachine byzantium none custom "" true
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: iso
|
||||
path: results/*.iso
|
||||
37
.github/workflows/reusable-workflow.yaml
vendored
@@ -1,37 +0,0 @@
|
||||
name: Reusable Workflow
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
build_command:
|
||||
required: true
|
||||
type: string
|
||||
artifact_name:
|
||||
required: true
|
||||
type: string
|
||||
artifact_path:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
ENVIRONMENT: "dev"
|
||||
|
||||
jobs:
|
||||
generic_build_job:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Build image
|
||||
run: ${{ inputs.build_command }}
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}
|
||||
path: ${{ inputs.artifact_path }}
|
||||
237
.github/workflows/startos-iso.yaml
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
name: Debian-based ISO and SquashFS
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
type: choice
|
||||
description: Environment
|
||||
options:
|
||||
- NONE
|
||||
- dev
|
||||
- unstable
|
||||
- dev-unstable
|
||||
- docker
|
||||
- dev-docker
|
||||
- dev-unstable-docker
|
||||
runner:
|
||||
type: choice
|
||||
description: Runner
|
||||
options:
|
||||
- standard
|
||||
- fast
|
||||
platform:
|
||||
type: choice
|
||||
description: Platform
|
||||
options:
|
||||
- ALL
|
||||
- x86_64
|
||||
- x86_64-nonfree
|
||||
- aarch64
|
||||
- aarch64-nonfree
|
||||
- raspberrypi
|
||||
deploy:
|
||||
type: choice
|
||||
description: Deploy
|
||||
options:
|
||||
- NONE
|
||||
- alpha
|
||||
- beta
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
|
||||
env:
|
||||
NODEJS_VERSION: "18.15.0"
|
||||
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||
|
||||
jobs:
|
||||
compile:
|
||||
name: Compile Base Binaries
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
arch: >-
|
||||
${{
|
||||
fromJson('{
|
||||
"x86_64": ["x86_64"],
|
||||
"x86_64-nonfree": ["x86_64"],
|
||||
"aarch64": ["aarch64"],
|
||||
"aarch64-nonfree": ["aarch64"],
|
||||
"raspberrypi": ["aarch64"],
|
||||
"ALL": ["x86_64", "aarch64"]
|
||||
}')[github.event.inputs.platform || 'ALL']
|
||||
}}
|
||||
runs-on: ${{ fromJson('["ubuntu-22.04", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||
steps:
|
||||
- run: |
|
||||
sudo mount -t tmpfs tmpfs .
|
||||
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODEJS_VERSION }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Make
|
||||
run: make ARCH=${{ matrix.arch }} compiled-${{ matrix.arch }}.tar
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: compiled-${{ matrix.arch }}.tar
|
||||
path: compiled-${{ matrix.arch }}.tar
|
||||
image:
|
||||
name: Build Image
|
||||
needs: [compile]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: >-
|
||||
${{
|
||||
fromJson(
|
||||
format(
|
||||
'[
|
||||
["{0}"],
|
||||
["x86_64", "x86_64-nonfree", "aarch64", "aarch64-nonfree", "raspberrypi"]
|
||||
]',
|
||||
github.event.inputs.platform || 'ALL'
|
||||
)
|
||||
)[(github.event.inputs.platform || 'ALL') == 'ALL']
|
||||
}}
|
||||
runs-on: >-
|
||||
${{
|
||||
fromJson(
|
||||
format(
|
||||
'["ubuntu-22.04", "{0}"]',
|
||||
fromJson('{
|
||||
"x86_64": "buildjet-8vcpu-ubuntu-2204",
|
||||
"x86_64-nonfree": "buildjet-8vcpu-ubuntu-2204",
|
||||
"aarch64": "buildjet-8vcpu-ubuntu-2204-arm",
|
||||
"aarch64-nonfree": "buildjet-8vcpu-ubuntu-2204-arm",
|
||||
"raspberrypi": "buildjet-8vcpu-ubuntu-2204-arm",
|
||||
}')[matrix.platform]
|
||||
)
|
||||
)[github.event.inputs.runner == 'fast']
|
||||
}}
|
||||
env:
|
||||
ARCH: >-
|
||||
${{
|
||||
fromJson('{
|
||||
"x86_64": "x86_64",
|
||||
"x86_64-nonfree": "x86_64",
|
||||
"aarch64": "aarch64",
|
||||
"aarch64-nonfree": "aarch64",
|
||||
"raspberrypi": "aarch64",
|
||||
}')[matrix.platform]
|
||||
}}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y qemu-user-static
|
||||
wget https://deb.debian.org/debian/pool/main/d/debspawn/debspawn_0.6.2-1_all.deb
|
||||
sha256sum ./debspawn_0.6.2-1_all.deb | grep 37ef27458cb1e35e8bce4d4f639b06b4b3866fc0b9191ec6b9bd157afd06a817
|
||||
sudo apt-get install -y ./debspawn_0.6.2-1_all.deb
|
||||
|
||||
- name: Configure debspawn
|
||||
run: |
|
||||
sudo mkdir -p /etc/debspawn/
|
||||
echo "AllowUnsafePermissions=true" | sudo tee /etc/debspawn/global.toml
|
||||
sudo mkdir -p /var/tmp/debspawn
|
||||
|
||||
- run: sudo mount -t tmpfs tmpfs /var/tmp/debspawn
|
||||
if: ${{ github.event.inputs.runner == 'fast' && (matrix.platform == 'x86_64' || matrix.platform == 'x86_64-nonfree') }}
|
||||
|
||||
- name: Download compiled artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: compiled-${{ env.ARCH }}.tar
|
||||
|
||||
- name: Extract compiled artifacts
|
||||
run: tar -xvf compiled-${{ env.ARCH }}.tar
|
||||
|
||||
- name: Prevent rebuild of compiled artifacts
|
||||
run: |
|
||||
mkdir -p frontend/dist/raw
|
||||
PLATFORM=${{ matrix.platform }} make -t compiled-${{ env.ARCH }}.tar
|
||||
|
||||
- name: Run iso build
|
||||
run: PLATFORM=${{ matrix.platform }} make iso
|
||||
if: ${{ matrix.platform != 'raspberrypi' }}
|
||||
|
||||
- name: Run img build
|
||||
run: PLATFORM=${{ matrix.platform }} make img
|
||||
if: ${{ matrix.platform == 'raspberrypi' }}
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.platform }}.squashfs
|
||||
path: results/*.squashfs
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.platform }}.iso
|
||||
path: results/*.iso
|
||||
if: ${{ matrix.platform != 'raspberrypi' }}
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.platform }}.img
|
||||
path: results/*.img
|
||||
if: ${{ matrix.platform == 'raspberrypi' }}
|
||||
|
||||
- name: Upload OTA to registry
|
||||
run: >-
|
||||
PLATFORM=${{ matrix.platform }} make upload-ota TARGET="${{
|
||||
fromJson('{
|
||||
"alpha": "alpha-registry-x.start9.com",
|
||||
"beta": "beta-registry.start9.com",
|
||||
}')[github.event.inputs.deploy]
|
||||
}}" KEY="${{
|
||||
fromJson(
|
||||
format('{{
|
||||
"alpha": "{0}",
|
||||
"beta": "{1}",
|
||||
}}', secrets.ALPHA_INDEX_KEY, secrets.BETA_INDEX_KEY)
|
||||
)[github.event.inputs.deploy]
|
||||
}}"
|
||||
if: ${{ github.event.inputs.deploy != '' && github.event.inputs.deploy != 'NONE' }}
|
||||
|
||||
index:
|
||||
if: ${{ github.event.inputs.deploy != '' && github.event.inputs.deploy != 'NONE' }}
|
||||
needs: [image]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- run: >-
|
||||
curl "https://${{
|
||||
fromJson('{
|
||||
"alpha": "alpha-registry-x.start9.com",
|
||||
"beta": "beta-registry.start9.com",
|
||||
}')[github.event.inputs.deploy]
|
||||
}}:8443/resync.cgi?key=${{
|
||||
fromJson(
|
||||
format('{{
|
||||
"alpha": "{0}",
|
||||
"beta": "{1}",
|
||||
}}', secrets.ALPHA_INDEX_KEY, secrets.BETA_INDEX_KEY)
|
||||
)[github.event.inputs.deploy]
|
||||
}}"
|
||||
8
.gitignore
vendored
@@ -16,13 +16,15 @@ deploy_web.sh
|
||||
secrets.db
|
||||
.vscode/
|
||||
/cargo-deps/**/*
|
||||
/PLATFORM.txt
|
||||
/ENVIRONMENT.txt
|
||||
/GIT_HASH.txt
|
||||
/VERSION.txt
|
||||
/embassyos-*.tar.gz
|
||||
/eos-*.tar.gz
|
||||
/*.deb
|
||||
/target
|
||||
/*.squashfs
|
||||
/debian
|
||||
/DEBIAN
|
||||
/results
|
||||
/dpkg-workdir
|
||||
/compiled.tar
|
||||
/compiled-*.tar
|
||||
429
CHANGELOG.md
Normal file
@@ -0,0 +1,429 @@
|
||||
# v0.3.3
|
||||
## Highlights
|
||||
- x86_64 architecture compatibility
|
||||
- Kiosk mode - use your Embassy with monitor, keyboard, and mouse (available on x86 builds only, disabled on Raspberry Pi)
|
||||
- "Updates" tab - view all service updates from all registries in one place
|
||||
- Various UI/UX improvements
|
||||
- Various bugfixes and optimizations
|
||||
|
||||
## What's Changed
|
||||
- Minor typo fixes by @kn0wmad in #1887
|
||||
- Update build pipeline by @moerketh in #1896
|
||||
- Feature/setup migrate by @elvece in #1841
|
||||
- Feat/patch migration by @Blu-J in #1890
|
||||
- make js cancellable by @dr-bonez in #1901
|
||||
- wip: Making Injectable exec by @Blu-J in #1897
|
||||
- Fix/debug by @Blu-J in #1909
|
||||
- chore: Fix on the rsync not having stdout. by @Blu-J in #1911
|
||||
- install wizard project by @MattDHill in #1893
|
||||
- chore: Remove the duplicate loggging information that is making usele… by @Blu-J in #1912
|
||||
- Http proxy by @redragonx in #1772
|
||||
- fix(marketplace): loosen type in categories component by @waterplea in #1918
|
||||
- set custom meta title by @MattDHill in #1915
|
||||
- Feature/git hash by @dr-bonez in #1919
|
||||
- closes #1900 by @dr-bonez in #1920
|
||||
- feature/marketplace icons by @dr-bonez in #1921
|
||||
- Bugfix/0.3.3 migration by @dr-bonez in #1922
|
||||
- feat: Exposing the rsync that we have to the js by @Blu-J in #1907
|
||||
- Feature/install wizard disk info by @dr-bonez in #1923
|
||||
- bump shared and marketplace npm versions by @dr-bonez in #1924
|
||||
- fix error handling when store unreachable by @dr-bonez in #1925
|
||||
- wait for network online before launching init by @dr-bonez in #1930
|
||||
- silence service crash notifications by @dr-bonez in #1929
|
||||
- disable efi by @dr-bonez in #1931
|
||||
- Tor daemon fix by @redragonx in #1934
|
||||
- wait for url to be available before launching kiosk by @dr-bonez in #1933
|
||||
- fix migration to support portable fatties by @dr-bonez in #1935
|
||||
- Add guid to partition type by @MattDHill in #1932
|
||||
- add localhost support to the http server by @redragonx in #1939
|
||||
- refactor setup wizard by @dr-bonez in #1937
|
||||
- feat(shared): Ticker add new component and use it in marketplace by @waterplea in #1940
|
||||
- feat: For ota update using rsyncd by @Blu-J in #1938
|
||||
- Feat/update progress by @MattDHill in #1944
|
||||
- Fix/app show hidden by @MattDHill in #1948
|
||||
- create dpkg and iso workflows by @dr-bonez in #1941
|
||||
- changing ip addr type by @redragonx in #1950
|
||||
- Create mountpoints first by @k0gen in #1949
|
||||
- Hard code registry icons by @MattDHill in #1951
|
||||
- fix: Cleanup by sending a command and kill when dropped by @Blu-J in #1945
|
||||
- Update setup wizard styling by @elvece in #1954
|
||||
- Feature/homepage by @elvece in #1956
|
||||
- Fix millis by @Blu-J in #1960
|
||||
- fix accessing dev tools by @MattDHill in #1966
|
||||
- Update/misc UI fixes by @elvece in #1961
|
||||
- Embassy-init typo by @redragonx in #1959
|
||||
- feature: 0.3.2 -> 0.3.3 upgrade by @dr-bonez in #1958
|
||||
- Fix/migrate by @Blu-J in #1962
|
||||
- chore: Make validation reject containers by @Blu-J in #1970
|
||||
- get pubkey and encrypt password on login by @elvece in #1965
|
||||
- Multiple bugs and styling by @MattDHill in #1975
|
||||
- filter out usb stick during install by @dr-bonez in #1974
|
||||
- fix http upgrades by @dr-bonez in #1980
|
||||
- restore interfaces before creating manager by @dr-bonez in #1982
|
||||
- fuckit: no patch db locks by @dr-bonez in #1969
|
||||
- fix websocket hangup error by @dr-bonez in #1981
|
||||
- revert app show to use header and fix back button by @MattDHill in #1984
|
||||
- Update/marketplace info by @elvece in #1983
|
||||
- force docker image removal by @dr-bonez in #1985
|
||||
- do not error if cannot determine live usb device by @dr-bonez in #1986
|
||||
- remove community registry from FE defaults by @MattDHill in #1988
|
||||
- check environment by @dr-bonez in #1990
|
||||
- fix marketplace search and better category disabling by @MattDHill in #1991
|
||||
- better migration progress bar by @dr-bonez in #1993
|
||||
- bump cargo version by @dr-bonez in #1995
|
||||
- preload icons and pause on setup complete for kiosk mode by @MattDHill in #1997
|
||||
- use squashfs for rpi updates by @dr-bonez in #1998
|
||||
- do not start progress at 0 before diff complete by @dr-bonez in #1999
|
||||
- user must click continue in kiosk on success page by @MattDHill in #2001
|
||||
- fix regex in image rip script by @dr-bonez in #2002
|
||||
- fix bug with showing embassy drives and center error text by @MattDHill in #2006
|
||||
- fix partition type by @dr-bonez in #2007
|
||||
- lowercase service for alphabetic sorting by @MattDHill in #2008
|
||||
- dont add updates cat by @MattDHill in #2009
|
||||
- make downloaded page a full html doc by @MattDHill in #2011
|
||||
- wait for monitor to be attached before launching firefox by @chrisguida in #2005
|
||||
- UI fixes by @elvece in #2014
|
||||
- fix: Stop service before by @Blu-J in #2019
|
||||
- shield links update by @k0gen in #2018
|
||||
- fix: Undoing the breaking introduced by trying to stopp by @Blu-J in #2023
|
||||
- update link rename from embassy -> system by @elvece in #2027
|
||||
- initialize embassy before restoring packages by @dr-bonez in #2029
|
||||
- make procfs an optional dependency so sdk can build on macos by @elvece in #2028
|
||||
- take(1) for recover select by @MattDHill in #2030
|
||||
- take one from server info to prevent multiple reqs to registries by @MattDHill in #2032
|
||||
- remove write lock during backup by @MattDHill in #2033
|
||||
- fix: Ensure that during migration we make the urls have a trailing slash by @Blu-J in #2036
|
||||
- fix: Make the restores limited # restore at a time by @Blu-J in #2037
|
||||
- fix error and display of unknown font weight on success page by @elvece in #2038
|
||||
|
||||
## Checksums
|
||||
```
|
||||
8602e759d3ece7cf503b9ca43e8419109f14e424617c2703b3771c8801483d7e embassyos_amd64.deb
|
||||
b5c0d8d1af760881a1b5cf32bd7c5b1d1cf6468f6da594a1b4895a866d03a58c embassyos_amd64.iso
|
||||
fe518453a7e1a8d8c2be43223a1a12adff054468f8082df0560e1ec50df3dbfd embassyos_raspberrypi.img
|
||||
7b1ff0ada27b6714062aa991ec31c2d95ac4edf254cd464a4fa251905aa47ebd embassyos_raspberrypi.tar.gz
|
||||
```
|
||||
|
||||
# v0.3.2.1
|
||||
## What's Changed
|
||||
- Update index.html copy and styling by @elvece in #1855
|
||||
- increase maximum avahi entry group size by @dr-bonez in #1869
|
||||
- bump version by @dr-bonez in #1871
|
||||
|
||||
### Linux and Mac
|
||||
|
||||
Download the `eos.tar.gz` file, then extract and flash the resulting eos.img to your SD Card
|
||||
Windows
|
||||
|
||||
Download the `eos.zip` file, then extract and flash the resulting eos.img to your SD Card
|
||||
|
||||
## SHA-256 Checksums
|
||||
```
|
||||
c4b17658910dd10c37df134d5d5fdd6478f962ba1b803d24477d563d44430f96 eos.tar.gz
|
||||
3a8b29878fe222a9d7cbf645c975b12805704b0f39c7daa46033d22380f9828c eos.zip
|
||||
dedff3eb408ea411812b8f46e6c6ed32bfbd97f61ec2b85a6be40373c0528256 eos.img
|
||||
```
|
||||
|
||||
# v0.3.2
|
||||
## Highlights
|
||||
- Autoscrolling for logs
|
||||
- Improved connectivity between browser and Embassy
|
||||
- Switch to Postgres for EOS database for better performance
|
||||
- Multiple bug fixes and under-the-hood improvements
|
||||
- Various UI/UX enhancements
|
||||
- Removal of product keys
|
||||
|
||||
Update Hash (SHA256): `d8ce908b06baee6420b45be1119e5eb9341ba8df920d1e255f94d1ffb7cc4de9`
|
||||
|
||||
Image Hash (SHA256): `e035cd764e5ad9eb1c60e2f7bc3b9bd7248f42a91c69015c8a978a0f94b90bbb`
|
||||
|
||||
Note: This image was uploaded as a gzipped POSIX sparse TAR file. The recommended command for unpacking it on systems that support sparse files is `tar --format=posix --sparse -zxvf eos.tar.gz`
|
||||
|
||||
## What's Changed
|
||||
- formatting by @dr-bonez in #1698
|
||||
- Update README.md by @kn0wmad in #1705
|
||||
- Update README.md by @dr-bonez in #1703
|
||||
- feat: migrate to Angular 14 and RxJS 7 by @waterplea in #1681
|
||||
- 0312 multiple FE by @MattDHill in #1712
|
||||
- Fix http requests by @MattDHill in #1717
|
||||
- Add build-essential to README.md by @chrisguida in #1716
|
||||
- write image to sparse-aware archive format by @dr-bonez in #1709
|
||||
- fix: Add modification to the max_user_watches by @Blu-J in #1695
|
||||
- [Feat] follow logs by @chrisguida in #1714
|
||||
- Update README.md by @dr-bonez in #1728
|
||||
- fix build for patch-db client for consistency by @elvece in #1722
|
||||
- fix cli install by @chrisguida in #1720
|
||||
- highlight instructions if not viewed by @MattDHill in #1731
|
||||
- Feat: HttpReader by @redragonx in #1733
|
||||
- Bugfix/dns by @dr-bonez in #1741
|
||||
- add x86 build and run unittests to backend pipeline by @moerketh in #1682
|
||||
- [Fix] websocket connecting and patchDB connection monitoring by @MattDHill in #1738
|
||||
- Set pipeline job timeouts and add ca-certificates to test container by @moerketh in #1753
|
||||
- Disable bluetooth properly #862 by @redragonx in #1745
|
||||
- [feat]: resumable downloads by @dr-bonez in #1746
|
||||
- Fix/empty properties by @elvece in #1764
|
||||
- use hostname from patchDB as default server name by @MattDHill in #1758
|
||||
- switch to postgresql by @dr-bonez in #1763
|
||||
- remove product key from setup flow by @MattDHill in #1750
|
||||
- pinning cargo dep versions for CLI by @redragonx in #1775
|
||||
- fix: Js deep dir by @Blu-J in #1784
|
||||
- 0.3.2 final cleanup by @dr-bonez in #1782
|
||||
- expect ui marketplace to be undefined by @MattDHill in #1787
|
||||
- fix init to exit on failure by @dr-bonez in #1788
|
||||
- fix search to return more accurate results by @MattDHill in #1792
|
||||
- update backend dependencies by @dr-bonez in #1796
|
||||
- use base64 for HTTP headers by @dr-bonez in #1795
|
||||
- fix: Bad cert of *.local.local is now fixed to correct. by @Blu-J in #1798
|
||||
- fix duplicate patch updates, add scroll button to setup success by @MattDHill in #1800
|
||||
- level_slider reclaiming that precious RAM memory by @k0gen in #1799
|
||||
- stop leaking avahi clients by @dr-bonez in #1802
|
||||
- fix: Deep is_parent was wrong and could be escapped by @Blu-J in #1801
|
||||
- prevent cfg str generation from running forever by @dr-bonez in #1804
|
||||
- better RPC error message by @MattDHill in #1803
|
||||
- Bugfix/marketplace add by @elvece in #1805
|
||||
- fix mrketplace swtiching by @MattDHill in #1810
|
||||
- clean up code and logs by @MattDHill in #1809
|
||||
- fix: Minor fix that matt wanted by @Blu-J in #1808
|
||||
- onion replace instead of adding tor repository by @k0gen in #1813
|
||||
- bank Start as embassy hostname from the begining by @k0gen in #1814
|
||||
- add descriptions to marketplace list page by @elvece in #1812
|
||||
- Fix/encryption by @elvece in #1811
|
||||
- restructure initialization by @dr-bonez in #1816
|
||||
- update license by @MattDHill in #1819
|
||||
- perform system rebuild after updating by @dr-bonez in #1820
|
||||
- ignore file not found error for delete by @dr-bonez in #1822
|
||||
- Multiple by @MattDHill in #1823
|
||||
- Bugfix/correctly package backend job by @moerketh in #1826
|
||||
- update patch-db by @dr-bonez in #1831
|
||||
- give name to logs file by @MattDHill in #1833
|
||||
- play song during update by @dr-bonez in #1832
|
||||
- Seed patchdb UI data by @elvece in #1835
|
||||
- update patch db and enable logging by @dr-bonez in #1837
|
||||
- reduce patch-db log level to warn by @dr-bonez in #1840
|
||||
- update ts matches to fix properties ordering bug by @elvece in #1843
|
||||
- handle multiple image tags having the same hash and increase timeout by @dr-bonez in #1844
|
||||
- retry pgloader up to 5x by @dr-bonez in #1845
|
||||
- show connection bar right away by @MattDHill in #1849
|
||||
- dizzy Rebranding to embassyOS by @k0gen in #1851
|
||||
- update patch db by @MattDHill in #1852
|
||||
- camera_flash screenshots update by @k0gen in #1853
|
||||
- disable concurrency and delete tmpdir before retry by @dr-bonez in #1846
|
||||
|
||||
## New Contributors
|
||||
|
||||
- @redragonx made their first contribution in #1733
|
||||
|
||||
|
||||
# v0.3.1.1
|
||||
## What's Changed
|
||||
|
||||
- whale2 docker stats fix by @k0gen in #1630
|
||||
- update backend dependencies by @dr-bonez in #1637
|
||||
- Fix/receipts health by @Blu-J in #1616
|
||||
- return correct error on failed os download by @dr-bonez in #1636
|
||||
- fix build by @dr-bonez in #1639
|
||||
- Update product.yaml by @dr-bonez in #1638
|
||||
- handle case where selected union enum is invalid after migration by @MattDHill in #1658
|
||||
- fix: Resolve fighting with NM by @Blu-J in #1660
|
||||
- sdk: don't allow mounts in inject actions by @chrisguida in #1653
|
||||
- feat: Variable args by @Blu-J in #1667
|
||||
- add readme to system-images folder by @elvece in #1665
|
||||
- Mask chars beyond 16 by @MattDHill in #1666
|
||||
- chore: Update to have the new version 0.3.1.1 by @Blu-J in #1668
|
||||
- feat: Make the rename effect by @Blu-J in #1669
|
||||
- fix migration, add logging by @dr-bonez in #1674
|
||||
- run build checks only when relevant FE changes by @elvece in #1664
|
||||
- trust local ca by @dr-bonez in #1670
|
||||
- lower log level for docker deser fallback message by @dr-bonez in #1672
|
||||
- refactor build process by @dr-bonez in #1675
|
||||
- chore: enable strict mode by @waterplea in #1569
|
||||
- draft releases notes for 0311 by @MattDHill in #1677
|
||||
- add standby mode by @dr-bonez in #1671
|
||||
- feat: atomic writing by @Blu-J in #1673
|
||||
- allow server.update to update to current version by @dr-bonez in #1679
|
||||
- allow falsey rpc response by @dr-bonez in #1680
|
||||
- issue notification when individual package restore fails by @dr-bonez in #1685
|
||||
- replace bang with question mark in html by @MattDHill in #1683
|
||||
- only validate mounts for inject if eos >=0.3.1.1 by @dr-bonez in #1686
|
||||
- add marketplace_url to backup metadata for service by @dr-bonez in #1688
|
||||
- marketplace published at for service by @MattDHill in #1689
|
||||
- sync data to fs before shutdown by @dr-bonez in #1690
|
||||
- messaging for restart, shutdown, rebuild by @MattDHill in #1691
|
||||
- honor shutdown from diagnostic ui by @dr-bonez in #1692
|
||||
- ask for sudo password immediately during make by @dr-bonez in #1693
|
||||
- sync blockdev after update by @dr-bonez in #1694
|
||||
- set Matt as default assignee by @MattDHill in #1697
|
||||
- NO_KEY for CI images by @dr-bonez in #1700
|
||||
- fix typo by @dr-bonez in #1702
|
||||
|
||||
# v0.3.1
|
||||
## What's Changed
|
||||
- Feat bulk locking by @Blu-J in #1422
|
||||
- Switching SSH keys to start9 user by @k0gen in #1321
|
||||
- chore: Convert from ajv to ts-matches by @Blu-J in #1415
|
||||
- Fix/id params by @elvece in #1414
|
||||
- make nicer update sound by @ProofOfKeags in #1438
|
||||
- adds product key to error message in setup flow when there is mismatch by @dr-bonez in #1436
|
||||
- Update README.md to include yq by @cryptodread in #1385
|
||||
- yin_yang For the peace of mind yin_yang by @k0gen in #1444
|
||||
- Feature/update sound by @ProofOfKeags in #1439
|
||||
- Feature/script packing by @ProofOfKeags in #1435
|
||||
- rename ActionImplementation to PackageProcedure by @dr-bonez in #1448
|
||||
- Chore/warning cleanse by @ProofOfKeags in #1447
|
||||
- refactor packing to async by @ProofOfKeags in #1453
|
||||
- Add nginx config for proxy redirect by @yzernik in #1421
|
||||
- Proxy local frontend to remote backend by @elvece in #1452
|
||||
- Feat/js action by @Blu-J in #1437
|
||||
- Fix/making js work by @Blu-J in #1456
|
||||
- fix: Dependency vs dependents by @Blu-J in #1462
|
||||
- refactor: isolate network toast and login redirect to separate services by @waterplea in #1412
|
||||
- Fix links in CONTRIBUTING.md, update ToC by @BBlackwo in #1463
|
||||
- Feature/require script consistency by @ProofOfKeags in #1451
|
||||
- Chore/version 0 3 1 0 by @Blu-J in #1475
|
||||
- remove interactive TTY requirement from scripts by @moerketh in #1469
|
||||
- Disable view in marketplace button when side-loaded by @BBlackwo in #1471
|
||||
- Link to tor address on LAN setup page (#1277) by @BBlackwo in #1466
|
||||
- UI version updates and welcome message for 0.3.1 by @elvece in #1479
|
||||
- Update contribution and frontend readme by @BBlackwo in #1467
|
||||
- Clean up config by @MattDHill in #1484
|
||||
- Enable Control Groups for Docker containers by @k0gen in #1468
|
||||
- Fix/patch db unwrap remove by @Blu-J in #1481
|
||||
- handles spaces in working dir in make-image.sh by @moerketh in #1487
|
||||
- UI cosmetic improvements by @MattDHill in #1486
|
||||
- chore: fix the master by @Blu-J in #1495
|
||||
- generate unique ca names based off of server id by @ProofOfKeags in #1500
|
||||
- allow embassy-cli not as root by @dr-bonez in #1501
|
||||
- fix: potential fix for the docker leaking the errors and such by @Blu-J in #1496
|
||||
- Fix/memory leak docker by @Blu-J in #1505
|
||||
- fixes serialization of regex pattern + description by @ProofOfKeags in #1509
|
||||
- allow interactive TTY if available by @dr-bonez in #1508
|
||||
- fix "missing proxy" error in embassy-cli by @dr-bonez in #1516
|
||||
- Feat/js known errors by @Blu-J in #1514
|
||||
- fixes a bug where nginx will crash if eos goes into diagnostic mode a… by @dr-bonez in #1506
|
||||
- fix: restart/ uninstall sometimes didn't work by @Blu-J in #1527
|
||||
- add "error_for_status" to static file downloads by @dr-bonez in #1532
|
||||
- fixes #1169 by @dr-bonez in #1533
|
||||
- disable unnecessary services by @dr-bonez in #1535
|
||||
- chore: Update types to match embassyd by @Blu-J in #1539
|
||||
- fix: found a unsaturaded args fix by @Blu-J in #1540
|
||||
- chore: Update the lite types to include the union and enum by @Blu-J in #1542
|
||||
- Feat: Make the js check for health by @Blu-J in #1543
|
||||
- fix incorrect error message for deserialization in ValueSpecString by @dr-bonez in #1547
|
||||
- fix dependency/dependent id issue by @dr-bonez in #1546
|
||||
- add textarea to ValueSpecString by @dr-bonez in #1534
|
||||
- Feat/js metadata by @Blu-J in #1548
|
||||
- feat: uid/gid/mode added to metadata by @Blu-J in #1551
|
||||
- Strict null checks by @waterplea in #1464
|
||||
- fix backend builds for safe git config by @elvece in #1549
|
||||
- update should send version not version spec by @elvece in #1559
|
||||
- chore: Add tracing for debuging the js procedure slowness by @Blu-J in #1552
|
||||
- Reset password through setup wizard by @MattDHill in #1490
|
||||
- feat: Make sdk by @Blu-J in #1564
|
||||
- fix: Missing a feature flat cfg by @Blu-J in #1563
|
||||
- fixed sentence that didn't make sense by @BitcoinMechanic in #1565
|
||||
- refactor(patch-db): use PatchDB class declaratively by @waterplea in #1562
|
||||
- fix bugs with config and clean up dev options by @MattDHill in #1558
|
||||
- fix: Make it so we only need the password on the backup by @Blu-J in #1566
|
||||
- kill all sessions and remove ripple effect by @MattDHill in #1567
|
||||
- adjust service marketplace button for installation source relevance by @elvece in #1571
|
||||
- fix connection failure display monitoring and other style changes by @MattDHill in #1573
|
||||
- add dns server to embassy-os by @dr-bonez in #1572
|
||||
- Fix/mask generic inputs by @elvece in #1570
|
||||
- Fix/sideload icon type by @elvece in #1577
|
||||
- add avahi conditional compilation flags to dns by @dr-bonez in #1579
|
||||
- selective backups and better drive selection interface by @MattDHill in #1576
|
||||
- Feat/use modern tor by @kn0wmad in #1575
|
||||
- update welcome notes for 031 by @MattDHill in #1580
|
||||
- fix: Properties had a null description by @Blu-J in #1581
|
||||
- fix backup lock ordering by @dr-bonez in #1582
|
||||
- Bugfix/backup lock order by @dr-bonez in #1583
|
||||
- preload redacted and visibility hidden by @MattDHill in #1584
|
||||
- turn chevron red in config if error by @MattDHill in #1586
|
||||
- switch to utc by @dr-bonez in #1587
|
||||
- update patchdb for array patch fix by @elvece in #1588
|
||||
- filter package ids when backing up by @dr-bonez in #1589
|
||||
- add select/deselect all to backups and enum lists by @elvece in #1590
|
||||
- fix: Stop the buffer from dropped pre-maturly by @Blu-J in #1591
|
||||
- chore: commit the snapshots by @Blu-J in #1592
|
||||
- nest new entries and message updates better by @MattDHill in #1595
|
||||
- fix html parsing in logs by @elvece in #1598
|
||||
- don't crash service if io-format is set for main by @dr-bonez in #1599
|
||||
- strip html from colors from logs by @elvece in #1604
|
||||
- feat: fetch effect by @Blu-J in #1605
|
||||
- Fix/UI misc by @elvece in #1606
|
||||
- display bottom item in backup list and refactor for cleanliness by @MattDHill in #1609
|
||||
|
||||
# v0.3.0.3
|
||||
## What's Changed
|
||||
- refactor: decompose app component by @waterplea in #1359
|
||||
- Update Makefile by @kn0wmad in #1400
|
||||
- ⬐ smarter wget by @k0gen in #1401
|
||||
- prevent the kernel from OOMKilling embassyd by @dr-bonez in #1402
|
||||
- attempt to heal when health check passes by @dr-bonez in #1420
|
||||
- Feat new locking by @Blu-J in #1384
|
||||
- version bump by @dr-bonez in #1423
|
||||
- Update server-show.page.ts by @chrisguida in #1424
|
||||
- Bump async from 2.6.3 to 2.6.4 in /frontend by @dependabot in #1426
|
||||
- Update index.html by @mirkoRainer in #1419
|
||||
|
||||
## New Contributors
|
||||
- @dependabot made their first contribution in #1426
|
||||
- @mirkoRainer made their first contribution in #1419
|
||||
|
||||
# v0.3.0.2
|
||||
- Minor compatibility fixes
|
||||
- #1392
|
||||
- #1390
|
||||
- #1388
|
||||
|
||||
# v0.3.0.1
|
||||
Minor bugfixes and performance improvements
|
||||
|
||||
# v0.3.0
|
||||
- Websockets
|
||||
- Real-time sync
|
||||
- Patch DB
|
||||
- Closely mirror FE and BE state. Most operating systems are connected to their GUI. Here it is served over the web. Patch DB and websockets serve to close the perceptual gap of this inherent challenge.
|
||||
- Switch kernel from Raspbian to Ubuntu
|
||||
- 64 bit
|
||||
- Possibility for alternative hardware
|
||||
- Merging of lifeline, agent, and appmgr into embassyd
|
||||
- Elimination of Haskell in favor of pure Rust
|
||||
- Unified API for interacting with the OS
|
||||
- Easier to build from source
|
||||
- OS (quarantined from OS and service data)
|
||||
- Kernel/boot
|
||||
- Persistent metadata (disk guid, product key)
|
||||
- Rootfs (the os)
|
||||
- Reserved (for updates) - swaps with rootfs
|
||||
- Revamped OS updates
|
||||
- Progress indicators
|
||||
- Non-blocking
|
||||
- Simple swap on reboot
|
||||
- Revamped setup flow
|
||||
- Elimination of Setup App (Apple/Google dependencies gone)
|
||||
- Setup Wizard on http://embassy.local
|
||||
- Revamped service config
|
||||
- Dynamic, validated forms
|
||||
- Diagnostic UI
|
||||
- Missing disk, wrong disk, corrupt disk
|
||||
- Turing complete API for actions, backup/restore, config, properties, notifications, health checks, and dependency requirements
|
||||
- Optional, arbitrary inputs for actions
|
||||
- Install, update, recover progress for apps
|
||||
- Multiple interfaces
|
||||
- E.g. rpc, p2p, ui
|
||||
- Health checks
|
||||
- Developer defined
|
||||
- Internal, dependencies, and/or external
|
||||
- Full Embassy backup (diff-based)
|
||||
- External drive support/requirement
|
||||
- Single at first
|
||||
- Groundwork for extension and mirror drives
|
||||
- Disk encryption
|
||||
- Random key encrypted with static value
|
||||
- Groundwork for swapping static value with chosen password
|
||||
- Session Management
|
||||
- List all active sessions
|
||||
- Option to kill
|
||||
- More robust and extensive logs
|
||||
- Donations
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- omit in toc -->
|
||||
|
||||
# Contributing to Embassy OS
|
||||
# Contributing to StartOS
|
||||
|
||||
First off, thanks for taking the time to contribute! ❤️
|
||||
|
||||
@@ -19,7 +19,7 @@ forward to your contributions. 🎉
|
||||
> - Tweet about it
|
||||
> - Refer this project in your project's readme
|
||||
> - Mention the project at local meetups and tell your friends/colleagues
|
||||
> - Buy an [Embassy](https://start9labs.com)
|
||||
> - Buy a [Start9 server](https://start9.com)
|
||||
|
||||
<!-- omit in toc -->
|
||||
|
||||
@@ -49,7 +49,7 @@ forward to your contributions. 🎉
|
||||
> [Documentation](https://docs.start9labs.com).
|
||||
|
||||
Before you ask a question, it is best to search for existing
|
||||
[Issues](https://github.com/Start9Labs/embassy-os/issues) that might help you.
|
||||
[Issues](https://github.com/Start9Labs/start-os/issues) that might help you.
|
||||
In case you have found a suitable issue and still need clarification, you can
|
||||
write your question in this issue. It is also advisable to search the internet
|
||||
for answers first.
|
||||
@@ -57,7 +57,7 @@ for answers first.
|
||||
If you then still feel the need to ask a question and need clarification, we
|
||||
recommend the following:
|
||||
|
||||
- Open an [Issue](https://github.com/Start9Labs/embassy-os/issues/new).
|
||||
- Open an [Issue](https://github.com/Start9Labs/start-os/issues/new).
|
||||
- Provide as much context as you can about what you're running into.
|
||||
- Provide project and platform versions, depending on what seems relevant.
|
||||
|
||||
@@ -105,7 +105,7 @@ steps in advance to help us fix any potential bug as fast as possible.
|
||||
- To see if other users have experienced (and potentially already solved) the
|
||||
same issue you are having, check if there is not already a bug report existing
|
||||
for your bug or error in the
|
||||
[bug tracker](https://github.com/Start9Labs/embassy-os/issues?q=label%3Abug).
|
||||
[bug tracker](https://github.com/Start9Labs/start-os/issues?q=label%3Abug).
|
||||
- Also make sure to search the internet (including Stack Overflow) to see if
|
||||
users outside of the GitHub community have discussed the issue.
|
||||
- Collect information about the bug:
|
||||
@@ -131,7 +131,7 @@ steps in advance to help us fix any potential bug as fast as possible.
|
||||
We use GitHub issues to track bugs and errors. If you run into an issue with the
|
||||
project:
|
||||
|
||||
- Open an [Issue](https://github.com/Start9Labs/embassy-os/issues/new/choose)
|
||||
- Open an [Issue](https://github.com/Start9Labs/start-os/issues/new/choose)
|
||||
selecting the appropriate type.
|
||||
- Explain the behavior you would expect and the actual behavior.
|
||||
- Please provide as much context as possible and describe the _reproduction
|
||||
@@ -155,8 +155,7 @@ Once it's filed:
|
||||
|
||||
### Suggesting Enhancements
|
||||
|
||||
This section guides you through submitting an enhancement suggestion for Embassy
|
||||
OS, **including completely new features and minor improvements to existing
|
||||
This section guides you through submitting an enhancement suggestion for StartOS, **including completely new features and minor improvements to existing
|
||||
functionality**. Following these guidelines will help maintainers and the
|
||||
community to understand your suggestion and find related suggestions.
|
||||
|
||||
@@ -168,7 +167,7 @@ community to understand your suggestion and find related suggestions.
|
||||
- Read the [documentation](https://start9.com/latest/user-manual) carefully and
|
||||
find out if the functionality is already covered, maybe by an individual
|
||||
configuration.
|
||||
- Perform a [search](https://github.com/Start9Labs/embassy-os/issues) to see if
|
||||
- Perform a [search](https://github.com/Start9Labs/start-os/issues) to see if
|
||||
the enhancement has already been suggested. If it has, add a comment to the
|
||||
existing issue instead of opening a new one.
|
||||
- Find out whether your idea fits with the scope and aims of the project. It's
|
||||
@@ -182,7 +181,7 @@ community to understand your suggestion and find related suggestions.
|
||||
#### How Do I Submit a Good Enhancement Suggestion?
|
||||
|
||||
Enhancement suggestions are tracked as
|
||||
[GitHub issues](https://github.com/Start9Labs/embassy-os/issues).
|
||||
[GitHub issues](https://github.com/Start9Labs/start-os/issues).
|
||||
|
||||
- Use a **clear and descriptive title** for the issue to identify the
|
||||
suggestion.
|
||||
@@ -197,7 +196,7 @@ Enhancement suggestions are tracked as
|
||||
macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast)
|
||||
or [this tool](https://github.com/GNOME/byzanz) on Linux.
|
||||
<!-- this should only be included if the project has a GUI -->
|
||||
- **Explain why this enhancement would be useful** to most Embassy OS users. You
|
||||
- **Explain why this enhancement would be useful** to most StartOS users. You
|
||||
may also want to point out the other projects that solved it better and which
|
||||
could serve as inspiration.
|
||||
|
||||
@@ -205,24 +204,24 @@ Enhancement suggestions are tracked as
|
||||
|
||||
### Project Structure
|
||||
|
||||
embassyOS is composed of the following components. Please visit the README for
|
||||
StartOS is composed of the following components. Please visit the README for
|
||||
each component to understand the dependency requirements and installation
|
||||
instructions.
|
||||
|
||||
- [`backend`](backend/README.md) (Rust) is a command line utility, daemon, and
|
||||
software development kit that sets up and manages services and their
|
||||
environments, provides the interface for the ui, manages system state, and
|
||||
provides utilities for packaging services for embassyOS.
|
||||
provides utilities for packaging services for StartOS.
|
||||
- [`build`](build/README.md) contains scripts and necessary for deploying
|
||||
embassyOS to a debian/raspbian system.
|
||||
StartOS to a debian/raspbian system.
|
||||
- [`frontend`](frontend/README.md) (Typescript Ionic Angular) is the code that
|
||||
is deployed to the browser to provide the user interface for embassyOS.
|
||||
- `projects/ui` - Code for the user interface that is displayed when embassyOS
|
||||
is deployed to the browser to provide the user interface for StartOS.
|
||||
- `projects/ui` - Code for the user interface that is displayed when StartOS
|
||||
is running normally.
|
||||
- `projects/setup-wizard`(frontend/README.md) - Code for the user interface
|
||||
that is displayed during the setup and recovery process for embassyOS.
|
||||
that is displayed during the setup and recovery process for StartOS.
|
||||
- `projects/diagnostic-ui` - Code for the user interface that is displayed
|
||||
when something has gone wrong with starting up embassyOS, which provides
|
||||
when something has gone wrong with starting up StartOS, which provides
|
||||
helpful debugging tools.
|
||||
- `libs` (Rust) is a set of standalone crates that were separated out of
|
||||
`backend` for the purpose of portability
|
||||
@@ -232,18 +231,18 @@ instructions.
|
||||
[client](https://github.com/Start9Labs/patch-db/tree/master/client) with its
|
||||
own dependency and installation requirements.
|
||||
- `system-images` - (Docker, Rust) A suite of utility Docker images that are
|
||||
preloaded with embassyOS to assist with functions relating to services (eg.
|
||||
preloaded with StartOS to assist with functions relating to services (eg.
|
||||
configuration, backups, health checks).
|
||||
|
||||
### Your First Code Contribution
|
||||
|
||||
#### Setting Up Your Development Environment
|
||||
|
||||
First, clone the embassyOS repository and from the project root, pull in the
|
||||
First, clone the StartOS repository and from the project root, pull in the
|
||||
submodules for dependent libraries.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Start9Labs/embassy-os.git
|
||||
git clone https://github.com/Start9Labs/start-os.git
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
@@ -254,7 +253,7 @@ to, follow the installation requirements listed in that component's README
|
||||
#### Building The Raspberry Pi Image
|
||||
|
||||
This step is for setting up an environment in which to test your code changes if
|
||||
you do not yet have a embassyOS.
|
||||
you do not yet have a StartOS.
|
||||
|
||||
- Requirements
|
||||
- `ext4fs` (available if running on the Linux kernel)
|
||||
@@ -262,7 +261,7 @@ you do not yet have a embassyOS.
|
||||
- GNU Make
|
||||
- Building
|
||||
- see setup instructions [here](build/README.md)
|
||||
- run `make embassyos-raspi.img ARCH=aarch64` from the project root
|
||||
- run `make startos-raspi.img ARCH=aarch64` from the project root
|
||||
|
||||
### Improving The Documentation
|
||||
|
||||
@@ -286,7 +285,7 @@ seamless and intuitive experience.
|
||||
|
||||
### Formatting
|
||||
|
||||
Each component of embassyOS contains its own style guide. Code must be formatted
|
||||
Each component of StartOS contains its own style guide. Code must be formatted
|
||||
with the formatter designated for each component. These are outlined within each
|
||||
component folder's README.
|
||||
|
||||
@@ -306,7 +305,7 @@ component. i.e. `backend: update to tokio v0.3`.
|
||||
|
||||
The body of a pull request should contain sufficient description of what the
|
||||
changes do, as well as a justification. You should include references to any
|
||||
relevant [issues](https://github.com/Start9Labs/embassy-os/issues).
|
||||
relevant [issues](https://github.com/Start9Labs/start-os/issues).
|
||||
|
||||
### Rebasing Changes
|
||||
|
||||
|
||||
5607
Cargo.lock
generated
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Start9 Labs, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
42
LICENSE.md
@@ -1,42 +0,0 @@
|
||||
# START9 NON-COMMERCIAL LICENSE v1
|
||||
Version 1, 22 September 2022
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
### 1.Definitions
|
||||
|
||||
"License" means version 1 of the Start9 Non-Commercial License.
|
||||
|
||||
"Licensor" means the Start9 Labs, Inc, or its successor(s) in interest, or a future assignee of the copyright.
|
||||
|
||||
"You" (or "Your") means an individual or organization exercising permissions granted by this License.
|
||||
|
||||
"Source Code" for a work means the preferred form of the work for making modifications to it.
|
||||
|
||||
"Object Code" means any non-source form of a work, including the machine-language output by a compiler or assembler.
|
||||
|
||||
"Work" means any work of authorship, whether in Source or Object form, made available under this License.
|
||||
|
||||
"Derivative Work" means any work, whether in Source or Object form, that is based on (or derived from) the Work.
|
||||
|
||||
"Distribute" means to convey or to publish and generally has the same meaning here as under U.S. Copyright law.
|
||||
|
||||
"Sell" means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including, without limitation, fees for hosting, consulting, or support services), a product or service whose value derives, entirely or substantially, from the functionality of the Work or Derivative Work.
|
||||
|
||||
### 2. Grant of Rights
|
||||
|
||||
Subject to the terms of this license, the Licensor grants you, the licensee, a non-exclusive, worldwide, royalty-free copyright license to access, audit, copy, modify, compile, run, test, distribute, or otherwise use the Software.
|
||||
|
||||
### 3. Limitations
|
||||
|
||||
1. The grant of rights under the License does NOT include, and the License does NOT grant You the right to Sell the Work or Derivative Work.
|
||||
2. If you Distribute the Work or Derivative Work, you expressly undertake not to remove or modify, in any manner, the copyright notices attached to the Work or displayed in any output of the Work when run, and to reproduce these notices, in an identical manner, in any distributed copies of the Work or Derivative Work together with a copy of this License.
|
||||
3. If you Distribute a Derivative Work, it must carry prominent notices stating that it has been modified from the Work, providing a relevant date.
|
||||
|
||||
### 4. Contributions
|
||||
|
||||
You hereby grant to Licensor a perpetual, irrevocable, worldwide, non-exclusive, royalty-free license to use and exploit any Derivative Work of which you are the author.
|
||||
|
||||
### 5. Disclaimer
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. LICENSOR HAS NO OBLIGATION TO SUPPORT RECIPIENTS OF THE SOFTWARE.
|
||||
242
Makefile
@@ -1,44 +1,63 @@
|
||||
RASPI_TARGETS := embassyos-raspi.img embassyos-raspi.tar.gz gzip lite-upgrade.img
|
||||
ARCH := $(shell if echo $(RASPI_TARGETS) | grep -qw "$(MAKECMDGOALS)"; then echo aarch64; else uname -m; fi)
|
||||
OS_ARCH := $(shell if echo $(RASPI_TARGETS) | grep -qw "$(MAKECMDGOALS)"; then echo raspberrypi; else uname -m; fi)
|
||||
ENVIRONMENT_FILE = $(shell ./check-environment.sh)
|
||||
GIT_HASH_FILE = $(shell ./check-git-hash.sh)
|
||||
VERSION_FILE = $(shell ./check-version.sh)
|
||||
EMBASSY_BINS := backend/target/$(ARCH)-unknown-linux-gnu/release/embassyd backend/target/$(ARCH)-unknown-linux-gnu/release/embassy-init backend/target/$(ARCH)-unknown-linux-gnu/release/embassy-cli backend/target/$(ARCH)-unknown-linux-gnu/release/embassy-sdk backend/target/$(ARCH)-unknown-linux-gnu/release/avahi-alias libs/target/aarch64-unknown-linux-musl/release/embassy_container_init libs/target/x86_64-unknown-linux-musl/release/embassy_container_init
|
||||
EMBASSY_UIS := frontend/dist/ui frontend/dist/setup-wizard frontend/dist/diagnostic-ui frontend/dist/install-wizard
|
||||
BUILD_SRC := $(shell find build)
|
||||
EMBASSY_SRC := backend/embassyd.service backend/embassy-init.service $(EMBASSY_UIS) $(BUILD_SRC)
|
||||
COMPAT_SRC := $(shell find system-images/compat/ -not -path 'system-images/compat/target/*' -and -not -name *.tar -and -not -name target)
|
||||
UTILS_SRC := $(shell find system-images/utils/ -not -name *.tar)
|
||||
BINFMT_SRC := $(shell find system-images/binfmt/ -not -name *.tar)
|
||||
BACKEND_SRC := $(shell find backend/src) $(shell find backend/migrations) $(shell find patch-db/*/src) $(shell find libs/*/src) libs/*/Cargo.toml backend/Cargo.toml backend/Cargo.lock
|
||||
FRONTEND_SHARED_SRC := $(shell find frontend/projects/shared) $(shell ls -p frontend/ | grep -v / | sed 's/^/frontend\//g') frontend/package.json frontend/node_modules frontend/config.json patch-db/client/dist frontend/patchdb-ui-seed.json
|
||||
FRONTEND_UI_SRC := $(shell find frontend/projects/ui)
|
||||
FRONTEND_SETUP_WIZARD_SRC := $(shell find frontend/projects/setup-wizard)
|
||||
FRONTEND_DIAGNOSTIC_UI_SRC := $(shell find frontend/projects/diagnostic-ui)
|
||||
FRONTEND_INSTALL_WIZARD_SRC := $(shell find frontend/projects/install-wizard)
|
||||
PATCH_DB_CLIENT_SRC := $(shell find patch-db/client -not -path patch-db/client/dist)
|
||||
PLATFORM_FILE := $(shell ./check-platform.sh)
|
||||
ENVIRONMENT_FILE := $(shell ./check-environment.sh)
|
||||
GIT_HASH_FILE := $(shell ./check-git-hash.sh)
|
||||
VERSION_FILE := $(shell ./check-version.sh)
|
||||
BASENAME := $(shell ./basename.sh)
|
||||
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)
|
||||
IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi)
|
||||
EMBASSY_BINS := backend/target/$(ARCH)-unknown-linux-gnu/release/startbox libs/target/aarch64-unknown-linux-musl/release/embassy_container_init libs/target/x86_64-unknown-linux-musl/release/embassy_container_init
|
||||
EMBASSY_UIS := frontend/dist/raw/ui frontend/dist/raw/setup-wizard frontend/dist/raw/diagnostic-ui frontend/dist/raw/install-wizard
|
||||
BUILD_SRC := $(shell git ls-files build) build/lib/depends build/lib/conflicts
|
||||
DEBIAN_SRC := $(shell git ls-files debian/)
|
||||
IMAGE_RECIPE_SRC := $(shell git ls-files image-recipe/)
|
||||
EMBASSY_SRC := backend/startd.service $(BUILD_SRC)
|
||||
COMPAT_SRC := $(shell git ls-files system-images/compat/)
|
||||
UTILS_SRC := $(shell git ls-files system-images/utils/)
|
||||
BINFMT_SRC := $(shell git ls-files system-images/binfmt/)
|
||||
BACKEND_SRC := $(shell git ls-files backend) $(shell git ls-files --recurse-submodules patch-db) $(shell git ls-files libs) frontend/dist/static
|
||||
FRONTEND_SHARED_SRC := $(shell git ls-files frontend/projects/shared) $(shell ls -p frontend/ | grep -v / | sed 's/^/frontend\//g') frontend/node_modules frontend/config.json patch-db/client/dist frontend/patchdb-ui-seed.json
|
||||
FRONTEND_UI_SRC := $(shell git ls-files frontend/projects/ui)
|
||||
FRONTEND_SETUP_WIZARD_SRC := $(shell git ls-files frontend/projects/setup-wizard)
|
||||
FRONTEND_DIAGNOSTIC_UI_SRC := $(shell git ls-files frontend/projects/diagnostic-ui)
|
||||
FRONTEND_INSTALL_WIZARD_SRC := $(shell git ls-files frontend/projects/install-wizard)
|
||||
PATCH_DB_CLIENT_SRC := $(shell git ls-files --recurse-submodules patch-db/client)
|
||||
GZIP_BIN := $(shell which pigz || which gzip)
|
||||
$(shell sudo true)
|
||||
TAR_BIN := $(shell which gtar || which tar)
|
||||
COMPILED_TARGETS := $(EMBASSY_BINS) system-images/compat/docker-images/$(ARCH).tar system-images/utils/docker-images/$(ARCH).tar system-images/binfmt/docker-images/$(ARCH).tar
|
||||
ALL_TARGETS := $(EMBASSY_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) $(COMPILED_TARGETS) $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep; fi) $(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then echo cargo-deps/$(ARCH)-unknown-linux-gnu/release/tokio-console; fi') $(PLATFORM_FILE)
|
||||
|
||||
ifeq ($(REMOTE),)
|
||||
mkdir = mkdir -p $1
|
||||
rm = rm -rf $1
|
||||
cp = cp -r $1 $2
|
||||
ln = ln -sf $1 $2
|
||||
else
|
||||
ifeq ($(SSHPASS),)
|
||||
ssh = ssh $(REMOTE) $1
|
||||
else
|
||||
ssh = sshpass -p $(SSHPASS) ssh $(REMOTE) $1
|
||||
endif
|
||||
mkdir = $(call ssh,'sudo mkdir -p $1')
|
||||
rm = $(call ssh,'sudo rm -rf $1')
|
||||
ln = $(call ssh,'sudo ln -sf $1 $2')
|
||||
define cp
|
||||
$(TAR_BIN) --transform "s|^$1|x|" -czv -f- $1 | $(call ssh,"sudo tar --transform 's|^x|$2|' -xzv -f- -C /")
|
||||
endef
|
||||
endif
|
||||
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
.PHONY: all gzip install clean format sdk snapshots frontends ui backend
|
||||
.PHONY: all metadata install clean format sdk snapshots frontends ui backend reflash deb $(IMAGE_TYPE) squashfs sudo wormhole docker-buildx
|
||||
|
||||
all: $(EMBASSY_SRC) $(EMBASSY_BINS) system-images/compat/docker-images/aarch64.tar system-images/utils/docker-images/$(ARCH).tar system-images/binfmt/docker-images/$(ARCH).tar $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE)
|
||||
all: $(ALL_TARGETS)
|
||||
|
||||
gzip: embassyos-raspi.tar.gz
|
||||
metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
||||
|
||||
embassyos-raspi.tar.gz: embassyos-raspi.img
|
||||
tar --format=posix -cS -f- embassyos-raspi.img | $(GZIP_BIN) > embassyos-raspi.tar.gz
|
||||
sudo:
|
||||
sudo true
|
||||
|
||||
clean:
|
||||
rm -f 2022-01-28-raspios-bullseye-arm64-lite.zip
|
||||
rm -f raspios.img
|
||||
rm -f embassyos-raspi.img
|
||||
rm -f embassyos-raspi.tar.gz
|
||||
rm -f ubuntu.img
|
||||
rm -f product_key.txt
|
||||
rm -f system-images/**/*.tar
|
||||
rm -rf system-images/compat/target
|
||||
rm -rf backend/target
|
||||
@@ -51,9 +70,13 @@ clean:
|
||||
rm -rf patch-db/client/dist
|
||||
rm -rf patch-db/target
|
||||
rm -rf cargo-deps
|
||||
rm ENVIRONMENT.txt
|
||||
rm GIT_HASH.txt
|
||||
rm VERSION.txt
|
||||
rm -rf dpkg-workdir
|
||||
rm -rf image-recipe/deb
|
||||
rm -rf results
|
||||
rm -f ENVIRONMENT.txt
|
||||
rm -f PLATFORM.txt
|
||||
rm -f GIT_HASH.txt
|
||||
rm -f VERSION.txt
|
||||
|
||||
format:
|
||||
cd backend && cargo +nightly fmt
|
||||
@@ -62,63 +85,93 @@ format:
|
||||
sdk:
|
||||
cd backend/ && ./install-sdk.sh
|
||||
|
||||
embassyos-raspi.img: all raspios.img cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast
|
||||
! test -f embassyos-raspi.img || rm embassyos-raspi.img
|
||||
./build/raspberry-pi/make-image.sh
|
||||
deb: results/$(BASENAME).deb
|
||||
|
||||
lite-upgrade.img: raspios.img cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast $(BUILD_SRC) eos.raspberrypi.squashfs
|
||||
! test -f lite-upgrade.img || rm lite-upgrade.img
|
||||
./build/raspberry-pi/make-upgrade-image.sh
|
||||
debian/control: build/lib/depends build/lib/conflicts
|
||||
./debuild/control.sh
|
||||
|
||||
eos_raspberrypi.img: raspios.img $(BUILD_SRC) eos.raspberrypi.squashfs $(VERSION_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
||||
! test -f eos_raspberrypi.img || rm eos_raspberrypi.img
|
||||
./build/raspberry-pi/make-initialized-image.sh
|
||||
results/$(BASENAME).deb: dpkg-build.sh $(DEBIAN_SRC) $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
||||
PLATFORM=$(PLATFORM) ./dpkg-build.sh
|
||||
|
||||
$(IMAGE_TYPE): results/$(BASENAME).$(IMAGE_TYPE)
|
||||
|
||||
squashfs: results/$(BASENAME).squashfs
|
||||
|
||||
results/$(BASENAME).$(IMAGE_TYPE) results/$(BASENAME).squashfs: $(IMAGE_RECIPE_SRC) results/$(BASENAME).deb
|
||||
./image-recipe/run-local-build.sh "results/$(BASENAME).deb"
|
||||
|
||||
# For creating os images. DO NOT USE
|
||||
install: all
|
||||
mkdir -p $(DESTDIR)/usr/bin
|
||||
cp backend/target/$(ARCH)-unknown-linux-gnu/release/embassy-init $(DESTDIR)/usr/bin/
|
||||
cp backend/target/$(ARCH)-unknown-linux-gnu/release/embassyd $(DESTDIR)/usr/bin/
|
||||
cp backend/target/$(ARCH)-unknown-linux-gnu/release/embassy-cli $(DESTDIR)/usr/bin/
|
||||
cp backend/target/$(ARCH)-unknown-linux-gnu/release/avahi-alias $(DESTDIR)/usr/bin/
|
||||
install: $(ALL_TARGETS)
|
||||
$(call mkdir,$(DESTDIR)/usr/bin)
|
||||
$(call cp,backend/target/$(ARCH)-unknown-linux-gnu/release/startbox,$(DESTDIR)/usr/bin/startbox)
|
||||
$(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-sdk)
|
||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-deno)
|
||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/avahi-alias)
|
||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/embassy-cli)
|
||||
if [ "$(PLATFORM)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi
|
||||
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then $(call cp,cargo-deps/$(ARCH)-unknown-linux-gnu/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); fi
|
||||
|
||||
mkdir -p $(DESTDIR)/usr/lib
|
||||
rm -rf $(DESTDIR)/usr/lib/embassy
|
||||
cp -r build/lib $(DESTDIR)/usr/lib/embassy
|
||||
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
||||
$(call cp,backend/startd.service,$(DESTDIR)/lib/systemd/system/startd.service)
|
||||
|
||||
cp ENVIRONMENT.txt $(DESTDIR)/usr/lib/embassy/
|
||||
cp GIT_HASH.txt $(DESTDIR)/usr/lib/embassy/
|
||||
cp VERSION.txt $(DESTDIR)/usr/lib/embassy/
|
||||
$(call mkdir,$(DESTDIR)/usr/lib)
|
||||
$(call rm,$(DESTDIR)/usr/lib/startos)
|
||||
$(call cp,build/lib,$(DESTDIR)/usr/lib/startos)
|
||||
|
||||
mkdir -p $(DESTDIR)/usr/lib/embassy/container
|
||||
cp libs/target/aarch64-unknown-linux-musl/release/embassy_container_init $(DESTDIR)/usr/lib/embassy/container/embassy_container_init.arm64
|
||||
cp libs/target/x86_64-unknown-linux-musl/release/embassy_container_init $(DESTDIR)/usr/lib/embassy/container/embassy_container_init.amd64
|
||||
$(call cp,PLATFORM.txt,$(DESTDIR)/usr/lib/startos/PLATFORM.txt)
|
||||
$(call cp,ENVIRONMENT.txt,$(DESTDIR)/usr/lib/startos/ENVIRONMENT.txt)
|
||||
$(call cp,GIT_HASH.txt,$(DESTDIR)/usr/lib/startos/GIT_HASH.txt)
|
||||
$(call cp,VERSION.txt,$(DESTDIR)/usr/lib/startos/VERSION.txt)
|
||||
|
||||
mkdir -p $(DESTDIR)/usr/lib/embassy/system-images
|
||||
cp system-images/compat/docker-images/aarch64.tar $(DESTDIR)/usr/lib/embassy/system-images/compat.tar
|
||||
cp system-images/utils/docker-images/$(ARCH).tar $(DESTDIR)/usr/lib/embassy/system-images/utils.tar
|
||||
cp system-images/binfmt/docker-images/$(ARCH).tar $(DESTDIR)/usr/lib/embassy/system-images/binfmt.tar
|
||||
$(call mkdir,$(DESTDIR)/usr/lib/startos/container)
|
||||
$(call cp,libs/target/aarch64-unknown-linux-musl/release/embassy_container_init,$(DESTDIR)/usr/lib/startos/container/embassy_container_init.arm64)
|
||||
$(call cp,libs/target/x86_64-unknown-linux-musl/release/embassy_container_init,$(DESTDIR)/usr/lib/startos/container/embassy_container_init.amd64)
|
||||
|
||||
mkdir -p $(DESTDIR)/var/www/html
|
||||
cp -r frontend/dist/diagnostic-ui $(DESTDIR)/var/www/html/diagnostic
|
||||
cp -r frontend/dist/setup-wizard $(DESTDIR)/var/www/html/setup
|
||||
cp -r frontend/dist/install-wizard $(DESTDIR)/var/www/html/install
|
||||
cp -r frontend/dist/ui $(DESTDIR)/var/www/html/main
|
||||
cp index.html $(DESTDIR)/var/www/html/
|
||||
$(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,system-images/binfmt/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/binfmt.tar)
|
||||
|
||||
system-images/compat/docker-images/aarch64.tar: $(COMPAT_SRC)
|
||||
cd system-images/compat && make
|
||||
update-overlay: $(ALL_TARGETS)
|
||||
@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"
|
||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||
@if [ "`ssh $(REMOTE) 'cat /usr/lib/startos/VERSION.txt'`" != "`cat ./VERSION.txt`" ]; then >&2 echo "StartOS requires migrations: update-overlay is unavailable." && false; fi
|
||||
$(call ssh,"sudo systemctl stop startd")
|
||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) PLATFORM=$(PLATFORM)
|
||||
$(call ssh,"sudo systemctl start startd")
|
||||
|
||||
system-images/utils/docker-images/aarch64.tar system-images/utils/docker-images/x86_64.tar: $(UTILS_SRC)
|
||||
cd system-images/utils && make
|
||||
wormhole: backend/target/$(ARCH)-unknown-linux-gnu/release/startbox
|
||||
@wormhole send backend/target/$(ARCH)-unknown-linux-gnu/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 }'
|
||||
|
||||
system-images/binfmt/docker-images/aarch64.tar system-images/binfmt/docker-images/x86_64.tar: $(BINFMT_SRC)
|
||||
cd system-images/binfmt && make
|
||||
update: $(ALL_TARGETS)
|
||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||
$(call ssh,"sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/")
|
||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/embassy/next PLATFORM=$(PLATFORM)
|
||||
$(call ssh,'sudo NO_SYNC=1 /media/embassy/next/usr/lib/startos/scripts/chroot-and-upgrade "apt-get install -y $(shell cat ./build/lib/depends)"')
|
||||
|
||||
raspios.img:
|
||||
wget --continue https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2022-01-28/2022-01-28-raspios-bullseye-arm64-lite.zip
|
||||
unzip 2022-01-28-raspios-bullseye-arm64-lite.zip
|
||||
mv 2022-01-28-raspios-bullseye-arm64-lite.img raspios.img
|
||||
emulate-reflash: $(ALL_TARGETS)
|
||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||
$(call ssh,"sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/")
|
||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/embassy/next PLATFORM=$(PLATFORM)
|
||||
$(call ssh,"sudo touch /media/embassy/config/upgrade && sudo rm -f /media/embassy/config/disk.guid && sudo sync && sudo reboot")
|
||||
|
||||
upload-ota: results/$(BASENAME).squashfs
|
||||
TARGET=$(TARGET) KEY=$(KEY) ./upload-ota.sh
|
||||
|
||||
build/lib/depends build/lib/conflicts: build/dpkg-deps/*
|
||||
build/dpkg-deps/generate.sh
|
||||
|
||||
system-images/compat/docker-images/$(ARCH).tar: $(COMPAT_SRC) backend/Cargo.lock | docker-buildx
|
||||
cd system-images/compat && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
||||
|
||||
system-images/utils/docker-images/$(ARCH).tar: $(UTILS_SRC) | docker-buildx
|
||||
cd system-images/utils && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
||||
|
||||
system-images/binfmt/docker-images/$(ARCH).tar: $(BINFMT_SRC) | docker-buildx
|
||||
cd system-images/binfmt && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
||||
|
||||
snapshots: libs/snapshot_creator/Cargo.toml
|
||||
cd libs/ && ./build-v8-snapshot.sh
|
||||
@@ -131,27 +184,26 @@ $(EMBASSY_BINS): $(BACKEND_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) frontend/pa
|
||||
frontend/node_modules: frontend/package.json
|
||||
npm --prefix frontend ci
|
||||
|
||||
frontend/dist/ui: $(FRONTEND_UI_SRC) $(FRONTEND_SHARED_SRC) $(ENVIRONMENT_FILE)
|
||||
frontend/dist/raw/ui: $(FRONTEND_UI_SRC) $(FRONTEND_SHARED_SRC)
|
||||
npm --prefix frontend run build:ui
|
||||
|
||||
frontend/dist/setup-wizard: $(FRONTEND_SETUP_WIZARD_SRC) $(FRONTEND_SHARED_SRC) $(ENVIRONMENT_FILE)
|
||||
frontend/dist/raw/setup-wizard: $(FRONTEND_SETUP_WIZARD_SRC) $(FRONTEND_SHARED_SRC)
|
||||
npm --prefix frontend run build:setup
|
||||
|
||||
frontend/dist/diagnostic-ui: $(FRONTEND_DIAGNOSTIC_UI_SRC) $(FRONTEND_SHARED_SRC) $(ENVIRONMENT_FILE)
|
||||
frontend/dist/raw/diagnostic-ui: $(FRONTEND_DIAGNOSTIC_UI_SRC) $(FRONTEND_SHARED_SRC)
|
||||
npm --prefix frontend run build:dui
|
||||
|
||||
frontend/dist/install-wizard: $(FRONTEND_INSTALL_WIZARD_SRC) $(FRONTEND_SHARED_SRC) $(ENVIRONMENT_FILE)
|
||||
frontend/dist/raw/install-wizard: $(FRONTEND_INSTALL_WIZARD_SRC) $(FRONTEND_SHARED_SRC)
|
||||
npm --prefix frontend run build:install-wiz
|
||||
|
||||
frontend/dist/static: $(EMBASSY_UIS) $(ENVIRONMENT_FILE)
|
||||
./compress-uis.sh
|
||||
|
||||
frontend/config.json: $(GIT_HASH_FILE) frontend/config-sample.json
|
||||
jq '.useMocks = false' frontend/config-sample.json > frontend/config.json
|
||||
jq '.packageArch = "$(ARCH)"' frontend/config.json > frontend/config.json.tmp
|
||||
jq '.osArch = "$(OS_ARCH)"' frontend/config.json.tmp > frontend/config.json
|
||||
rm frontend/config.json.tmp
|
||||
npm --prefix frontend run-script build-config
|
||||
jq '.useMocks = false' frontend/config-sample.json | jq '.gitHash = "$(shell cat GIT_HASH.txt)"' > frontend/config.json
|
||||
|
||||
frontend/patchdb-ui-seed.json: frontend/package.json
|
||||
jq '."ack-welcome" = "$(shell yq '.version' frontend/package.json)"' frontend/patchdb-ui-seed.json > ui-seed.tmp
|
||||
jq '."ack-welcome" = $(shell jq '.version' frontend/package.json)' frontend/patchdb-ui-seed.json > ui-seed.tmp
|
||||
mv ui-seed.tmp frontend/patchdb-ui-seed.json
|
||||
|
||||
patch-db/client/node_modules: patch-db/client/package.json
|
||||
@@ -162,17 +214,17 @@ patch-db/client/dist: $(PATCH_DB_CLIENT_SRC) patch-db/client/node_modules
|
||||
npm --prefix frontend run build:deps
|
||||
|
||||
# used by github actions
|
||||
backend-$(ARCH).tar: $(EMBASSY_BINS)
|
||||
compiled-$(ARCH).tar: $(COMPILED_TARGETS) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE)
|
||||
tar -cvf $@ $^
|
||||
|
||||
# this is a convenience step to build all frontends - it is not referenced elsewhere in this file
|
||||
frontends: $(EMBASSY_UIS)
|
||||
|
||||
# this is a convenience step to build the UI
|
||||
ui: frontend/dist/ui
|
||||
ui: frontend/dist/raw/ui
|
||||
|
||||
# used by github actions
|
||||
backend: $(EMBASSY_BINS)
|
||||
cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep:
|
||||
ARCH=aarch64 ./build-cargo-dep.sh pi-beep
|
||||
|
||||
cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast:
|
||||
./build-cargo-dep.sh nc-broadcast
|
||||
cargo-deps/$(ARCH)-unknown-linux-gnu/release/tokio-console:
|
||||
ARCH=$(ARCH) ./build-cargo-dep.sh tokio-console
|
||||
127
README.md
@@ -1,51 +1,84 @@
|
||||
# embassyOS
|
||||
[](https://github.com/Start9Labs/embassy-os/releases)
|
||||
[](https://github.com/Start9Labs/embassy-os/actions/workflows/product.yaml)
|
||||
[](https://matrix.to/#/#community:matrix.start9labs.com)
|
||||
[](https://t.me/start9_labs)
|
||||
[](https://docs.start9.com)
|
||||
[](https://matrix.to/#/#community-dev:matrix.start9labs.com)
|
||||
[](https://start9.com)
|
||||
|
||||
[](http://mastodon.start9labs.com)
|
||||
[](https://twitter.com/start9labs)
|
||||
|
||||
### _Welcome to the era of Sovereign Computing_ ###
|
||||
|
||||
embassyOS is a browser-based, graphical operating system for a personal server. embassyOS facilitates the discovery, installation, network configuration, service configuration, data backup, dependency management, and health monitoring of self-hosted software services. It is the most advanced, secure, reliable, and user friendly personal server OS in the world.
|
||||
|
||||
## Running embassyOS
|
||||
There are multiple ways to get your hands on embassyOS.
|
||||
|
||||
### :moneybag: Buy an Embassy
|
||||
This is the most convenient option. Simply [buy an Embassy](https://start9.com) from Start9 and plug it in. Depending on where you live, shipping costs and import duties will vary.
|
||||
|
||||
### :construction_worker: Build your own Embassy
|
||||
While not as convenient as buying an Embassy, this option is easier than you might imagine, and there are 4 reasons why you might prefer it:
|
||||
1. You already have a Raspberry Pi and would like to re-purpose it.
|
||||
1. You want to save on shipping costs.
|
||||
1. You prefer not to divulge your physical address.
|
||||
1. You just like building things.
|
||||
|
||||
To pursue this option, follow this [guide](https://start9.com/latest/diy).
|
||||
|
||||
### :hammer_and_wrench: Build embassyOS from Source
|
||||
|
||||
embassyOS can be built from source, for personal use, for free.
|
||||
A detailed guide for doing so can be found [here](https://github.com/Start9Labs/embassy-os/blob/master/build/README.md).
|
||||
|
||||
## :heart: Contributing
|
||||
There are multiple ways to contribute: work directly on embassyOS, package a service for the marketplace, or help with documentation and guides. To learn more about contributing, see [here](https://github.com/Start9Labs/embassy-os/blob/master/CONTRIBUTING.md).
|
||||
|
||||
## UI Screenshots
|
||||
<div align="center">
|
||||
<img src="frontend/projects/shared/assets/img/icon.png" alt="StartOS Logo" width="16%" />
|
||||
<h1 style="margin-top: 0;">StartOS</h1>
|
||||
<a href="https://github.com/Start9Labs/start-os/releases">
|
||||
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/start9labs/start-os?logo=github">
|
||||
</a>
|
||||
<a href="https://github.com/Start9Labs/start-os/actions/workflows/startos-iso.yaml">
|
||||
<img src="https://github.com/Start9Labs/start-os/actions/workflows/startos-iso.yaml/badge.svg">
|
||||
</a>
|
||||
<a href="https://heyapollo.com/product/startos">
|
||||
<img alt="Static Badge" src="https://img.shields.io/badge/apollo-review%20%E2%AD%90%E2%AD%90%E2%AD%90%E2%AD%90%E2%AD%90%20-slateblue">
|
||||
</a>
|
||||
<a href="https://twitter.com/start9labs">
|
||||
<img alt="X (formerly Twitter) Follow" src="https://img.shields.io/twitter/follow/start9labs">
|
||||
</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">
|
||||
<img alt="Static Badge" src="https://img.shields.io/badge/community-matrix-yellow?logo=matrix">
|
||||
</a>
|
||||
<a href="https://t.me/start9_labs">
|
||||
<img alt="Static Badge" src="https://img.shields.io/badge/community-telegram-blue?logo=telegram">
|
||||
</a>
|
||||
<a href="https://docs.start9.com">
|
||||
<img alt="Static Badge" src="https://img.shields.io/badge/docs-orange?label=%F0%9F%91%A4%20support">
|
||||
</a>
|
||||
<a href="https://matrix.to/#/#community-dev:matrix.start9labs.com">
|
||||
<img alt="Static Badge" src="https://img.shields.io/badge/developer-matrix-darkcyan?logo=matrix">
|
||||
</a>
|
||||
<a href="https://start9.com">
|
||||
<img alt="Website" src="https://img.shields.io/website?up_message=online&down_message=offline&url=https%3A%2F%2Fstart9.com&logo=website&label=%F0%9F%8C%90%20website">
|
||||
</a>
|
||||
</div>
|
||||
<br />
|
||||
<div align="center">
|
||||
<h3>
|
||||
Welcome to the era of Sovereign Computing
|
||||
</h3>
|
||||
<p>
|
||||
StartOS is an open source Linux distribution optimized for running a personal server. It facilitates the discovery, installation, network configuration, service configuration, data backup, dependency management, and health monitoring of self-hosted software services.
|
||||
</p>
|
||||
</div>
|
||||
<br />
|
||||
<p align="center">
|
||||
<img src="assets/embassyOS.png" alt="embassyOS" width="85%">
|
||||
<img src="assets/StartOS.png" alt="StartOS" width="85%">
|
||||
</p>
|
||||
<br />
|
||||
|
||||
## Running StartOS
|
||||
There are multiple ways to get started with StartOS:
|
||||
|
||||
### 💰 Buy a Start9 server
|
||||
This is the most convenient option. Simply [buy a server](https://store.start9.com) from Start9 and plug it in.
|
||||
|
||||
### 👷 Build your own server
|
||||
This option is easier than you might imagine, and there are 4 reasons why you might prefer it:
|
||||
1. You already have hardware
|
||||
1. You want to save on shipping costs
|
||||
1. You prefer not to divulge your physical address
|
||||
1. You just like building things
|
||||
|
||||
To pursue this option, follow one of our [DIY guides](https://start9.com/latest/diy).
|
||||
|
||||
## ❤️ Contributing
|
||||
There are multiple ways to contribute: work directly on StartOS, package a service for the marketplace, or help with documentation and guides. To learn more about contributing, see [here](https://start9.com/contribute/).
|
||||
|
||||
To report security issues, please email our security team - security@start9.com.
|
||||
|
||||
## 🌎 Marketplace
|
||||
There are dozens of services available for StartOS, and new ones are being added all the time. Check out the full list of available services [here](https://marketplace.start9.com/marketplace). To read more about the Marketplace ecosystem, check out this [blog post](https://blog.start9.com/start9-marketplace-strategy/)
|
||||
|
||||
## 🖥️ User Interface Screenshots
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/eOS-preferences.png" alt="Embassy Preferences" width="49%">
|
||||
<img src="assets/eOS-ghost.png" alt="Embassy Ghost Service" width="49%">
|
||||
</p>
|
||||
<p align="center">
|
||||
<img src="assets/eOS-synapse-health-check.png" alt="Embassy Synapse Health Checks" width="49%">
|
||||
<img src="assets/eOS-sideload.png" alt="Embassy Sideload Service" width="49%">
|
||||
<img src="assets/registry.png" alt="StartOS Marketplace" width="49%">
|
||||
<img src="assets/community.png" alt="StartOS Community Registry" width="49%">
|
||||
<img src="assets/c-lightning.png" alt="StartOS NextCloud Service" width="49%">
|
||||
<img src="assets/btcpay.png" alt="StartOS BTCPay Service" width="49%">
|
||||
<img src="assets/nextcloud.png" alt="StartOS System Settings" width="49%">
|
||||
<img src="assets/system.png" alt="StartOS System Settings" width="49%">
|
||||
<img src="assets/welcome.png" alt="StartOS System Settings" width="49%">
|
||||
<img src="assets/logs.png" alt="StartOS System Settings" width="49%">
|
||||
</p>
|
||||
|
||||
BIN
assets/StartOS.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
assets/btcpay.png
Normal file
|
After Width: | Height: | Size: 396 KiB |
BIN
assets/c-lightning.png
Normal file
|
After Width: | Height: | Size: 402 KiB |
BIN
assets/community.png
Normal file
|
After Width: | Height: | Size: 591 KiB |
|
Before Width: | Height: | Size: 281 KiB |
|
Before Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 191 KiB |
BIN
assets/logs.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
assets/nextcloud.png
Normal file
|
After Width: | Height: | Size: 319 KiB |
BIN
assets/registry.png
Normal file
|
After Width: | Height: | Size: 521 KiB |
BIN
assets/system.png
Normal file
|
After Width: | Height: | Size: 331 KiB |
BIN
assets/welcome.png
Normal file
|
After Width: | Height: | Size: 402 KiB |
16
backend/.sqlx/query-1ce5254f27de971fd87f5ab66d300f2b22433c86617a0dbf796bf2170186dd2e.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO network_keys (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1ce5254f27de971fd87f5ab66d300f2b22433c86617a0dbf796bf2170186dd2e"
|
||||
}
|
||||
14
backend/.sqlx/query-21471490cdc3adb206274cc68e1ea745ffa5da4479478c1fd2158a45324b1930.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM ssh_keys WHERE fingerprint = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "21471490cdc3adb206274cc68e1ea745ffa5da4479478c1fd2158a45324b1930"
|
||||
}
|
||||
40
backend/.sqlx/query-28ea34bbde836e0618c5fc9bb7c36e463c20c841a7d6a0eb15be0f24f4a928ec.json
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT hostname, path, username, password FROM cifs_shares WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "hostname",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "path",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "username",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "password",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "28ea34bbde836e0618c5fc9bb7c36e463c20c841a7d6a0eb15be0f24f4a928ec"
|
||||
}
|
||||
34
backend/.sqlx/query-4099028a5c0de578255bf54a67cef6cb0f1e9a4e158260700f1639dd4b438997.json
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM ssh_keys WHERE fingerprint = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "fingerprint",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "openssh_pubkey",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_at",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4099028a5c0de578255bf54a67cef6cb0f1e9a4e158260700f1639dd4b438997"
|
||||
}
|
||||
50
backend/.sqlx/query-4691e3a2ce80b59009ac17124f54f925f61dc5ea371903e62cdffa5d7b67ca96.json
generated
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM session WHERE logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "logged_in",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "logged_out",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "last_active",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "user_agent",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "metadata",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4691e3a2ce80b59009ac17124f54f925f61dc5ea371903e62cdffa5d7b67ca96"
|
||||
}
|
||||
14
backend/.sqlx/query-4bcfbefb1eb3181343871a1cd7fc3afb81c2be5c681cfa8b4be0ce70610e9c3a.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE session SET logged_out = CURRENT_TIMESTAMP WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4bcfbefb1eb3181343871a1cd7fc3afb81c2be5c681cfa8b4be0ce70610e9c3a"
|
||||
}
|
||||
20
backend/.sqlx/query-629be61c3c341c131ddbbff0293a83dbc6afd07cae69d246987f62cf0cc35c2a.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT password FROM account",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "password",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "629be61c3c341c131ddbbff0293a83dbc6afd07cae69d246987f62cf0cc35c2a"
|
||||
}
|
||||
23
backend/.sqlx/query-687688055e63d27123cdc89a5bbbd8361776290a9411d527eaf1fdb40bef399d.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT key FROM tor WHERE package = $1 AND interface = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "key",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "687688055e63d27123cdc89a5bbbd8361776290a9411d527eaf1fdb40bef399d"
|
||||
}
|
||||
14
backend/.sqlx/query-6d35ccf780fb2bb62586dd1d3df9c1550a41ee580dad3f49d35cb843ebef10ca.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE session SET last_active = CURRENT_TIMESTAMP WHERE id = $1 AND logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6d35ccf780fb2bb62586dd1d3df9c1550a41ee580dad3f49d35cb843ebef10ca"
|
||||
}
|
||||
24
backend/.sqlx/query-770c1017734720453dc87b58c385b987c5af5807151ff71a59000014586752e0.json
generated
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO network_keys (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO UPDATE SET package = EXCLUDED.package RETURNING key",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "key",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "770c1017734720453dc87b58c385b987c5af5807151ff71a59000014586752e0"
|
||||
}
|
||||
65
backend/.sqlx/query-7b64f032d507e8ffe37c41f4c7ad514a66c421a11ab04c26d89a7aa8f6b67210.json
generated
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications WHERE id < $1 ORDER BY id DESC LIMIT $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "package_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "code",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "level",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "title",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "message",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "data",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "7b64f032d507e8ffe37c41f4c7ad514a66c421a11ab04c26d89a7aa8f6b67210"
|
||||
}
|
||||
19
backend/.sqlx/query-7c7a3549c997eb75bf964ea65fbb98a73045adf618696cd838d79203ef5383fb.json
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO account (\n id,\n server_id,\n hostname,\n password,\n network_key,\n root_ca_key_pem,\n root_ca_cert_pem\n ) VALUES (\n 0, $1, $2, $3, $4, $5, $6\n ) ON CONFLICT (id) DO UPDATE SET\n server_id = EXCLUDED.server_id,\n hostname = EXCLUDED.hostname,\n password = EXCLUDED.password,\n network_key = EXCLUDED.network_key,\n root_ca_key_pem = EXCLUDED.root_ca_key_pem,\n root_ca_cert_pem = EXCLUDED.root_ca_cert_pem\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bytea",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7c7a3549c997eb75bf964ea65fbb98a73045adf618696cd838d79203ef5383fb"
|
||||
}
|
||||
14
backend/.sqlx/query-7e0649d839927e57fa03ee51a2c9f96a8bdb0fc97ee8a3c6df1069e1e2b98576.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM tor WHERE package = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7e0649d839927e57fa03ee51a2c9f96a8bdb0fc97ee8a3c6df1069e1e2b98576"
|
||||
}
|
||||
16
backend/.sqlx/query-8951b9126fbf60dbb5997241e11e3526b70bccf3e407327917294a993bc17ed5.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8951b9126fbf60dbb5997241e11e3526b70bccf3e407327917294a993bc17ed5"
|
||||
}
|
||||
64
backend/.sqlx/query-94d471bb374b4965c6cbedf8c17bbf6bea226d38efaf6559923c79a36d5ca08c.json
generated
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications ORDER BY id DESC LIMIT $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "package_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "code",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "level",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "title",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "message",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "data",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "94d471bb374b4965c6cbedf8c17bbf6bea226d38efaf6559923c79a36d5ca08c"
|
||||
}
|
||||
44
backend/.sqlx/query-95c4ab4c645f3302568c6ff13d85ab58252362694cf0f56999bf60194d20583a.json
generated
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, hostname, path, username, password FROM cifs_shares",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "hostname",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "path",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "username",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "password",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "95c4ab4c645f3302568c6ff13d85ab58252362694cf0f56999bf60194d20583a"
|
||||
}
|
||||
14
backend/.sqlx/query-a60d6e66719325b08dc4ecfacaf337527233c84eee758ac9be967906e5841d27.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM cifs_shares WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a60d6e66719325b08dc4ecfacaf337527233c84eee758ac9be967906e5841d27"
|
||||
}
|
||||
32
backend/.sqlx/query-a6b0c8909a3a5d6d9156aebfb359424e6b5a1d1402e028219e21726f1ebd282e.json
generated
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT fingerprint, openssh_pubkey, created_at FROM ssh_keys",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "fingerprint",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "openssh_pubkey",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_at",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a6b0c8909a3a5d6d9156aebfb359424e6b5a1d1402e028219e21726f1ebd282e"
|
||||
}
|
||||
18
backend/.sqlx/query-b1147beaaabbed89f2ab8c1e13ec4393a9a8fde2833cf096af766a979d94dee6.json
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE cifs_shares SET hostname = $1, path = $2, username = $3, password = $4 WHERE id = $5",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b1147beaaabbed89f2ab8c1e13ec4393a9a8fde2833cf096af766a979d94dee6"
|
||||
}
|
||||
14
backend/.sqlx/query-b203820ee1c553a4b246eac74b79bd10d5717b2a0ddecf22330b7d531aac7c5d.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM network_keys WHERE package = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b203820ee1c553a4b246eac74b79bd10d5717b2a0ddecf22330b7d531aac7c5d"
|
||||
}
|
||||
20
backend/.sqlx/query-d5117054072476377f3c4f040ea429d4c9b2cf534e76f35c80a2bf60e8599cca.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT openssh_pubkey FROM ssh_keys",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "openssh_pubkey",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d5117054072476377f3c4f040ea429d4c9b2cf534e76f35c80a2bf60e8599cca"
|
||||
}
|
||||
19
backend/.sqlx/query-da71f94b29798d1738d2b10b9a721ea72db8cfb362e7181c8226d9297507c62b.json
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO notifications (package_id, code, level, title, message, data) VALUES ($1, $2, $3, $4, $5, $6)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "da71f94b29798d1738d2b10b9a721ea72db8cfb362e7181c8226d9297507c62b"
|
||||
}
|
||||
14
backend/.sqlx/query-e185203cf84e43b801dfb23b4159e34aeaef1154dcd3d6811ab504915497ccf7.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM notifications WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e185203cf84e43b801dfb23b4159e34aeaef1154dcd3d6811ab504915497ccf7"
|
||||
}
|
||||
20
backend/.sqlx/query-e545696735f202f9d13cf22a561f3ff3f9aed7f90027a9ba97634bcb47d772f0.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT tor_key FROM account WHERE id = 0",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "tor_key",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "e545696735f202f9d13cf22a561f3ff3f9aed7f90027a9ba97634bcb47d772f0"
|
||||
}
|
||||
16
backend/.sqlx/query-e5843c5b0e7819b29aa1abf2266799bd4f82e761837b526a0972c3d4439a264d.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO session (id, user_agent, metadata) VALUES ($1, $2, $3)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e5843c5b0e7819b29aa1abf2266799bd4f82e761837b526a0972c3d4439a264d"
|
||||
}
|
||||
40
backend/.sqlx/query-e95322a8e2ae3b93f1e974b24c0b81803f1e9ec9e8ebbf15cafddfc1c5a028ed.json
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n network_keys.package,\n network_keys.interface,\n network_keys.key,\n tor.key AS \"tor_key?\"\n FROM\n network_keys\n LEFT JOIN\n tor\n ON\n network_keys.package = tor.package\n AND\n network_keys.interface = tor.interface\n WHERE\n network_keys.package = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "package",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "interface",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "key",
|
||||
"type_info": "Bytea"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "tor_key?",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e95322a8e2ae3b93f1e974b24c0b81803f1e9ec9e8ebbf15cafddfc1c5a028ed"
|
||||
}
|
||||
14
backend/.sqlx/query-eb750adaa305bdbf3c5b70aaf59139c7b7569602adb58f2d6b3a94da4f167b0a.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM notifications WHERE id < $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "eb750adaa305bdbf3c5b70aaf59139c7b7569602adb58f2d6b3a94da4f167b0a"
|
||||
}
|
||||
25
backend/.sqlx/query-ecc765d8205c0876956f95f76944ac6a5f34dd820c4073b7728c7067aab9fded.json
generated
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO cifs_shares (hostname, path, username, password) VALUES ($1, $2, $3, $4) RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ecc765d8205c0876956f95f76944ac6a5f34dd820c4073b7728c7067aab9fded"
|
||||
}
|
||||
16
backend/.sqlx/query-f6d1c5ef0f9d9577bea8382318967b9deb46da75788c7fe6082b43821c22d556.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO ssh_keys (fingerprint, openssh_pubkey, created_at) VALUES ($1, $2, $3)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f6d1c5ef0f9d9577bea8382318967b9deb46da75788c7fe6082b43821c22d556"
|
||||
}
|
||||
20
backend/.sqlx/query-f7d2dae84613bcef330f7403352cc96547f3f6dbec11bf2eadfaf53ad8ab51b5.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT network_key FROM account WHERE id = 0",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "network_key",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f7d2dae84613bcef330f7403352cc96547f3f6dbec11bf2eadfaf53ad8ab51b5"
|
||||
}
|
||||
62
backend/.sqlx/query-fe6e4f09f3028e5b6b6259e86cbad285680ce157aae9d7837ac020c8b2945e7f.json
generated
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM account WHERE id = 0",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "password",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "tor_key",
|
||||
"type_info": "Bytea"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "server_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "hostname",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "network_key",
|
||||
"type_info": "Bytea"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "root_ca_key_pem",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "root_ca_cert_pem",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "fe6e4f09f3028e5b6b6259e86cbad285680ce157aae9d7837ac020c8b2945e7f"
|
||||
}
|
||||
4433
backend/Cargo.lock
generated
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
||||
description = "The core of the Start9 Embassy Operating System"
|
||||
documentation = "https://docs.rs/embassy-os"
|
||||
description = "The core of StartOS"
|
||||
documentation = "https://docs.rs/start-os"
|
||||
edition = "2021"
|
||||
keywords = [
|
||||
"self-hosted",
|
||||
@@ -11,146 +11,160 @@ keywords = [
|
||||
"full-node",
|
||||
"lightning",
|
||||
]
|
||||
name = "embassy-os"
|
||||
name = "start-os"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/Start9Labs/embassy-os"
|
||||
version = "0.3.3"
|
||||
repository = "https://github.com/Start9Labs/start-os"
|
||||
version = "0.3.5"
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
name = "embassy"
|
||||
name = "startos"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "embassyd"
|
||||
path = "src/bin/embassyd.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "embassy-init"
|
||||
path = "src/bin/embassy-init.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "embassy-sdk"
|
||||
path = "src/bin/embassy-sdk.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "embassy-cli"
|
||||
path = "src/bin/embassy-cli.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "avahi-alias"
|
||||
path = "src/bin/avahi-alias.rs"
|
||||
name = "startbox"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
avahi = ["avahi-sys"]
|
||||
default = ["avahi", "js_engine"]
|
||||
avahi-alias = ["avahi"]
|
||||
cli = []
|
||||
daemon = []
|
||||
default = ["cli", "sdk", "daemon", "js_engine"]
|
||||
dev = []
|
||||
unstable = ["patch-db/unstable"]
|
||||
docker = []
|
||||
sdk = []
|
||||
unstable = ["console-subscriber", "tokio/tracing"]
|
||||
|
||||
[dependencies]
|
||||
aes = { version = "0.7.5", features = ["ctr"] }
|
||||
async-stream = "0.3.3"
|
||||
async-trait = "0.1.56"
|
||||
async-compression = { version = "0.4.4", features = [
|
||||
"gzip",
|
||||
"brotli",
|
||||
"tokio",
|
||||
] }
|
||||
async-stream = "0.3.5"
|
||||
async-trait = "0.1.74"
|
||||
avahi-sys = { git = "https://github.com/Start9Labs/avahi-sys", version = "0.10.0", branch = "feature/dynamic-linking", features = [
|
||||
"dynamic",
|
||||
], optional = true }
|
||||
base32 = "0.4.0"
|
||||
base64 = "0.13.0"
|
||||
base64ct = "1.5.1"
|
||||
base64 = "0.21.4"
|
||||
base64ct = "1.6.0"
|
||||
basic-cookies = "0.1.4"
|
||||
bollard = "0.13.0"
|
||||
bytes = "1"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
clap = "3.2.8"
|
||||
color-eyre = "0.6.1"
|
||||
cookie_store = "0.16.1"
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
clap = "3.2.25"
|
||||
color-eyre = "0.6.2"
|
||||
console = "0.15.7"
|
||||
console-subscriber = { version = "0.2", optional = true }
|
||||
cookie = "0.18.0"
|
||||
cookie_store = "0.20.0"
|
||||
current_platform = "0.2.0"
|
||||
digest = "0.10.3"
|
||||
digest-old = { package = "digest", version = "0.9.0" }
|
||||
digest = "0.10.7"
|
||||
divrem = "1.0.0"
|
||||
ed25519 = { version = "1.5.2", features = ["pkcs8", "pem", "alloc"] }
|
||||
ed25519-dalek = { version = "1.0.1", features = ["serde"] }
|
||||
ed25519 = { version = "2.2.3", features = ["pkcs8", "pem", "alloc"] }
|
||||
ed25519-dalek = { version = "2.0.0", features = [
|
||||
"serde",
|
||||
"zeroize",
|
||||
"rand_core",
|
||||
"digest",
|
||||
] }
|
||||
ed25519-dalek-v1 = { package = "ed25519-dalek", version = "1" }
|
||||
embassy_container_init = { path = "../libs/embassy_container_init" }
|
||||
emver = { version = "0.1.7", git = "https://github.com/Start9Labs/emver-rs.git", features = [
|
||||
"serde",
|
||||
] }
|
||||
fd-lock-rs = "0.1.4"
|
||||
futures = "0.3.21"
|
||||
git-version = "0.3.5"
|
||||
futures = "0.3.28"
|
||||
gpt = "3.1.0"
|
||||
helpers = { path = "../libs/helpers" }
|
||||
embassy_container_init = { path = "../libs/embassy_container_init" }
|
||||
hex = "0.4.3"
|
||||
hmac = "0.12.1"
|
||||
http = "0.2.8"
|
||||
hyper = { version = "0.14.20", features = ["full"] }
|
||||
hyper-ws-listener = "0.2.0"
|
||||
imbl = "2.0.0"
|
||||
indexmap = { version = "1.9.1", features = ["serde"] }
|
||||
http = "0.2.9"
|
||||
hyper = { version = "0.14.27", features = ["full"] }
|
||||
hyper-ws-listener = "0.3.0"
|
||||
imbl = "2.0.2"
|
||||
imbl-value = { git = "https://github.com/Start9Labs/imbl-value.git" }
|
||||
include_dir = "0.7.3"
|
||||
indexmap = { version = "2.0.2", features = ["serde"] }
|
||||
indicatif = { version = "0.17.7", features = ["tokio"] }
|
||||
ipnet = { version = "2.8.0", features = ["serde"] }
|
||||
iprange = { version = "0.6.7", features = ["serde"] }
|
||||
isocountry = "0.3.2"
|
||||
itertools = "0.10.3"
|
||||
josekit = "0.8.1"
|
||||
itertools = "0.11.0"
|
||||
jaq-core = "0.10.1"
|
||||
jaq-std = "0.10.0"
|
||||
josekit = "0.8.4"
|
||||
js_engine = { path = '../libs/js_engine', optional = true }
|
||||
jsonpath_lib = "0.3.0"
|
||||
jsonpath_lib = { git = "https://github.com/Start9Labs/jsonpath.git" }
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.126"
|
||||
log = "0.4.17"
|
||||
mbrman = "0.5.0"
|
||||
libc = "0.2.149"
|
||||
log = "0.4.20"
|
||||
mbrman = "0.5.2"
|
||||
models = { version = "*", path = "../libs/models" }
|
||||
nix = "0.25.0"
|
||||
nom = "7.1.1"
|
||||
num = "0.4.0"
|
||||
num_enum = "0.5.7"
|
||||
openssh-keys = "0.5.0"
|
||||
openssl = { version = "0.10.41", features = ["vendored"] }
|
||||
new_mime_guess = "4"
|
||||
nix = { version = "0.27.1", features = ["user", "process", "signal", "fs"] }
|
||||
nom = "7.1.3"
|
||||
num = "0.4.1"
|
||||
num_enum = "0.7.0"
|
||||
openssh-keys = "0.6.2"
|
||||
openssl = { version = "0.10.57", features = ["vendored"] }
|
||||
p256 = { version = "0.13.2", features = ["pem"] }
|
||||
patch-db = { version = "*", path = "../patch-db/patch-db", features = [
|
||||
"trace",
|
||||
] }
|
||||
pbkdf2 = "0.11.0"
|
||||
pin-project = "1.0.11"
|
||||
pkcs8 = { version = "0.9.0", features = ["std"] }
|
||||
prettytable-rs = "0.9.0"
|
||||
proptest = "1.0.0"
|
||||
proptest-derive = "0.3.0"
|
||||
pbkdf2 = "0.12.2"
|
||||
pin-project = "1.1.3"
|
||||
pkcs8 = { version = "0.10.2", features = ["std"] }
|
||||
prettytable-rs = "0.10.0"
|
||||
proptest = "1.3.1"
|
||||
proptest-derive = "0.4.0"
|
||||
rand = { version = "0.8.5", features = ["std"] }
|
||||
rand-old = { package = "rand", version = "0.7.3" }
|
||||
regex = "1.6.0"
|
||||
reqwest = { version = "0.11.11", features = ["stream", "json", "socks"] }
|
||||
reqwest_cookie_store = "0.4.0"
|
||||
rpassword = "7.0.0"
|
||||
regex = "1.10.2"
|
||||
reqwest = { version = "0.11.22", features = ["stream", "json", "socks"] }
|
||||
reqwest_cookie_store = "0.6.0"
|
||||
rpassword = "7.2.0"
|
||||
rpc-toolkit = "0.2.2"
|
||||
rust-argon2 = "1.0.0"
|
||||
rust-argon2 = "2.0.0"
|
||||
scopeguard = "1.1" # because avahi-sys fucks your shit up
|
||||
serde = { version = "1.0.139", features = ["derive", "rc"] }
|
||||
serde_cbor = { package = "ciborium", version = "0.2.0" }
|
||||
serde_json = "1.0.82"
|
||||
serde_toml = { package = "toml", version = "0.5.9" }
|
||||
serde_with = { version = "2.0.1", features = ["macros", "json"] }
|
||||
serde_yaml = "0.9.11"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_cbor = { package = "ciborium", version = "0.2.1" }
|
||||
serde_json = "1.0"
|
||||
serde_toml = { package = "toml", version = "0.8.2" }
|
||||
serde_with = { version = "3.4.0", features = ["macros", "json"] }
|
||||
serde_yaml = "0.9.25"
|
||||
sha2 = "0.10.2"
|
||||
sha2-old = { package = "sha2", version = "0.9.9" }
|
||||
simple-logging = "2.0.2"
|
||||
sqlx = { version = "0.6.0", features = [
|
||||
sqlx = { version = "0.7.2", features = [
|
||||
"chrono",
|
||||
"offline",
|
||||
"runtime-tokio-rustls",
|
||||
"postgres",
|
||||
] }
|
||||
stderrlog = "0.5.3"
|
||||
tar = "0.4.38"
|
||||
thiserror = "1.0.31"
|
||||
tokio = { version = "1.21.2", features = ["full"] }
|
||||
tokio-stream = { version = "0.1.9", features = ["io-util", "sync"] }
|
||||
sscanf = "0.4.1"
|
||||
ssh-key = { version = "0.6.2", features = ["ed25519"] }
|
||||
stderrlog = "0.5.4"
|
||||
tar = "0.4.40"
|
||||
thiserror = "1.0.49"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-rustls = "0.24.1"
|
||||
tokio-socks = "0.5.1"
|
||||
tokio-stream = { version = "0.1.14", features = ["io-util", "sync", "net"] }
|
||||
tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" }
|
||||
tokio-tungstenite = { version = "0.17.1", features = ["native-tls"] }
|
||||
tokio-rustls = "0.23.4"
|
||||
tokio-util = { version = "0.7.3", features = ["io"] }
|
||||
tokio-tungstenite = { version = "0.20.1", features = ["native-tls"] }
|
||||
tokio-util = { version = "0.7.9", features = ["io"] }
|
||||
torut = "0.2.1"
|
||||
tracing = "0.1.35"
|
||||
tracing = "0.1.39"
|
||||
tracing-error = "0.2.0"
|
||||
tracing-futures = "0.2.5"
|
||||
tracing-subscriber = { version = "0.3.14", features = ["env-filter"] }
|
||||
trust-dns-server = "0.22.0"
|
||||
typed-builder = "0.10.0"
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
tracing-journald = "0.3.0"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
trust-dns-server = "0.23.1"
|
||||
typed-builder = "0.17.0"
|
||||
url = { version = "2.4.1", features = ["serde"] }
|
||||
urlencoding = "2.1.3"
|
||||
uuid = { version = "1.4.1", features = ["v4"] }
|
||||
zeroize = "1.6.0"
|
||||
|
||||
[profile.test]
|
||||
opt-level = 3
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
# embassyOS Backend
|
||||
# StartOS Backend
|
||||
|
||||
- Requirements:
|
||||
- [Install Rust](https://rustup.rs)
|
||||
- Recommended: [rust-analyzer](https://rust-analyzer.github.io/)
|
||||
- [Docker](https://docs.docker.com/get-docker/)
|
||||
- [Rust ARM64 Build Container](https://github.com/Start9Labs/rust-arm-builder)
|
||||
- Scripts (run withing the `./backend` directory)
|
||||
- Mac `brew install gnu-tar`
|
||||
- Scripts (run within the `./backend` directory)
|
||||
- `build-prod.sh` - compiles a release build of the artifacts for running on
|
||||
ARM64
|
||||
- A Linux computer or VM
|
||||
|
||||
## Structure
|
||||
|
||||
The embassyOS backend is broken up into 4 different binaries:
|
||||
The StartOS backend is packed into a single binary `startbox` that is symlinked under
|
||||
several different names for different behaviour:
|
||||
|
||||
- embassyd: This is the main workhorse of embassyOS - any new functionality you
|
||||
- startd: This is the main workhorse of StartOS - any new functionality you
|
||||
want will likely go here
|
||||
- embassy-init: This is the component responsible for allowing you to set up
|
||||
your device, and handles system initialization on startup
|
||||
- embassy-cli: This is a CLI tool that will allow you to issue commands to
|
||||
embassyd and control it similarly to the UI
|
||||
- embassy-sdk: This is a CLI tool that aids in building and packaging services
|
||||
you wish to deploy to the Embassy
|
||||
- start-cli: This is a CLI tool that will allow you to issue commands to
|
||||
startd and control it similarly to the UI
|
||||
- start-sdk: This is a CLI tool that aids in building and packaging services
|
||||
you wish to deploy to StartOS
|
||||
|
||||
Finally there is a library `embassy` that supports all four of these tools.
|
||||
Finally there is a library `startos` that supports all of these tools.
|
||||
|
||||
See [here](/backend/Cargo.toml) for details.
|
||||
|
||||
## Building
|
||||
|
||||
You can build the entire operating system image using `make` from the root of
|
||||
the embassyOS project. This will subsequently invoke the build scripts above to
|
||||
the StartOS project. This will subsequently invoke the build scripts above to
|
||||
actually create the requisite binaries and put them onto the final operating
|
||||
system image.
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
shopt -s expand_aliases
|
||||
|
||||
if [ "$0" != "./build-dev.sh" ]; then
|
||||
>&2 echo "Must be run from backend directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
USE_TTY=
|
||||
if tty -s; then
|
||||
USE_TTY="-it"
|
||||
fi
|
||||
|
||||
alias 'rust-arm64-builder'='docker run $USE_TTY --rm -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src start9/rust-arm-cross:aarch64'
|
||||
|
||||
cd ..
|
||||
rust-arm64-builder sh -c "(cd backend && cargo build --locked)"
|
||||
cd backend
|
||||
|
||||
sudo chown -R $USER target
|
||||
sudo chown -R $USER ~/.cargo
|
||||
#rust-arm64-builder aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/embassyd
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
shopt -s expand_aliases
|
||||
|
||||
if [ "$0" != "./build-portable-dev.sh" ]; then
|
||||
>&2 echo "Must be run from backend directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
USE_TTY=
|
||||
if tty -s; then
|
||||
USE_TTY="-it"
|
||||
fi
|
||||
|
||||
alias 'rust-musl-builder'='docker run $USE_TTY --rm -v "$HOME"/.cargo/registry:/root/.cargo/registry -v "$(pwd)":/home/rust/src start9/rust-musl-cross:x86_64-musl'
|
||||
|
||||
cd ..
|
||||
rust-musl-builder sh -c "(cd backend && cargo +beta build --target=x86_64-unknown-linux-musl --no-default-features --locked)"
|
||||
cd backend
|
||||
|
||||
sudo chown -R $USER target
|
||||
sudo chown -R $USER ~/.cargo
|
||||
@@ -17,46 +17,28 @@ if tty -s; then
|
||||
USE_TTY="-it"
|
||||
fi
|
||||
|
||||
alias 'rust-gnu-builder'='docker run $USE_TTY --rm -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src -P start9/rust-arm-cross:aarch64'
|
||||
alias 'rust-musl-builder'='docker run $USE_TTY --rm -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
|
||||
|
||||
cd ..
|
||||
FLAGS=""
|
||||
if [[ "$ENVIRONMENT" =~ (^|-)unstable($|-) ]]; then
|
||||
FLAGS="unstable,$FLAGS"
|
||||
fi
|
||||
if [[ "$ENVIRONMENT" =~ (^|-)dev($|-) ]]; then
|
||||
FLAGS="dev,$FLAGS"
|
||||
fi
|
||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
|
||||
alias 'rust-gnu-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/usr/local/cargo/registry -v "$(pwd)":/home/rust/src -w /home/rust/src -P start9/rust-arm-cross:aarch64'
|
||||
alias 'rust-musl-builder'='docker run $USE_TTY --rm -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
|
||||
|
||||
set +e
|
||||
fail=
|
||||
if [[ "$FLAGS" = "" ]]; then
|
||||
rust-gnu-builder sh -c "(git config --global --add safe.directory '*'; cd backend && cargo build --release --locked --target=$ARCH-unknown-linux-gnu)"
|
||||
if test $? -ne 0; then
|
||||
fail=true
|
||||
fi
|
||||
for ARCH in x86_64 aarch64
|
||||
do
|
||||
rust-musl-builder sh -c "(git config --global --add safe.directory '*'; cd libs && cargo build --release --locked --bin embassy_container_init )"
|
||||
if test $? -ne 0; then
|
||||
fail=true
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "FLAGS=$FLAGS"
|
||||
rust-gnu-builder sh -c "(git config --global --add safe.directory '*'; cd backend && cargo build --release --features $FLAGS --locked --target=$ARCH-unknown-linux-gnu)"
|
||||
if test $? -ne 0; then
|
||||
fail=true
|
||||
fi
|
||||
for ARCH in x86_64 aarch64
|
||||
do
|
||||
rust-musl-builder sh -c "(git config --global --add safe.directory '*'; cd libs && cargo build --release --features $FLAGS --locked --bin embassy_container_init)"
|
||||
if test $? -ne 0; then
|
||||
fail=true
|
||||
fi
|
||||
done
|
||||
echo "FEATURES=\"$FEATURES\""
|
||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||
rust-gnu-builder sh -c "(cd backend && cargo build --release --features avahi-alias,$FEATURES --locked --target=$ARCH-unknown-linux-gnu)"
|
||||
if test $? -ne 0; then
|
||||
fail=true
|
||||
fi
|
||||
for ARCH in x86_64 aarch64
|
||||
do
|
||||
rust-musl-builder sh -c "(cd libs && cargo build --release --locked --bin embassy_container_init)"
|
||||
if test $? -ne 0; then
|
||||
fail=true
|
||||
fi
|
||||
done
|
||||
set -e
|
||||
cd backend
|
||||
|
||||
@@ -67,5 +49,3 @@ sudo chown -R $USER ../libs/target
|
||||
if [ -n "$fail" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#rust-arm64-builder aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/embassyd
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
[Unit]
|
||||
Description=Embassy Init
|
||||
After=network-online.target
|
||||
Requires=network-online.target
|
||||
Wants=avahi-daemon.service tor.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
Environment=RUST_LOG=embassy_init=debug,embassy=debug,js_engine=debug,patch_db=warn
|
||||
ExecStart=/usr/bin/embassy-init
|
||||
RemainAfterExit=true
|
||||
StandardOutput=append:/var/log/embassy-init.log
|
||||
|
||||
[Install]
|
||||
WantedBy=embassyd.service
|
||||
@@ -1,17 +0,0 @@
|
||||
[Unit]
|
||||
Description=Embassy Daemon
|
||||
After=embassy-init.service
|
||||
Requires=embassy-init.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment=RUST_LOG=embassyd=debug,embassy=debug,js_engine=debug,patch_db=warn
|
||||
ExecStart=/usr/bin/embassyd
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
ManagedOOMPreference=avoid
|
||||
CPUAccounting=true
|
||||
CPUWeight=1000
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -8,4 +8,14 @@ if [ "$0" != "./install-sdk.sh" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cargo install --bin=embassy-sdk --bin=embassy-cli --path=. --no-default-features --features=js_engine --locked
|
||||
frontend="../frontend/dist/static"
|
||||
[ -d "$frontend" ] || mkdir -p "$frontend"
|
||||
|
||||
if [ -z "$PLATFORM" ]; then
|
||||
export PLATFORM=$(uname -m)
|
||||
fi
|
||||
|
||||
cargo install --path=. --no-default-features --features=js_engine,sdk,cli --locked
|
||||
startbox_loc=$(which startbox)
|
||||
ln -sf $startbox_loc $(dirname $startbox_loc)/start-cli
|
||||
ln -sf $startbox_loc $(dirname $startbox_loc)/start-sdk
|
||||
|
||||
62
backend/migrations/20230118185232_NetworkKeys.sql
Normal file
@@ -0,0 +1,62 @@
|
||||
-- Add migration script here
|
||||
CREATE EXTENSION pgcrypto;
|
||||
|
||||
ALTER TABLE
|
||||
account
|
||||
ADD
|
||||
COLUMN server_id TEXT,
|
||||
ADD
|
||||
COLUMN hostname TEXT,
|
||||
ADD
|
||||
COLUMN network_key BYTEA CHECK (length(network_key) = 32),
|
||||
ADD
|
||||
COLUMN root_ca_key_pem TEXT,
|
||||
ADD
|
||||
COLUMN root_ca_cert_pem TEXT;
|
||||
|
||||
UPDATE
|
||||
account
|
||||
SET
|
||||
network_key = gen_random_bytes(32),
|
||||
root_ca_key_pem = (
|
||||
SELECT
|
||||
priv_key_pem
|
||||
FROM
|
||||
certificates
|
||||
WHERE
|
||||
id = 0
|
||||
),
|
||||
root_ca_cert_pem = (
|
||||
SELECT
|
||||
certificate_pem
|
||||
FROM
|
||||
certificates
|
||||
WHERE
|
||||
id = 0
|
||||
)
|
||||
WHERE
|
||||
id = 0;
|
||||
|
||||
ALTER TABLE
|
||||
account
|
||||
ALTER COLUMN
|
||||
tor_key DROP NOT NULL,
|
||||
ALTER COLUMN
|
||||
network_key
|
||||
SET
|
||||
NOT NULL,
|
||||
ALTER COLUMN
|
||||
root_ca_key_pem
|
||||
SET
|
||||
NOT NULL,
|
||||
ALTER COLUMN
|
||||
root_ca_cert_pem
|
||||
SET
|
||||
NOT NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS network_keys (
|
||||
package TEXT NOT NULL,
|
||||
interface TEXT NOT NULL,
|
||||
key BYTEA NOT NULL CHECK (length(key) = 32),
|
||||
PRIMARY KEY (package, interface)
|
||||
);
|
||||
@@ -1,810 +0,0 @@
|
||||
{
|
||||
"db": "PostgreSQL",
|
||||
"094882d4d46d52e814f9aaf5fae172a5dd745b06cbde347f47b18e6498167269": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE certificates SET priv_key_pem = $1, certificate_pem = $2, updated_at = now() WHERE lookup_string = $3"
|
||||
},
|
||||
"165daa7d6a60cb42122373b2c5ac7d39399bcc99992f0002ee7bfef50a8daceb": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
}
|
||||
},
|
||||
"query": "DELETE FROM certificates WHERE id = 0 OR id = 1;"
|
||||
},
|
||||
"1f7936d27d63f01118ecd6f824e8a79607ed2b6e6def23f3e2487466dd2ddfe1": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO certificates (priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES ($1, $2, $3, now(), now())"
|
||||
},
|
||||
"21471490cdc3adb206274cc68e1ea745ffa5da4479478c1fd2158a45324b1930": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "DELETE FROM ssh_keys WHERE fingerprint = $1"
|
||||
},
|
||||
"22613628ff50341fdc35366e194fdcd850118824763cfe0dfff68dadc72167e9": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO UPDATE SET key = $3"
|
||||
},
|
||||
"28ea34bbde836e0618c5fc9bb7c36e463c20c841a7d6a0eb15be0f24f4a928ec": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "hostname",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "password",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT hostname, path, username, password FROM cifs_shares WHERE id = $1"
|
||||
},
|
||||
"3502e58f2ab48fb4566d21c920c096f81acfa3ff0d02f970626a4dcd67bac71d": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "tor_key",
|
||||
"ordinal": 0,
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
}
|
||||
},
|
||||
"query": "SELECT tor_key FROM account"
|
||||
},
|
||||
"4099028a5c0de578255bf54a67cef6cb0f1e9a4e158260700f1639dd4b438997": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "fingerprint",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "openssh_pubkey",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT * FROM ssh_keys WHERE fingerprint = $1"
|
||||
},
|
||||
"46815a4ac2c43e1dfbab3c0017ed09d5c833062e523205db4245a5218b2562b8": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "priv_key_pem",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "certificate_pem",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE lookup_string = $1"
|
||||
},
|
||||
"4691e3a2ce80b59009ac17124f54f925f61dc5ea371903e62cdffa5d7b67ca96": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "logged_in",
|
||||
"ordinal": 1,
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"name": "logged_out",
|
||||
"ordinal": 2,
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"name": "last_active",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"name": "user_agent",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "metadata",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
}
|
||||
},
|
||||
"query": "SELECT * FROM session WHERE logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP"
|
||||
},
|
||||
"4bcfbefb1eb3181343871a1cd7fc3afb81c2be5c681cfa8b4be0ce70610e9c3a": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE session SET logged_out = CURRENT_TIMESTAMP WHERE id = $1"
|
||||
},
|
||||
"548448e8ed8bcdf9efdc813d65af2cc55064685293b936f0f09e07f91a328eb9": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "setval",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
null
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
}
|
||||
},
|
||||
"query": "SELECT setval('certificates_id_seq', GREATEST(MAX(id) + 1, nextval('certificates_id_seq') - 1)) FROM certificates"
|
||||
},
|
||||
"629be61c3c341c131ddbbff0293a83dbc6afd07cae69d246987f62cf0cc35c2a": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "password",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
}
|
||||
},
|
||||
"query": "SELECT password FROM account"
|
||||
},
|
||||
"687688055e63d27123cdc89a5bbbd8361776290a9411d527eaf1fdb40bef399d": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "key",
|
||||
"ordinal": 0,
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT key FROM tor WHERE package = $1 AND interface = $2"
|
||||
},
|
||||
"6c96d76bffcc5f03290d8d8544a58521345ed2a843a509b17bbcd6257bb81821": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "priv_key_pem",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "certificate_pem",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
}
|
||||
},
|
||||
"query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE id = 1;"
|
||||
},
|
||||
"6d35ccf780fb2bb62586dd1d3df9c1550a41ee580dad3f49d35cb843ebef10ca": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE session SET last_active = CURRENT_TIMESTAMP WHERE id = $1 AND logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP"
|
||||
},
|
||||
"7b64f032d507e8ffe37c41f4c7ad514a66c421a11ab04c26d89a7aa8f6b67210": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "package_id",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 2,
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "level",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "data",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Int8"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications WHERE id < $1 ORDER BY id DESC LIMIT $2"
|
||||
},
|
||||
"7e0649d839927e57fa03ee51a2c9f96a8bdb0fc97ee8a3c6df1069e1e2b98576": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "DELETE FROM tor WHERE package = $1"
|
||||
},
|
||||
"8951b9126fbf60dbb5997241e11e3526b70bccf3e407327917294a993bc17ed5": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING"
|
||||
},
|
||||
"94d471bb374b4965c6cbedf8c17bbf6bea226d38efaf6559923c79a36d5ca08c": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "package_id",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 2,
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "level",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "data",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications ORDER BY id DESC LIMIT $1"
|
||||
},
|
||||
"95c4ab4c645f3302568c6ff13d85ab58252362694cf0f56999bf60194d20583a": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "hostname",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "password",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
}
|
||||
},
|
||||
"query": "SELECT id, hostname, path, username, password FROM cifs_shares"
|
||||
},
|
||||
"a60d6e66719325b08dc4ecfacaf337527233c84eee758ac9be967906e5841d27": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "DELETE FROM cifs_shares WHERE id = $1"
|
||||
},
|
||||
"a645d636be810a4ba61dcadf22e90de6e9baf3614aa9e97f053ff480cb3118a2": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO tor (package, interface, key) VALUES ($1, 'main', $2) ON CONFLICT (package, interface) DO UPDATE SET key = $2"
|
||||
},
|
||||
"a6645d91f76b3d5fac2191ea3bec5dab7d7d124715fde02e6a816fa5dbc7acf2": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO account (id, password, tor_key) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET password = $2, tor_key = $3"
|
||||
},
|
||||
"a6b0c8909a3a5d6d9156aebfb359424e6b5a1d1402e028219e21726f1ebd282e": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "fingerprint",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "openssh_pubkey",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
}
|
||||
},
|
||||
"query": "SELECT fingerprint, openssh_pubkey, created_at FROM ssh_keys"
|
||||
},
|
||||
"b1147beaaabbed89f2ab8c1e13ec4393a9a8fde2833cf096af766a979d94dee6": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Int4"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE cifs_shares SET hostname = $1, path = $2, username = $3, password = $4 WHERE id = $5"
|
||||
},
|
||||
"cec8112be0ebc02ef7e651631be09efe26d1677b5b8aa95ceb3a92aff1afdbcc": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (1, $1, $2, NULL, now(), now())"
|
||||
},
|
||||
"d5117054072476377f3c4f040ea429d4c9b2cf534e76f35c80a2bf60e8599cca": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "openssh_pubkey",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
}
|
||||
},
|
||||
"query": "SELECT openssh_pubkey FROM ssh_keys"
|
||||
},
|
||||
"da71f94b29798d1738d2b10b9a721ea72db8cfb362e7181c8226d9297507c62b": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO notifications (package_id, code, level, title, message, data) VALUES ($1, $2, $3, $4, $5, $6)"
|
||||
},
|
||||
"df4428ccb891bd791824bcd7990550cc9651e1cfaab1db33833ff7959d113b2c": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (0, $1, $2, NULL, now(), now())"
|
||||
},
|
||||
"e185203cf84e43b801dfb23b4159e34aeaef1154dcd3d6811ab504915497ccf7": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "DELETE FROM notifications WHERE id = $1"
|
||||
},
|
||||
"e25e53c45c5a494a45cdb4d145de507df6f322ac6706e71b86895f1c64195f41": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE account SET password = $1"
|
||||
},
|
||||
"e5843c5b0e7819b29aa1abf2266799bd4f82e761837b526a0972c3d4439a264d": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO session (id, user_agent, metadata) VALUES ($1, $2, $3)"
|
||||
},
|
||||
"e85749336fce4afaf16627bee8cfcb70be6f189ea7d1f05f9a26bead4be11839": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "interface",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"ordinal": 1,
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT interface, key FROM tor WHERE package = $1"
|
||||
},
|
||||
"eb750adaa305bdbf3c5b70aaf59139c7b7569602adb58f2d6b3a94da4f167b0a": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "DELETE FROM notifications WHERE id < $1"
|
||||
},
|
||||
"ecc765d8205c0876956f95f76944ac6a5f34dd820c4073b7728c7067aab9fded": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO cifs_shares (hostname, path, username, password) VALUES ($1, $2, $3, $4) RETURNING id"
|
||||
},
|
||||
"ed848affa5bf92997cd441e3a50b3616b6724df3884bd9d199b3225e0bea8a54": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "priv_key_pem",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "certificate_pem",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
}
|
||||
},
|
||||
"query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE id = 0;"
|
||||
},
|
||||
"f6d1c5ef0f9d9577bea8382318967b9deb46da75788c7fe6082b43821c22d556": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO ssh_keys (fingerprint, openssh_pubkey, created_at) VALUES ($1, $2, $3)"
|
||||
}
|
||||
}
|
||||
132
backend/src/account.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use ed25519_dalek::SecretKey;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::x509::X509;
|
||||
use sqlx::PgExecutor;
|
||||
|
||||
use crate::hostname::{generate_hostname, generate_id, Hostname};
|
||||
use crate::net::keys::Key;
|
||||
use crate::net::ssl::{generate_key, make_root_cert};
|
||||
use crate::prelude::*;
|
||||
use crate::util::crypto::ed25519_expand_key;
|
||||
|
||||
fn hash_password(password: &str) -> Result<String, Error> {
|
||||
argon2::hash_encoded(
|
||||
password.as_bytes(),
|
||||
&rand::random::<[u8; 16]>()[..],
|
||||
&argon2::Config::rfc9106_low_mem(),
|
||||
)
|
||||
.with_kind(crate::ErrorKind::PasswordHashGeneration)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AccountInfo {
|
||||
pub server_id: String,
|
||||
pub hostname: Hostname,
|
||||
pub password: String,
|
||||
pub key: Key,
|
||||
pub root_ca_key: PKey<Private>,
|
||||
pub root_ca_cert: X509,
|
||||
}
|
||||
impl AccountInfo {
|
||||
pub fn new(password: &str, start_time: SystemTime) -> Result<Self, Error> {
|
||||
let server_id = generate_id();
|
||||
let hostname = generate_hostname();
|
||||
let root_ca_key = generate_key()?;
|
||||
let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?;
|
||||
Ok(Self {
|
||||
server_id,
|
||||
hostname,
|
||||
password: hash_password(password)?,
|
||||
key: Key::new(None),
|
||||
root_ca_key,
|
||||
root_ca_cert,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn load(secrets: impl PgExecutor<'_>) -> Result<Self, Error> {
|
||||
let r = sqlx::query!("SELECT * FROM account WHERE id = 0")
|
||||
.fetch_one(secrets)
|
||||
.await?;
|
||||
|
||||
let server_id = r.server_id.unwrap_or_else(generate_id);
|
||||
let hostname = r.hostname.map(Hostname).unwrap_or_else(generate_hostname);
|
||||
let password = r.password;
|
||||
let network_key = SecretKey::try_from(r.network_key).map_err(|e| {
|
||||
Error::new(
|
||||
eyre!("expected vec of len 32, got len {}", e.len()),
|
||||
ErrorKind::ParseDbField,
|
||||
)
|
||||
})?;
|
||||
let tor_key = if let Some(k) = &r.tor_key {
|
||||
<[u8; 64]>::try_from(&k[..]).map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("expected vec of len 64, got len {}", k.len()),
|
||||
ErrorKind::ParseDbField,
|
||||
)
|
||||
})?
|
||||
} else {
|
||||
ed25519_expand_key(&network_key)
|
||||
};
|
||||
let key = Key::from_pair(None, network_key, tor_key);
|
||||
let root_ca_key = PKey::private_key_from_pem(r.root_ca_key_pem.as_bytes())?;
|
||||
let root_ca_cert = X509::from_pem(r.root_ca_cert_pem.as_bytes())?;
|
||||
|
||||
Ok(Self {
|
||||
server_id,
|
||||
hostname,
|
||||
password,
|
||||
key,
|
||||
root_ca_key,
|
||||
root_ca_cert,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn save(&self, secrets: impl PgExecutor<'_>) -> Result<(), Error> {
|
||||
let server_id = self.server_id.as_str();
|
||||
let hostname = self.hostname.0.as_str();
|
||||
let password = self.password.as_str();
|
||||
let network_key = self.key.as_bytes();
|
||||
let network_key = network_key.as_slice();
|
||||
let root_ca_key = String::from_utf8(self.root_ca_key.private_key_to_pem_pkcs8()?)?;
|
||||
let root_ca_cert = String::from_utf8(self.root_ca_cert.to_pem()?)?;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO account (
|
||||
id,
|
||||
server_id,
|
||||
hostname,
|
||||
password,
|
||||
network_key,
|
||||
root_ca_key_pem,
|
||||
root_ca_cert_pem
|
||||
) VALUES (
|
||||
0, $1, $2, $3, $4, $5, $6
|
||||
) ON CONFLICT (id) DO UPDATE SET
|
||||
server_id = EXCLUDED.server_id,
|
||||
hostname = EXCLUDED.hostname,
|
||||
password = EXCLUDED.password,
|
||||
network_key = EXCLUDED.network_key,
|
||||
root_ca_key_pem = EXCLUDED.root_ca_key_pem,
|
||||
root_ca_cert_pem = EXCLUDED.root_ca_cert_pem
|
||||
"#,
|
||||
server_id,
|
||||
hostname,
|
||||
password,
|
||||
network_key,
|
||||
root_ca_key,
|
||||
root_ca_cert,
|
||||
)
|
||||
.execute(secrets)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_password(&mut self, password: &str) -> Result<(), Error> {
|
||||
self.password = hash_password(password)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,14 @@ use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use indexmap::IndexSet;
|
||||
pub use models::ActionId;
|
||||
use models::ImageId;
|
||||
use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::config::{Config, ConfigSpec};
|
||||
use crate::context::RpcContext;
|
||||
use crate::id::ImageId;
|
||||
use crate::prelude::*;
|
||||
use crate::procedure::docker::DockerContainers;
|
||||
use crate::procedure::{PackageProcedure, ProcedureName};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
@@ -56,16 +57,16 @@ pub struct Action {
|
||||
pub input_spec: ConfigSpec,
|
||||
}
|
||||
impl Action {
|
||||
#[instrument]
|
||||
#[instrument(skip_all)]
|
||||
pub fn validate(
|
||||
&self,
|
||||
container: &Option<DockerContainers>,
|
||||
_container: &Option<DockerContainers>,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
) -> Result<(), Error> {
|
||||
self.implementation
|
||||
.validate(container, eos_version, volumes, image_ids, true)
|
||||
.validate(eos_version, volumes, image_ids, true)
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
@@ -74,7 +75,7 @@ impl Action {
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn execute(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
@@ -120,7 +121,7 @@ fn display_action_result(action_result: ActionResult, matches: &ArgMatches) {
|
||||
}
|
||||
|
||||
#[command(about = "Executes an action", display(display_action_result))]
|
||||
#[instrument(skip(ctx))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn action(
|
||||
#[context] ctx: RpcContext,
|
||||
#[arg(rename = "id")] pkg_id: PackageId,
|
||||
@@ -130,18 +131,17 @@ pub async fn action(
|
||||
#[arg(long = "format")]
|
||||
format: Option<IoFormat>,
|
||||
) -> Result<ActionResult, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let manifest = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&pkg_id)
|
||||
.and_then(|p| p.installed())
|
||||
.expect(&mut db)
|
||||
let manifest = ctx
|
||||
.db
|
||||
.peek()
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::NotFound)?
|
||||
.manifest()
|
||||
.get(&mut db)
|
||||
.await?
|
||||
.to_owned();
|
||||
.as_package_data()
|
||||
.as_idx(&pkg_id)
|
||||
.or_not_found(&pkg_id)?
|
||||
.as_installed()
|
||||
.or_not_found(&pkg_id)?
|
||||
.as_manifest()
|
||||
.de()?;
|
||||
|
||||
if let Some(action) = manifest.actions.0.get(&action_id) {
|
||||
action
|
||||
|
||||
@@ -5,7 +5,6 @@ use chrono::{DateTime, Utc};
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use josekit::jwk::Jwk;
|
||||
use patch_db::{DbHandle, LockReceipt};
|
||||
use rpc_toolkit::command;
|
||||
use rpc_toolkit::command_helpers::prelude::{RequestParts, ResponseParts};
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
@@ -17,6 +16,7 @@ use tracing::instrument;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::middleware::auth::{AsLogoutSessionId, HasLoggedOutSessions, HashSessionToken};
|
||||
use crate::middleware::encrypt::EncryptedWire;
|
||||
use crate::prelude::*;
|
||||
use crate::util::display_none;
|
||||
use crate::util::serde::{display_serializable, IoFormat};
|
||||
use crate::{ensure_code, Error, ResultExt};
|
||||
@@ -84,13 +84,13 @@ fn gen_pwd() {
|
||||
argon2::hash_encoded(
|
||||
b"testing1234",
|
||||
&rand::random::<[u8; 16]>()[..],
|
||||
&argon2::Config::default()
|
||||
&argon2::Config::rfc9106_low_mem()
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, password))]
|
||||
#[instrument(skip_all)]
|
||||
async fn cli_login(
|
||||
ctx: CliContext,
|
||||
password: Option<PasswordType>,
|
||||
@@ -145,7 +145,7 @@ where
|
||||
display(display_none),
|
||||
metadata(authenticated = false)
|
||||
)]
|
||||
#[instrument(skip(ctx, password))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn login(
|
||||
#[context] ctx: RpcContext,
|
||||
#[request] req: &RequestParts,
|
||||
@@ -160,7 +160,7 @@ pub async fn login(
|
||||
) -> Result<(), Error> {
|
||||
let password = password.unwrap_or_default().decrypt(&ctx)?;
|
||||
let mut handle = ctx.secret_store.acquire().await?;
|
||||
check_password_against_db(&mut handle, &password).await?;
|
||||
check_password_against_db(handle.as_mut(), &password).await?;
|
||||
|
||||
let hash_token = HashSessionToken::new();
|
||||
let user_agent = req.headers.get("user-agent").and_then(|h| h.to_str().ok());
|
||||
@@ -172,7 +172,7 @@ pub async fn login(
|
||||
user_agent,
|
||||
metadata,
|
||||
)
|
||||
.execute(&mut handle)
|
||||
.execute(handle.as_mut())
|
||||
.await?;
|
||||
res.headers.insert(
|
||||
"set-cookie",
|
||||
@@ -183,7 +183,7 @@ pub async fn login(
|
||||
}
|
||||
|
||||
#[command(display(display_none), metadata(authenticated = false))]
|
||||
#[instrument(skip(ctx))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn logout(
|
||||
#[context] ctx: RpcContext,
|
||||
#[request] req: &RequestParts,
|
||||
@@ -250,7 +250,7 @@ fn display_sessions(arg: SessionList, matches: &ArgMatches) {
|
||||
}
|
||||
|
||||
#[command(display(display_sessions))]
|
||||
#[instrument(skip(ctx))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn list(
|
||||
#[context] ctx: RpcContext,
|
||||
#[request] req: &RequestParts,
|
||||
@@ -263,7 +263,7 @@ pub async fn list(
|
||||
sessions: sqlx::query!(
|
||||
"SELECT * FROM session WHERE logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP"
|
||||
)
|
||||
.fetch_all(&mut ctx.secret_store.acquire().await?)
|
||||
.fetch_all(ctx.secret_store.acquire().await?.as_mut())
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
@@ -296,7 +296,7 @@ impl AsLogoutSessionId for KillSessionId {
|
||||
}
|
||||
|
||||
#[command(display(display_none))]
|
||||
#[instrument(skip(ctx))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn kill(
|
||||
#[context] ctx: RpcContext,
|
||||
#[arg(parse(parse_comma_separated))] ids: Vec<String>,
|
||||
@@ -305,7 +305,7 @@ pub async fn kill(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, old_password, new_password))]
|
||||
#[instrument(skip_all)]
|
||||
async fn cli_reset_password(
|
||||
ctx: CliContext,
|
||||
old_password: Option<PasswordType>,
|
||||
@@ -343,58 +343,12 @@ async fn cli_reset_password(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct SetPasswordReceipt(LockReceipt<String, ()>);
|
||||
impl SetPasswordReceipt {
|
||||
pub async fn new<Db: DbHandle>(db: &mut Db) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
let password_hash = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.password_hash()
|
||||
.make_locker(patch_db::LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| Ok(Self(password_hash.verify(skeleton_key)?))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_password<Db: DbHandle, Ex>(
|
||||
db: &mut Db,
|
||||
receipt: &SetPasswordReceipt,
|
||||
secrets: &mut Ex,
|
||||
password: &str,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: Executor<'a, Database = Postgres>,
|
||||
{
|
||||
let password = argon2::hash_encoded(
|
||||
password.as_bytes(),
|
||||
&rand::random::<[u8; 16]>()[..],
|
||||
&argon2::Config::default(),
|
||||
)
|
||||
.with_kind(crate::ErrorKind::PasswordHashGeneration)?;
|
||||
|
||||
sqlx::query!("UPDATE account SET password = $1", password,)
|
||||
.execute(secrets)
|
||||
.await?;
|
||||
|
||||
receipt.0.set(db, password).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command(
|
||||
rename = "reset-password",
|
||||
custom_cli(cli_reset_password(async, context(CliContext))),
|
||||
display(display_none)
|
||||
)]
|
||||
#[instrument(skip(ctx, old_password, new_password))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn reset_password(
|
||||
#[context] ctx: RpcContext,
|
||||
#[arg(rename = "old-password")] old_password: Option<PasswordType>,
|
||||
@@ -403,20 +357,33 @@ pub async fn reset_password(
|
||||
let old_password = old_password.unwrap_or_default().decrypt(&ctx)?;
|
||||
let new_password = new_password.unwrap_or_default().decrypt(&ctx)?;
|
||||
|
||||
let mut secrets = ctx.secret_store.acquire().await?;
|
||||
check_password_against_db(&mut secrets, &old_password).await?;
|
||||
|
||||
let mut db = ctx.db.handle();
|
||||
|
||||
let set_password_receipt = SetPasswordReceipt::new(&mut db).await?;
|
||||
|
||||
set_password(&mut db, &set_password_receipt, &mut secrets, &new_password).await?;
|
||||
|
||||
Ok(())
|
||||
let mut account = ctx.account.write().await;
|
||||
if !argon2::verify_encoded(&account.password, old_password.as_bytes())
|
||||
.with_kind(crate::ErrorKind::IncorrectPassword)?
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("Incorrect Password"),
|
||||
crate::ErrorKind::IncorrectPassword,
|
||||
));
|
||||
}
|
||||
account.set_password(&new_password)?;
|
||||
account.save(&ctx.secret_store).await?;
|
||||
let account_password = &account.password;
|
||||
ctx.db
|
||||
.mutate(|d| {
|
||||
d.as_server_info_mut()
|
||||
.as_password_hash_mut()
|
||||
.ser(account_password)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[command(rename = "get-pubkey", display(display_none))]
|
||||
#[instrument(skip(ctx))]
|
||||
#[command(
|
||||
rename = "get-pubkey",
|
||||
display(display_none),
|
||||
metadata(authenticated = false)
|
||||
)]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get_pubkey(#[context] ctx: RpcContext) -> Result<Jwk, RpcError> {
|
||||
let secret = ctx.as_ref().clone();
|
||||
let pub_key = secret.to_public_key()?;
|
||||
|
||||
@@ -1,122 +1,42 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::PathBuf;
|
||||
use std::collections::BTreeMap;
|
||||
use std::panic::UnwindSafe;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Utc;
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::AtomicFile;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::x509::X509;
|
||||
use patch_db::{DbHandle, LockType, PatchDbHandle};
|
||||
use imbl::OrdSet;
|
||||
use models::Version;
|
||||
use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use torut::onion::TorSecretKeyV3;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::target::BackupTargetId;
|
||||
use super::PackageBackupReport;
|
||||
use crate::auth::check_password_against_db;
|
||||
use crate::backup::os::OsBackup;
|
||||
use crate::backup::{BackupReport, ServerBackupReport};
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::BackupProgress;
|
||||
use crate::db::package::get_packages;
|
||||
use crate::disk::mount::backup::BackupMountGuard;
|
||||
use crate::disk::mount::filesystem::ReadWrite;
|
||||
use crate::disk::mount::guard::TmpMountGuard;
|
||||
use crate::manager::BackupReturn;
|
||||
use crate::notifications::NotificationLevel;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::status::MainStatus;
|
||||
use crate::util::display_none;
|
||||
use crate::util::io::dir_copy;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::version::VersionT;
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OsBackup {
|
||||
pub tor_key: TorSecretKeyV3,
|
||||
pub root_ca_key: PKey<Private>,
|
||||
pub root_ca_cert: X509,
|
||||
pub ui: Value,
|
||||
}
|
||||
impl<'de> Deserialize<'de> for OsBackup {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename = "kebab-case")]
|
||||
struct OsBackupDe {
|
||||
tor_key: String,
|
||||
root_ca_key: String,
|
||||
root_ca_cert: String,
|
||||
ui: Value,
|
||||
}
|
||||
let int = OsBackupDe::deserialize(deserializer)?;
|
||||
let key_vec = base32::decode(base32::Alphabet::RFC4648 { padding: true }, &int.tor_key)
|
||||
.ok_or_else(|| {
|
||||
serde::de::Error::invalid_value(
|
||||
serde::de::Unexpected::Str(&int.tor_key),
|
||||
&"an RFC4648 encoded string",
|
||||
)
|
||||
})?;
|
||||
if key_vec.len() != 64 {
|
||||
return Err(serde::de::Error::invalid_value(
|
||||
serde::de::Unexpected::Str(&int.tor_key),
|
||||
&"a 64 byte value encoded as an RFC4648 string",
|
||||
));
|
||||
}
|
||||
let mut key_slice = [0; 64];
|
||||
key_slice.clone_from_slice(&key_vec);
|
||||
Ok(OsBackup {
|
||||
tor_key: TorSecretKeyV3::from(key_slice),
|
||||
root_ca_key: PKey::<Private>::private_key_from_pem(int.root_ca_key.as_bytes())
|
||||
.map_err(serde::de::Error::custom)?,
|
||||
root_ca_cert: X509::from_pem(int.root_ca_cert.as_bytes())
|
||||
.map_err(serde::de::Error::custom)?,
|
||||
ui: int.ui,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Serialize for OsBackup {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename = "kebab-case")]
|
||||
struct OsBackupSer<'a> {
|
||||
tor_key: String,
|
||||
root_ca_key: String,
|
||||
root_ca_cert: String,
|
||||
ui: &'a Value,
|
||||
}
|
||||
OsBackupSer {
|
||||
tor_key: base32::encode(
|
||||
base32::Alphabet::RFC4648 { padding: true },
|
||||
&self.tor_key.as_bytes(),
|
||||
),
|
||||
root_ca_key: String::from_utf8(
|
||||
self.root_ca_key
|
||||
.private_key_to_pem_pkcs8()
|
||||
.map_err(serde::ser::Error::custom)?,
|
||||
)
|
||||
.map_err(serde::ser::Error::custom)?,
|
||||
root_ca_cert: String::from_utf8(
|
||||
self.root_ca_cert
|
||||
.to_pem()
|
||||
.map_err(serde::ser::Error::custom)?,
|
||||
)
|
||||
.map_err(serde::ser::Error::custom)?,
|
||||
ui: &self.ui,
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result<BTreeSet<PackageId>, Error> {
|
||||
fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result<OrdSet<PackageId>, Error> {
|
||||
arg.split(',')
|
||||
.map(|s| s.trim().parse().map_err(Error::from))
|
||||
.map(|s| s.trim().parse::<PackageId>().map_err(Error::from))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -133,55 +53,52 @@ pub async fn backup_all(
|
||||
long = "package-ids",
|
||||
parse(parse_comma_separated)
|
||||
)]
|
||||
package_ids: Option<BTreeSet<PackageId>>,
|
||||
package_ids: Option<OrdSet<PackageId>>,
|
||||
#[arg] password: crate::auth::PasswordType,
|
||||
) -> Result<(), Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let db = ctx.db.peek().await;
|
||||
let old_password_decrypted = old_password
|
||||
.as_ref()
|
||||
.unwrap_or(&password)
|
||||
.clone()
|
||||
.decrypt(&ctx)?;
|
||||
let password = password.decrypt(&ctx)?;
|
||||
check_password_against_db(&mut ctx.secret_store.acquire().await?, &password).await?;
|
||||
check_password_against_db(ctx.secret_store.acquire().await?.as_mut(), &password).await?;
|
||||
let fs = target_id
|
||||
.load(&mut ctx.secret_store.acquire().await?)
|
||||
.load(ctx.secret_store.acquire().await?.as_mut())
|
||||
.await?;
|
||||
let mut backup_guard = BackupMountGuard::mount(
|
||||
TmpMountGuard::mount(&fs, ReadWrite).await?,
|
||||
&old_password_decrypted,
|
||||
)
|
||||
.await?;
|
||||
let all_packages = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.get(&mut db)
|
||||
.await?
|
||||
.0
|
||||
.keys()
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let package_ids = package_ids.unwrap_or(all_packages);
|
||||
let package_ids = if let Some(ids) = package_ids {
|
||||
ids.into_iter()
|
||||
.flat_map(|package_id| {
|
||||
let version = db
|
||||
.as_package_data()
|
||||
.as_idx(&package_id)?
|
||||
.as_manifest()
|
||||
.as_version()
|
||||
.de()
|
||||
.ok()?;
|
||||
Some((package_id, version))
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
get_packages(db.clone())?.into_iter().collect()
|
||||
};
|
||||
if old_password.is_some() {
|
||||
backup_guard.change_password(&password)?;
|
||||
}
|
||||
assure_backing_up(&mut db, &package_ids).await?;
|
||||
assure_backing_up(&ctx.db, &package_ids).await?;
|
||||
tokio::task::spawn(async move {
|
||||
let backup_res = perform_backup(&ctx, &mut db, backup_guard, &package_ids).await;
|
||||
let backup_progress = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.status_info()
|
||||
.backup_progress();
|
||||
backup_progress
|
||||
.clone()
|
||||
.lock(&mut db, LockType::Write)
|
||||
.await
|
||||
.expect("failed to lock server status");
|
||||
let backup_res = perform_backup(&ctx, backup_guard, &package_ids).await;
|
||||
match backup_res {
|
||||
Ok(report) if report.iter().all(|(_, rep)| rep.error.is_none()) => ctx
|
||||
.notification_manager
|
||||
.notify(
|
||||
&mut db,
|
||||
ctx.db.clone(),
|
||||
None,
|
||||
NotificationLevel::Success,
|
||||
"Backup Complete".to_owned(),
|
||||
@@ -191,7 +108,10 @@ pub async fn backup_all(
|
||||
attempted: true,
|
||||
error: None,
|
||||
},
|
||||
packages: report,
|
||||
packages: report
|
||||
.into_iter()
|
||||
.map(|((package_id, _), value)| (package_id, value))
|
||||
.collect(),
|
||||
},
|
||||
None,
|
||||
)
|
||||
@@ -200,7 +120,7 @@ pub async fn backup_all(
|
||||
Ok(report) => ctx
|
||||
.notification_manager
|
||||
.notify(
|
||||
&mut db,
|
||||
ctx.db.clone(),
|
||||
None,
|
||||
NotificationLevel::Warning,
|
||||
"Backup Complete".to_owned(),
|
||||
@@ -210,7 +130,10 @@ pub async fn backup_all(
|
||||
attempted: true,
|
||||
error: None,
|
||||
},
|
||||
packages: report,
|
||||
packages: report
|
||||
.into_iter()
|
||||
.map(|((package_id, _), value)| (package_id, value))
|
||||
.collect(),
|
||||
},
|
||||
None,
|
||||
)
|
||||
@@ -221,7 +144,7 @@ pub async fn backup_all(
|
||||
tracing::debug!("{:?}", e);
|
||||
ctx.notification_manager
|
||||
.notify(
|
||||
&mut db,
|
||||
ctx.db.clone(),
|
||||
None,
|
||||
NotificationLevel::Error,
|
||||
"Backup Failed".to_owned(),
|
||||
@@ -239,220 +162,150 @@ pub async fn backup_all(
|
||||
.expect("failed to send notification");
|
||||
}
|
||||
}
|
||||
backup_progress
|
||||
.delete(&mut db)
|
||||
.await
|
||||
.expect("failed to change server status");
|
||||
ctx.db
|
||||
.mutate(|v| {
|
||||
v.as_server_info_mut()
|
||||
.as_status_info_mut()
|
||||
.as_backup_progress_mut()
|
||||
.ser(&None)
|
||||
})
|
||||
.await?;
|
||||
Ok::<(), Error>(())
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(db, packages))]
|
||||
async fn assure_backing_up(
|
||||
db: &mut PatchDbHandle,
|
||||
packages: impl IntoIterator<Item = &PackageId>,
|
||||
db: &PatchDb,
|
||||
packages: impl IntoIterator<Item = &(PackageId, Version)> + UnwindSafe + Send,
|
||||
) -> Result<(), Error> {
|
||||
let mut tx = db.begin().await?;
|
||||
let mut backing_up = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.status_info()
|
||||
.backup_progress()
|
||||
.get_mut(&mut tx)
|
||||
.await?;
|
||||
|
||||
if backing_up
|
||||
.iter()
|
||||
.flat_map(|x| x.values())
|
||||
.fold(false, |acc, x| {
|
||||
if !x.complete {
|
||||
return true;
|
||||
}
|
||||
acc
|
||||
})
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("Server is already backing up!"),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
*backing_up = Some(
|
||||
packages
|
||||
.into_iter()
|
||||
.map(|x| (x.clone(), BackupProgress { complete: false }))
|
||||
.collect(),
|
||||
);
|
||||
backing_up.save(&mut tx).await?;
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
db.mutate(|v| {
|
||||
let backing_up = v
|
||||
.as_server_info_mut()
|
||||
.as_status_info_mut()
|
||||
.as_backup_progress_mut();
|
||||
if backing_up
|
||||
.clone()
|
||||
.de()?
|
||||
.iter()
|
||||
.flat_map(|x| x.values())
|
||||
.fold(false, |acc, x| {
|
||||
if !x.complete {
|
||||
return true;
|
||||
}
|
||||
acc
|
||||
})
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("Server is already backing up!"),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
backing_up.ser(&Some(
|
||||
packages
|
||||
.into_iter()
|
||||
.map(|(x, _)| (x.clone(), BackupProgress { complete: false }))
|
||||
.collect(),
|
||||
))?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, db, backup_guard))]
|
||||
async fn perform_backup<Db: DbHandle>(
|
||||
#[instrument(skip(ctx, backup_guard))]
|
||||
async fn perform_backup(
|
||||
ctx: &RpcContext,
|
||||
mut db: Db,
|
||||
mut backup_guard: BackupMountGuard<TmpMountGuard>,
|
||||
package_ids: &BTreeSet<PackageId>,
|
||||
) -> Result<BTreeMap<PackageId, PackageBackupReport>, Error> {
|
||||
backup_guard: BackupMountGuard<TmpMountGuard>,
|
||||
package_ids: &OrdSet<(PackageId, Version)>,
|
||||
) -> Result<BTreeMap<(PackageId, Version), PackageBackupReport>, Error> {
|
||||
let mut backup_report = BTreeMap::new();
|
||||
let backup_guard = Arc::new(Mutex::new(backup_guard));
|
||||
|
||||
for package_id in crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.keys(&mut db)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|id| package_ids.contains(id))
|
||||
{
|
||||
let mut tx = db.begin().await?; // for lock scope
|
||||
let installed_model = if let Some(installed_model) = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&package_id)
|
||||
.and_then(|m| m.installed())
|
||||
.check(&mut tx)
|
||||
.await?
|
||||
for package_id in package_ids {
|
||||
let (response, _report) = match ctx
|
||||
.managers
|
||||
.get(package_id)
|
||||
.await
|
||||
.ok_or_else(|| Error::new(eyre!("Manager not found"), ErrorKind::InvalidRequest))?
|
||||
.backup(backup_guard.clone())
|
||||
.await
|
||||
{
|
||||
installed_model
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let main_status_model = installed_model.clone().status().main();
|
||||
|
||||
main_status_model.lock(&mut tx, LockType::Write).await?;
|
||||
let (started, health) = match main_status_model.get(&mut tx).await?.into_owned() {
|
||||
MainStatus::Starting { .. } => (Some(Utc::now()), Default::default()),
|
||||
MainStatus::Running { started, health } => (Some(started), health.clone()),
|
||||
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
||||
(None, Default::default())
|
||||
BackupReturn::Ran { report, res } => (res, report),
|
||||
BackupReturn::AlreadyRunning(report) => {
|
||||
backup_report.insert(package_id.clone(), report);
|
||||
continue;
|
||||
}
|
||||
MainStatus::BackingUp { .. } => {
|
||||
BackupReturn::Error(error) => {
|
||||
tracing::warn!("Backup thread error");
|
||||
tracing::debug!("{error:?}");
|
||||
backup_report.insert(
|
||||
package_id,
|
||||
package_id.clone(),
|
||||
PackageBackupReport {
|
||||
error: Some(
|
||||
"Can't do backup because service is in a backing up state".to_owned(),
|
||||
),
|
||||
error: Some("Backup thread error".to_owned()),
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
main_status_model
|
||||
.put(
|
||||
&mut tx,
|
||||
&MainStatus::BackingUp {
|
||||
started,
|
||||
health: health.clone(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
tx.save().await?; // drop locks
|
||||
|
||||
let manifest = installed_model.clone().manifest().get(&mut db).await?;
|
||||
|
||||
ctx.managers
|
||||
.get(&(manifest.id.clone(), manifest.version.clone()))
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest)
|
||||
})?
|
||||
.synchronize()
|
||||
.await;
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
installed_model.lock(&mut tx, LockType::Write).await?;
|
||||
|
||||
let guard = backup_guard.mount_package_backup(&package_id).await?;
|
||||
let res = manifest
|
||||
.backup
|
||||
.create(
|
||||
ctx,
|
||||
&mut tx,
|
||||
&package_id,
|
||||
&manifest.title,
|
||||
&manifest.version,
|
||||
&manifest.interfaces,
|
||||
&manifest.volumes,
|
||||
)
|
||||
.await;
|
||||
guard.unmount().await?;
|
||||
backup_report.insert(
|
||||
package_id.clone(),
|
||||
PackageBackupReport {
|
||||
error: res.as_ref().err().map(|e| e.to_string()),
|
||||
error: response.as_ref().err().map(|e| e.to_string()),
|
||||
},
|
||||
);
|
||||
|
||||
if let Ok(pkg_meta) = res {
|
||||
installed_model
|
||||
.last_backup()
|
||||
.put(&mut tx, &Some(pkg_meta.timestamp))
|
||||
.await?;
|
||||
if let Ok(pkg_meta) = response {
|
||||
backup_guard
|
||||
.lock()
|
||||
.await
|
||||
.metadata
|
||||
.package_backups
|
||||
.insert(package_id.clone(), pkg_meta);
|
||||
.insert(package_id.0.clone(), pkg_meta);
|
||||
}
|
||||
|
||||
main_status_model
|
||||
.put(
|
||||
&mut tx,
|
||||
&match started {
|
||||
Some(started) => MainStatus::Running { started, health },
|
||||
None => MainStatus::Stopped,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut backup_progress = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.status_info()
|
||||
.backup_progress()
|
||||
.get_mut(&mut tx)
|
||||
.await?;
|
||||
if backup_progress.is_none() {
|
||||
*backup_progress = Some(Default::default());
|
||||
}
|
||||
if let Some(mut backup_progress) = backup_progress
|
||||
.as_mut()
|
||||
.and_then(|bp| bp.get_mut(&package_id))
|
||||
{
|
||||
(*backup_progress).complete = true;
|
||||
}
|
||||
backup_progress.save(&mut tx).await?;
|
||||
tx.save().await?;
|
||||
}
|
||||
|
||||
crate::db::DatabaseModel::new()
|
||||
.lock(&mut db, LockType::Write)
|
||||
.await?;
|
||||
let ui = ctx.db.peek().await.into_ui().de()?;
|
||||
|
||||
let (root_ca_key, root_ca_cert) = ctx.net_controller.ssl.export_root_ca().await?;
|
||||
let mut os_backup_file = AtomicFile::new(
|
||||
backup_guard.as_ref().join("os-backup.cbor"),
|
||||
backup_guard.lock().await.as_ref().join("os-backup.cbor"),
|
||||
None::<PathBuf>,
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
os_backup_file
|
||||
.write_all(
|
||||
&IoFormat::Cbor.to_vec(&OsBackup {
|
||||
tor_key: ctx.net_controller.tor.embassyd_tor_key().await,
|
||||
root_ca_key,
|
||||
root_ca_cert,
|
||||
ui: crate::db::DatabaseModel::new()
|
||||
.ui()
|
||||
.get(&mut db)
|
||||
.await?
|
||||
.into_owned(),
|
||||
})?,
|
||||
)
|
||||
.write_all(&IoFormat::Cbor.to_vec(&OsBackup {
|
||||
account: ctx.account.read().await.clone(),
|
||||
ui,
|
||||
})?)
|
||||
.await?;
|
||||
os_backup_file
|
||||
.save()
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
|
||||
let luks_folder_old = backup_guard.lock().await.as_ref().join("luks.old");
|
||||
if tokio::fs::metadata(&luks_folder_old).await.is_ok() {
|
||||
tokio::fs::remove_dir_all(&luks_folder_old).await?;
|
||||
}
|
||||
let luks_folder_bak = backup_guard.lock().await.as_ref().join("luks");
|
||||
if tokio::fs::metadata(&luks_folder_bak).await.is_ok() {
|
||||
tokio::fs::rename(&luks_folder_bak, &luks_folder_old).await?;
|
||||
}
|
||||
let luks_folder = Path::new("/media/embassy/config/luks");
|
||||
if tokio::fs::metadata(&luks_folder).await.is_ok() {
|
||||
dir_copy(&luks_folder, &luks_folder_bak, None).await?;
|
||||
}
|
||||
|
||||
let timestamp = Some(Utc::now());
|
||||
let mut backup_guard = Arc::try_unwrap(backup_guard)
|
||||
.map_err(|_err| {
|
||||
Error::new(
|
||||
eyre!("Backup guard could not ensure that the others where dropped"),
|
||||
ErrorKind::Unknown,
|
||||
)
|
||||
})?
|
||||
.into_inner();
|
||||
|
||||
backup_guard.unencrypted_metadata.version = crate::version::Current::new().semver().into();
|
||||
backup_guard.unencrypted_metadata.full = true;
|
||||
@@ -461,10 +314,9 @@ async fn perform_backup<Db: DbHandle>(
|
||||
|
||||
backup_guard.save_and_unmount().await?;
|
||||
|
||||
crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.last_backup()
|
||||
.put(&mut db, ×tamp)
|
||||
ctx.db
|
||||
.mutate(|v| v.as_server_info_mut().as_last_backup_mut().ser(×tamp))
|
||||
.await?;
|
||||
|
||||
Ok(backup_report)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::AtomicFile;
|
||||
use patch_db::{DbHandle, HasModel};
|
||||
use models::{ImageId, OptionExt};
|
||||
use reqwest::Url;
|
||||
use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -14,20 +15,22 @@ use tracing::instrument;
|
||||
|
||||
use self::target::PackageBackupInfo;
|
||||
use crate::context::RpcContext;
|
||||
use crate::dependencies::reconfigure_dependents_with_live_pointers;
|
||||
use crate::id::ImageId;
|
||||
use crate::install::PKG_ARCHIVE_DIR;
|
||||
use crate::net::interface::{InterfaceId, Interfaces};
|
||||
use crate::manager::manager_seed::ManagerSeed;
|
||||
use crate::net::interface::InterfaceId;
|
||||
use crate::net::keys::Key;
|
||||
use crate::prelude::*;
|
||||
use crate::procedure::docker::DockerContainers;
|
||||
use crate::procedure::{NoOutput, PackageProcedure, ProcedureName};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::util::serde::{Base32, Base64, IoFormat};
|
||||
use crate::util::Version;
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::volume::{backup_dir, Volume, VolumeId, Volumes, BACKUP_DIR};
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
pub mod backup_bulk;
|
||||
pub mod os;
|
||||
pub mod restore;
|
||||
pub mod target;
|
||||
|
||||
@@ -45,7 +48,7 @@ pub struct ServerBackupReport {
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct PackageBackupReport {
|
||||
error: Option<String>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[command(subcommands(backup_bulk::backup_all, target::target))]
|
||||
@@ -61,11 +64,15 @@ pub fn package_backup() -> Result<(), Error> {
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct BackupMetadata {
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub tor_keys: BTreeMap<InterfaceId, String>,
|
||||
#[serde(default)]
|
||||
pub network_keys: BTreeMap<InterfaceId, Base64<[u8; 32]>>,
|
||||
#[serde(default)]
|
||||
pub tor_keys: BTreeMap<InterfaceId, Base32<[u8; 64]>>, // DEPRECATED
|
||||
pub marketplace_url: Option<Url>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct BackupActions {
|
||||
pub create: PackageProcedure,
|
||||
pub restore: PackageProcedure,
|
||||
@@ -73,34 +80,29 @@ pub struct BackupActions {
|
||||
impl BackupActions {
|
||||
pub fn validate(
|
||||
&self,
|
||||
container: &Option<DockerContainers>,
|
||||
_container: &Option<DockerContainers>,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
) -> Result<(), Error> {
|
||||
self.create
|
||||
.validate(container, eos_version, volumes, image_ids, false)
|
||||
.validate(eos_version, volumes, image_ids, false)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Create"))?;
|
||||
self.restore
|
||||
.validate(container, eos_version, volumes, image_ids, false)
|
||||
.validate(eos_version, volumes, image_ids, false)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Restore"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, db))]
|
||||
pub async fn create<Db: DbHandle>(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
pkg_id: &PackageId,
|
||||
pkg_title: &str,
|
||||
pkg_version: &Version,
|
||||
interfaces: &Interfaces,
|
||||
volumes: &Volumes,
|
||||
) -> Result<PackageBackupInfo, Error> {
|
||||
let mut volumes = volumes.to_readonly();
|
||||
#[instrument(skip_all)]
|
||||
pub async fn create(&self, seed: Arc<ManagerSeed>) -> Result<PackageBackupInfo, Error> {
|
||||
let manifest = &seed.manifest;
|
||||
let mut volumes = seed.manifest.volumes.to_readonly();
|
||||
let ctx = &seed.ctx;
|
||||
let pkg_id = &manifest.id;
|
||||
let pkg_version = &manifest.version;
|
||||
volumes.insert(VolumeId::Backup, Volume::Backup { readonly: false });
|
||||
let backup_dir = backup_dir(pkg_id);
|
||||
let backup_dir = backup_dir(&manifest.id);
|
||||
if tokio::fs::metadata(&backup_dir).await.is_err() {
|
||||
tokio::fs::create_dir_all(&backup_dir).await?
|
||||
}
|
||||
@@ -117,29 +119,29 @@ impl BackupActions {
|
||||
.await?
|
||||
.map_err(|e| eyre!("{}", e.1))
|
||||
.with_kind(crate::ErrorKind::Backup)?;
|
||||
let tor_keys = interfaces
|
||||
.tor_keys(&mut ctx.secret_store.acquire().await?, pkg_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|(id, key)| {
|
||||
(
|
||||
id,
|
||||
base32::encode(base32::Alphabet::RFC4648 { padding: true }, &key.as_bytes()),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let marketplace_url = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(pkg_id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(db)
|
||||
.await?
|
||||
.marketplace_url()
|
||||
.get(db)
|
||||
.await?
|
||||
.into_owned();
|
||||
let (network_keys, tor_keys): (Vec<_>, Vec<_>) =
|
||||
Key::for_package(&ctx.secret_store, pkg_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|k| {
|
||||
let interface = k.interface().map(|(_, i)| i)?;
|
||||
Some((
|
||||
(interface.clone(), Base64(k.as_bytes())),
|
||||
(interface, Base32(k.tor_key().as_bytes())),
|
||||
))
|
||||
})
|
||||
.unzip();
|
||||
let marketplace_url = ctx
|
||||
.db
|
||||
.peek()
|
||||
.await
|
||||
.as_package_data()
|
||||
.as_idx(&pkg_id)
|
||||
.or_not_found(pkg_id)?
|
||||
.expect_as_installed()?
|
||||
.as_installed()
|
||||
.as_marketplace_url()
|
||||
.de()?;
|
||||
let tmp_path = Path::new(BACKUP_DIR)
|
||||
.join(pkg_id)
|
||||
.join(format!("{}.s9pk", pkg_id));
|
||||
@@ -167,9 +169,12 @@ impl BackupActions {
|
||||
let mut outfile = AtomicFile::new(&metadata_path, None::<PathBuf>)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
let network_keys = network_keys.into_iter().collect();
|
||||
let tor_keys = tor_keys.into_iter().collect();
|
||||
outfile
|
||||
.write_all(&IoFormat::Cbor.to_vec(&BackupMetadata {
|
||||
timestamp,
|
||||
network_keys,
|
||||
tor_keys,
|
||||
marketplace_url,
|
||||
})?)
|
||||
@@ -177,22 +182,20 @@ impl BackupActions {
|
||||
outfile.save().await.with_kind(ErrorKind::Filesystem)?;
|
||||
Ok(PackageBackupInfo {
|
||||
os_version: Current::new().semver().into(),
|
||||
title: pkg_title.to_owned(),
|
||||
title: manifest.title.clone(),
|
||||
version: pkg_version.clone(),
|
||||
timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, db))]
|
||||
pub async fn restore<Db: DbHandle>(
|
||||
#[instrument(skip_all)]
|
||||
pub async fn restore(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
interfaces: &Interfaces,
|
||||
volumes: &Volumes,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<Option<Url>, Error> {
|
||||
let mut volumes = volumes.clone();
|
||||
volumes.insert(VolumeId::Backup, Volume::Backup { readonly: true });
|
||||
self.restore
|
||||
@@ -217,32 +220,7 @@ impl BackupActions {
|
||||
)
|
||||
})?,
|
||||
)?;
|
||||
let pde = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(pkg_id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(db)
|
||||
.await?;
|
||||
pde.marketplace_url()
|
||||
.put(db, &metadata.marketplace_url)
|
||||
.await?;
|
||||
|
||||
let entry = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(pkg_id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(db)
|
||||
.await?
|
||||
.get(db)
|
||||
.await?;
|
||||
|
||||
let receipts = crate::config::ConfigReceipts::new(db).await?;
|
||||
reconfigure_dependents_with_live_pointers(ctx, db, &receipts, &entry).await?;
|
||||
|
||||
Ok(())
|
||||
Ok(metadata.marketplace_url)
|
||||
}
|
||||
}
|
||||
|
||||
122
backend/src/backup/os.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
use openssl::pkey::PKey;
|
||||
use openssl::x509::X509;
|
||||
use patch_db::Value;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::account::AccountInfo;
|
||||
use crate::hostname::{generate_hostname, generate_id, Hostname};
|
||||
use crate::net::keys::Key;
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::Base64;
|
||||
|
||||
pub struct OsBackup {
|
||||
pub account: AccountInfo,
|
||||
pub ui: Value,
|
||||
}
|
||||
impl<'de> Deserialize<'de> for OsBackup {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let tagged = OsBackupSerDe::deserialize(deserializer)?;
|
||||
match tagged.version {
|
||||
0 => patch_db::value::from_value::<OsBackupV0>(tagged.rest)
|
||||
.map_err(serde::de::Error::custom)?
|
||||
.project()
|
||||
.map_err(serde::de::Error::custom),
|
||||
1 => patch_db::value::from_value::<OsBackupV1>(tagged.rest)
|
||||
.map_err(serde::de::Error::custom)?
|
||||
.project()
|
||||
.map_err(serde::de::Error::custom),
|
||||
v => Err(serde::de::Error::custom(&format!(
|
||||
"Unknown backup version {v}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Serialize for OsBackup {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
OsBackupSerDe {
|
||||
version: 1,
|
||||
rest: patch_db::value::to_value(
|
||||
&OsBackupV1::unproject(self).map_err(serde::ser::Error::custom)?,
|
||||
)
|
||||
.map_err(serde::ser::Error::custom)?,
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct OsBackupSerDe {
|
||||
#[serde(default)]
|
||||
version: usize,
|
||||
#[serde(flatten)]
|
||||
rest: Value,
|
||||
}
|
||||
|
||||
/// V0
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename = "kebab-case")]
|
||||
struct OsBackupV0 {
|
||||
// tor_key: Base32<[u8; 64]>,
|
||||
root_ca_key: String, // PEM Encoded OpenSSL Key
|
||||
root_ca_cert: String, // PEM Encoded OpenSSL X509 Certificate
|
||||
ui: Value, // JSON Value
|
||||
}
|
||||
impl OsBackupV0 {
|
||||
fn project(self) -> Result<OsBackup, Error> {
|
||||
Ok(OsBackup {
|
||||
account: AccountInfo {
|
||||
server_id: generate_id(),
|
||||
hostname: generate_hostname(),
|
||||
password: Default::default(),
|
||||
key: Key::new(None),
|
||||
root_ca_key: PKey::private_key_from_pem(self.root_ca_key.as_bytes())?,
|
||||
root_ca_cert: X509::from_pem(self.root_ca_cert.as_bytes())?,
|
||||
},
|
||||
ui: self.ui,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// V1
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename = "kebab-case")]
|
||||
struct OsBackupV1 {
|
||||
server_id: String, // uuidv4
|
||||
hostname: String, // embassy-<adjective>-<noun>
|
||||
net_key: Base64<[u8; 32]>, // Ed25519 Secret Key
|
||||
root_ca_key: String, // PEM Encoded OpenSSL Key
|
||||
root_ca_cert: String, // PEM Encoded OpenSSL X509 Certificate
|
||||
ui: Value, // JSON Value
|
||||
// TODO add more
|
||||
}
|
||||
impl OsBackupV1 {
|
||||
fn project(self) -> Result<OsBackup, Error> {
|
||||
Ok(OsBackup {
|
||||
account: AccountInfo {
|
||||
server_id: self.server_id,
|
||||
hostname: Hostname(self.hostname),
|
||||
password: Default::default(),
|
||||
key: Key::from_bytes(None, self.net_key.0),
|
||||
root_ca_key: PKey::private_key_from_pem(self.root_ca_key.as_bytes())?,
|
||||
root_ca_cert: X509::from_pem(self.root_ca_cert.as_bytes())?,
|
||||
},
|
||||
ui: self.ui,
|
||||
})
|
||||
}
|
||||
fn unproject(backup: &OsBackup) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
server_id: backup.account.server_id.clone(),
|
||||
hostname: backup.account.hostname.0.clone(),
|
||||
net_key: Base64(backup.account.key.as_bytes()),
|
||||
root_ca_key: String::from_utf8(backup.account.root_ca_key.private_key_to_pem_pkcs8()?)?,
|
||||
root_ca_cert: String::from_utf8(backup.account.root_ca_cert.to_pem()?)?,
|
||||
ui: backup.ui.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,34 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{collections::BTreeMap, pin::Pin};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::{future::BoxFuture, stream, Future};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{stream, FutureExt, StreamExt};
|
||||
use openssl::x509::X509;
|
||||
use patch_db::{DbHandle, PatchDbHandle};
|
||||
use rpc_toolkit::command;
|
||||
use sqlx::Connection;
|
||||
use tokio::fs::File;
|
||||
use tokio::task::JoinHandle;
|
||||
use torut::onion::OnionAddressV3;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::target::BackupTargetId;
|
||||
use crate::backup::backup_bulk::OsBackup;
|
||||
use crate::backup::os::OsBackup;
|
||||
use crate::backup::BackupMetadata;
|
||||
use crate::context::rpc::RpcContextConfig;
|
||||
use crate::context::{RpcContext, SetupContext};
|
||||
use crate::db::model::{PackageDataEntry, StaticFiles};
|
||||
use crate::db::model::{PackageDataEntry, PackageDataEntryRestoring, StaticFiles};
|
||||
use crate::disk::mount::backup::{BackupMountGuard, PackageBackupMountGuard};
|
||||
use crate::disk::mount::filesystem::ReadWrite;
|
||||
use crate::disk::mount::guard::TmpMountGuard;
|
||||
use crate::hostname::{get_hostname, Hostname};
|
||||
use crate::hostname::Hostname;
|
||||
use crate::init::init;
|
||||
use crate::install::progress::InstallProgress;
|
||||
use crate::install::{download_install_s9pk, PKG_PUBLIC_DIR};
|
||||
use crate::net::ssl::SslManager;
|
||||
use crate::notifications::NotificationLevel;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::s9pk::reader::S9pkReader;
|
||||
use crate::setup::SetupStatus;
|
||||
@@ -38,7 +36,6 @@ use crate::util::display_none;
|
||||
use crate::util::io::dir_size;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::volume::{backup_dir, BACKUP_DIR, PKG_VOLUME_DIR};
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result<Vec<PackageId>, Error> {
|
||||
arg.split(',')
|
||||
@@ -54,26 +51,24 @@ pub async fn restore_packages_rpc(
|
||||
#[arg(rename = "target-id")] target_id: BackupTargetId,
|
||||
#[arg] password: String,
|
||||
) -> Result<(), Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let fs = target_id
|
||||
.load(&mut ctx.secret_store.acquire().await?)
|
||||
.load(ctx.secret_store.acquire().await?.as_mut())
|
||||
.await?;
|
||||
let backup_guard =
|
||||
BackupMountGuard::mount(TmpMountGuard::mount(&fs, ReadWrite).await?, &password).await?;
|
||||
|
||||
let (backup_guard, tasks, _) = restore_packages(&ctx, &mut db, backup_guard, ids).await?;
|
||||
let (backup_guard, tasks, _) = restore_packages(&ctx, backup_guard, ids).await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
stream::iter(tasks.into_iter().map(|x| (x, ctx.clone())))
|
||||
.for_each_concurrent(5, |(res, ctx)| async move {
|
||||
let mut db = ctx.db.handle();
|
||||
match res.await {
|
||||
(Ok(_), _) => (),
|
||||
(Err(err), package_id) => {
|
||||
if let Err(err) = ctx
|
||||
.notification_manager
|
||||
.notify(
|
||||
&mut db,
|
||||
ctx.db.clone(),
|
||||
Some(package_id.clone()),
|
||||
NotificationLevel::Error,
|
||||
"Restoration Failure".to_string(),
|
||||
@@ -110,7 +105,7 @@ async fn approximate_progress(
|
||||
if tokio::fs::metadata(&dir).await.is_err() {
|
||||
*size = 0;
|
||||
} else {
|
||||
*size = dir_size(&dir).await?;
|
||||
*size = dir_size(&dir, None).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -185,37 +180,23 @@ pub async fn recover_full_embassy(
|
||||
.await?;
|
||||
|
||||
let os_backup_path = backup_guard.as_ref().join("os-backup.cbor");
|
||||
let os_backup: OsBackup =
|
||||
IoFormat::Cbor.from_slice(&tokio::fs::read(&os_backup_path).await.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
os_backup_path.display().to_string(),
|
||||
)
|
||||
})?)?;
|
||||
let mut os_backup: OsBackup = IoFormat::Cbor.from_slice(
|
||||
&tokio::fs::read(&os_backup_path)
|
||||
.await
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, os_backup_path.display().to_string()))?,
|
||||
)?;
|
||||
|
||||
let password = argon2::hash_encoded(
|
||||
os_backup.account.password = argon2::hash_encoded(
|
||||
embassy_password.as_bytes(),
|
||||
&rand::random::<[u8; 16]>()[..],
|
||||
&argon2::Config::default(),
|
||||
&argon2::Config::rfc9106_low_mem(),
|
||||
)
|
||||
.with_kind(crate::ErrorKind::PasswordHashGeneration)?;
|
||||
let key_vec = os_backup.tor_key.as_bytes().to_vec();
|
||||
let secret_store = ctx.secret_store().await?;
|
||||
sqlx::query!(
|
||||
"INSERT INTO account (id, password, tor_key) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET password = $2, tor_key = $3",
|
||||
0,
|
||||
password,
|
||||
key_vec,
|
||||
)
|
||||
.execute(&mut secret_store.acquire().await?)
|
||||
.await?;
|
||||
.with_kind(ErrorKind::PasswordHashGeneration)?;
|
||||
|
||||
let secret_store = ctx.secret_store().await?;
|
||||
|
||||
os_backup.account.save(&secret_store).await?;
|
||||
|
||||
SslManager::import_root_ca(
|
||||
secret_store.clone(),
|
||||
os_backup.root_ca_key,
|
||||
os_backup.root_ca_cert.clone(),
|
||||
)
|
||||
.await?;
|
||||
secret_store.close().await;
|
||||
|
||||
let cfg = RpcContextConfig::load(ctx.config_path.clone()).await?;
|
||||
@@ -223,33 +204,25 @@ pub async fn recover_full_embassy(
|
||||
init(&cfg).await?;
|
||||
|
||||
let rpc_ctx = RpcContext::init(ctx.config_path.clone(), disk_guid.clone()).await?;
|
||||
let mut db = rpc_ctx.db.handle();
|
||||
|
||||
let receipts = crate::hostname::HostNameReceipt::new(&mut db).await?;
|
||||
let hostname = get_hostname(&mut db, &receipts).await?;
|
||||
|
||||
drop(db);
|
||||
let mut db = rpc_ctx.db.handle();
|
||||
|
||||
let ids = backup_guard
|
||||
let ids: Vec<_> = backup_guard
|
||||
.metadata
|
||||
.package_backups
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect();
|
||||
let (backup_guard, tasks, progress_info) =
|
||||
restore_packages(&rpc_ctx, &mut db, backup_guard, ids).await?;
|
||||
restore_packages(&rpc_ctx, backup_guard, ids).await?;
|
||||
let task_consumer_rpc_ctx = rpc_ctx.clone();
|
||||
tokio::select! {
|
||||
_ = async move {
|
||||
stream::iter(tasks.into_iter().map(|x| (x, task_consumer_rpc_ctx.clone())))
|
||||
.for_each_concurrent(5, |(res, ctx)| async move {
|
||||
let mut db = ctx.db.handle();
|
||||
match res.await {
|
||||
(Ok(_), _) => (),
|
||||
(Err(err), package_id) => {
|
||||
if let Err(err) = ctx.notification_manager.notify(
|
||||
&mut db,
|
||||
ctx.db.clone(),
|
||||
Some(package_id.clone()),
|
||||
NotificationLevel::Error,
|
||||
"Restoration Failure".to_string(), format!("Error restoring package {}: {}", package_id,err), (), None).await{
|
||||
@@ -273,15 +246,15 @@ pub async fn recover_full_embassy(
|
||||
|
||||
Ok((
|
||||
disk_guid,
|
||||
hostname,
|
||||
os_backup.tor_key.public().get_onion_address(),
|
||||
os_backup.root_ca_cert,
|
||||
os_backup.account.hostname,
|
||||
os_backup.account.key.tor_address(),
|
||||
os_backup.account.root_ca_cert,
|
||||
))
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, backup_guard))]
|
||||
async fn restore_packages(
|
||||
ctx: &RpcContext,
|
||||
db: &mut PatchDbHandle,
|
||||
backup_guard: BackupMountGuard<TmpMountGuard>,
|
||||
ids: Vec<PackageId>,
|
||||
) -> Result<
|
||||
@@ -292,7 +265,7 @@ async fn restore_packages(
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let guards = assure_restoring(ctx, db, ids, &backup_guard).await?;
|
||||
let guards = assure_restoring(ctx, ids, &backup_guard).await?;
|
||||
|
||||
let mut progress_info = ProgressInfo::default();
|
||||
|
||||
@@ -300,10 +273,12 @@ async fn restore_packages(
|
||||
for (manifest, guard) in guards {
|
||||
let id = manifest.id.clone();
|
||||
let (progress, task) = restore_package(ctx.clone(), manifest, guard).await?;
|
||||
progress_info.package_installs.insert(id.clone(), progress);
|
||||
progress_info
|
||||
.package_installs
|
||||
.insert(id.clone(), progress.clone());
|
||||
progress_info
|
||||
.src_volume_size
|
||||
.insert(id.clone(), dir_size(backup_dir(&id)).await?);
|
||||
.insert(id.clone(), dir_size(backup_dir(&id), None).await?);
|
||||
progress_info.target_volume_size.insert(id.clone(), 0);
|
||||
let package_id = id.clone();
|
||||
tasks.push(
|
||||
@@ -324,23 +299,20 @@ async fn restore_packages(
|
||||
Ok((backup_guard, tasks, progress_info))
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, db, backup_guard))]
|
||||
#[instrument(skip(ctx, backup_guard))]
|
||||
async fn assure_restoring(
|
||||
ctx: &RpcContext,
|
||||
db: &mut PatchDbHandle,
|
||||
ids: Vec<PackageId>,
|
||||
backup_guard: &BackupMountGuard<TmpMountGuard>,
|
||||
) -> Result<Vec<(Manifest, PackageBackupMountGuard)>, Error> {
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let mut guards = Vec::with_capacity(ids.len());
|
||||
|
||||
let mut insert_packages = BTreeMap::new();
|
||||
|
||||
for id in ids {
|
||||
let mut model = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&id)
|
||||
.get_mut(&mut tx)
|
||||
.await?;
|
||||
let peek = ctx.db.peek().await;
|
||||
|
||||
let model = peek.as_package_data().as_idx(&id);
|
||||
|
||||
if !model.is_none() {
|
||||
return Err(Error::new(
|
||||
@@ -348,14 +320,15 @@ async fn assure_restoring(
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
|
||||
let guard = backup_guard.mount_package_backup(&id).await?;
|
||||
let s9pk_path = Path::new(BACKUP_DIR).join(&id).join(format!("{}.s9pk", id));
|
||||
let mut rdr = S9pkReader::open(&s9pk_path, false).await?;
|
||||
|
||||
let manifest = rdr.manifest().await?;
|
||||
let version = manifest.version.clone();
|
||||
let progress = InstallProgress::new(Some(tokio::fs::metadata(&s9pk_path).await?.len()));
|
||||
let progress = Arc::new(InstallProgress::new(Some(
|
||||
tokio::fs::metadata(&s9pk_path).await?.len(),
|
||||
)));
|
||||
|
||||
let public_dir_path = ctx
|
||||
.datadir
|
||||
@@ -379,18 +352,25 @@ async fn assure_restoring(
|
||||
let mut dst = File::create(&icon_path).await?;
|
||||
tokio::io::copy(&mut rdr.icon().await?, &mut dst).await?;
|
||||
dst.sync_all().await?;
|
||||
|
||||
*model = Some(PackageDataEntry::Restoring {
|
||||
install_progress: progress.clone(),
|
||||
static_files: StaticFiles::local(&id, &version, manifest.assets.icon_type()),
|
||||
manifest: manifest.clone(),
|
||||
});
|
||||
model.save(&mut tx).await?;
|
||||
insert_packages.insert(
|
||||
id.clone(),
|
||||
PackageDataEntry::Restoring(PackageDataEntryRestoring {
|
||||
install_progress: progress.clone(),
|
||||
static_files: StaticFiles::local(&id, &version, manifest.assets.icon_type()),
|
||||
manifest: manifest.clone(),
|
||||
}),
|
||||
);
|
||||
|
||||
guards.push((manifest, guard));
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
for (id, package) in insert_packages {
|
||||
db.as_package_data_mut().insert(&id, &package)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
Ok(guards)
|
||||
}
|
||||
|
||||
@@ -406,53 +386,71 @@ async fn restore_package<'a>(
|
||||
.join(format!("{}.s9pk", id));
|
||||
|
||||
let metadata_path = Path::new(BACKUP_DIR).join(&id).join("metadata.cbor");
|
||||
let metadata: BackupMetadata =
|
||||
IoFormat::Cbor.from_slice(&tokio::fs::read(&metadata_path).await.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
metadata_path.display().to_string(),
|
||||
)
|
||||
})?)?;
|
||||
for (iface, key) in metadata.tor_keys {
|
||||
let key_vec = base32::decode(base32::Alphabet::RFC4648 { padding: true }, &key)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("invalid base32 string"),
|
||||
crate::ErrorKind::Deserialization,
|
||||
)
|
||||
})?;
|
||||
let metadata: BackupMetadata = IoFormat::Cbor.from_slice(
|
||||
&tokio::fs::read(&metadata_path)
|
||||
.await
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, metadata_path.display().to_string()))?,
|
||||
)?;
|
||||
|
||||
let mut secrets = ctx.secret_store.acquire().await?;
|
||||
let mut secrets_tx = secrets.begin().await?;
|
||||
for (iface, key) in metadata.network_keys {
|
||||
let k = key.0.as_slice();
|
||||
sqlx::query!(
|
||||
"INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO UPDATE SET key = $3",
|
||||
*id,
|
||||
*iface,
|
||||
key_vec,
|
||||
)
|
||||
.execute(&ctx.secret_store)
|
||||
.await?;
|
||||
"INSERT INTO network_keys (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING",
|
||||
id.to_string(),
|
||||
iface.to_string(),
|
||||
k,
|
||||
)
|
||||
.execute(secrets_tx.as_mut()).await?;
|
||||
}
|
||||
// DEPRECATED
|
||||
for (iface, key) in metadata.tor_keys {
|
||||
let k = key.0.as_slice();
|
||||
sqlx::query!(
|
||||
"INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING",
|
||||
id.to_string(),
|
||||
iface.to_string(),
|
||||
k,
|
||||
)
|
||||
.execute(secrets_tx.as_mut()).await?;
|
||||
}
|
||||
secrets_tx.commit().await?;
|
||||
drop(secrets);
|
||||
|
||||
let len = tokio::fs::metadata(&s9pk_path)
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
s9pk_path.display().to_string(),
|
||||
)
|
||||
})?
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, s9pk_path.display().to_string()))?
|
||||
.len();
|
||||
let file = File::open(&s9pk_path).await.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
s9pk_path.display().to_string(),
|
||||
)
|
||||
})?;
|
||||
let file = File::open(&s9pk_path)
|
||||
.await
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, s9pk_path.display().to_string()))?;
|
||||
|
||||
let progress = InstallProgress::new(Some(len));
|
||||
let marketplace_url = metadata.marketplace_url;
|
||||
|
||||
let progress = Arc::new(progress);
|
||||
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_package_data_mut().insert(
|
||||
&id,
|
||||
&PackageDataEntry::Restoring(PackageDataEntryRestoring {
|
||||
install_progress: progress.clone(),
|
||||
static_files: StaticFiles::local(
|
||||
&id,
|
||||
&manifest.version,
|
||||
manifest.assets.icon_type(),
|
||||
),
|
||||
manifest: manifest.clone(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
Ok((
|
||||
progress.clone(),
|
||||
async move {
|
||||
download_install_s9pk(&ctx, &manifest, None, progress, file).await?;
|
||||
download_install_s9pk(ctx, manifest, marketplace_url, progress, file, None).await?;
|
||||
|
||||
guard.unmount().await?;
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ use crate::disk::mount::filesystem::cifs::Cifs;
|
||||
use crate::disk::mount::filesystem::ReadOnly;
|
||||
use crate::disk::mount::guard::TmpMountGuard;
|
||||
use crate::disk::util::{recovery_info, EmbassyOsRecoveryInfo};
|
||||
use crate::prelude::*;
|
||||
use crate::util::display_none;
|
||||
use crate::util::serde::KeyVal;
|
||||
use crate::Error;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
@@ -84,7 +84,7 @@ pub async fn update(
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", id),
|
||||
crate::ErrorKind::NotFound,
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
let cifs = Cifs {
|
||||
@@ -112,7 +112,7 @@ pub async fn update(
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", BackupTargetId::Cifs { id }),
|
||||
crate::ErrorKind::NotFound,
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
Ok(KeyVal {
|
||||
@@ -134,7 +134,7 @@ pub async fn remove(#[context] ctx: RpcContext, #[arg] id: BackupTargetId) -> Re
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", id),
|
||||
crate::ErrorKind::NotFound,
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
if sqlx::query!("DELETE FROM cifs_shares WHERE id = $1", id)
|
||||
@@ -145,7 +145,7 @@ pub async fn remove(#[context] ctx: RpcContext, #[arg] id: BackupTargetId) -> Re
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", BackupTargetId::Cifs { id }),
|
||||
crate::ErrorKind::NotFound,
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
Ok(())
|
||||
|
||||
@@ -11,6 +11,7 @@ use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
use sqlx::{Executor, Postgres};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::instrument;
|
||||
|
||||
use self::cifs::CifsBackupTarget;
|
||||
@@ -21,10 +22,10 @@ use crate::disk::mount::filesystem::cifs::Cifs;
|
||||
use crate::disk::mount::filesystem::{FileSystem, MountType, ReadWrite};
|
||||
use crate::disk::mount::guard::TmpMountGuard;
|
||||
use crate::disk::util::PartitionInfo;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::serde::{deserialize_from_str, display_serializable, serialize_display};
|
||||
use crate::util::Version;
|
||||
use crate::Error;
|
||||
use crate::util::{display_none, Version};
|
||||
|
||||
pub mod cifs;
|
||||
|
||||
@@ -42,7 +43,7 @@ pub enum BackupTarget {
|
||||
Cifs(CifsBackupTarget),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub enum BackupTargetId {
|
||||
Disk { logicalname: PathBuf },
|
||||
Cifs { id: i32 },
|
||||
@@ -71,14 +72,14 @@ impl std::fmt::Display for BackupTargetId {
|
||||
impl std::str::FromStr for BackupTargetId {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.split_once("-") {
|
||||
match s.split_once('-') {
|
||||
Some(("disk", logicalname)) => Ok(BackupTargetId::Disk {
|
||||
logicalname: Path::new(logicalname).to_owned(),
|
||||
}),
|
||||
Some(("cifs", id)) => Ok(BackupTargetId::Cifs { id: id.parse()? }),
|
||||
_ => Err(Error::new(
|
||||
eyre!("Invalid Backup Target ID"),
|
||||
crate::ErrorKind::InvalidBackupTargetId,
|
||||
ErrorKind::InvalidBackupTargetId,
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -129,7 +130,7 @@ impl FileSystem for BackupTargetFS {
|
||||
}
|
||||
}
|
||||
|
||||
#[command(subcommands(cifs::cifs, list, info))]
|
||||
#[command(subcommands(cifs::cifs, list, info, mount, umount))]
|
||||
pub fn target() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -141,7 +142,7 @@ pub async fn list(
|
||||
let mut sql_handle = ctx.secret_store.acquire().await?;
|
||||
let (disks_res, cifs) = tokio::try_join!(
|
||||
crate::disk::util::list(&ctx.os_partitions),
|
||||
cifs::list(&mut sql_handle),
|
||||
cifs::list(sql_handle.as_mut()),
|
||||
)?;
|
||||
Ok(disks_res
|
||||
.into_iter()
|
||||
@@ -212,7 +213,7 @@ fn display_backup_info(info: BackupInfo, matches: &ArgMatches) {
|
||||
]);
|
||||
for (id, info) in info.package_backups {
|
||||
let row = row![
|
||||
id.as_str(),
|
||||
&*id,
|
||||
info.version.as_str(),
|
||||
info.os_version.as_str(),
|
||||
&info.timestamp.to_string(),
|
||||
@@ -232,7 +233,7 @@ pub async fn info(
|
||||
let guard = BackupMountGuard::mount(
|
||||
TmpMountGuard::mount(
|
||||
&target_id
|
||||
.load(&mut ctx.secret_store.acquire().await?)
|
||||
.load(ctx.secret_store.acquire().await?.as_mut())
|
||||
.await?,
|
||||
ReadWrite,
|
||||
)
|
||||
@@ -247,3 +248,60 @@ pub async fn info(
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref USER_MOUNTS: Mutex<BTreeMap<BackupTargetId, BackupMountGuard<TmpMountGuard>>> =
|
||||
Mutex::new(BTreeMap::new());
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn mount(
|
||||
#[context] ctx: RpcContext,
|
||||
#[arg(rename = "target-id")] target_id: BackupTargetId,
|
||||
#[arg] password: String,
|
||||
) -> Result<String, Error> {
|
||||
let mut mounts = USER_MOUNTS.lock().await;
|
||||
|
||||
if let Some(existing) = mounts.get(&target_id) {
|
||||
return Ok(existing.as_ref().display().to_string());
|
||||
}
|
||||
|
||||
let guard = BackupMountGuard::mount(
|
||||
TmpMountGuard::mount(
|
||||
&target_id
|
||||
.clone()
|
||||
.load(ctx.secret_store.acquire().await?.as_mut())
|
||||
.await?,
|
||||
ReadWrite,
|
||||
)
|
||||
.await?,
|
||||
&password,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let res = guard.as_ref().display().to_string();
|
||||
|
||||
mounts.insert(target_id, guard);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
#[command(display(display_none))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn umount(
|
||||
#[context] _ctx: RpcContext,
|
||||
#[arg(rename = "target-id")] target_id: Option<BackupTargetId>,
|
||||
) -> Result<(), Error> {
|
||||
let mut mounts = USER_MOUNTS.lock().await;
|
||||
if let Some(target_id) = target_id {
|
||||
if let Some(existing) = mounts.remove(&target_id) {
|
||||
existing.unmount().await?;
|
||||
}
|
||||
} else {
|
||||
for (_, existing) in std::mem::take(&mut *mounts) {
|
||||
existing.unmount().await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use embassy::context::rpc::RpcContextConfig;
|
||||
use embassy::context::{DiagnosticContext, InstallContext, SetupContext};
|
||||
use embassy::disk::fsck::RepairStrategy;
|
||||
use embassy::disk::main::DEFAULT_PASSWORD;
|
||||
use embassy::disk::REPAIR_DISK_PATH;
|
||||
use embassy::init::STANDBY_MODE_PATH;
|
||||
use embassy::net::embassy_service_http_server::EmbassyServiceHTTPServer;
|
||||
#[cfg(feature = "avahi")]
|
||||
use embassy::net::mdns::MdnsController;
|
||||
use embassy::net::net_utils::ResourceFqdn;
|
||||
use embassy::net::static_server::{
|
||||
diag_ui_file_router, install_ui_file_router, setup_ui_file_router,
|
||||
};
|
||||
use embassy::shutdown::Shutdown;
|
||||
use embassy::sound::CHIME;
|
||||
use embassy::util::logger::EmbassyLogger;
|
||||
use embassy::util::Invoke;
|
||||
use embassy::{Error, ErrorKind, ResultExt};
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
|
||||
#[instrument]
|
||||
async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<(), Error> {
|
||||
if tokio::fs::metadata("/cdrom").await.is_ok() {
|
||||
#[cfg(feature = "avahi")]
|
||||
let _mdns = MdnsController::init().await?;
|
||||
|
||||
let ctx = InstallContext::init(cfg_path).await?;
|
||||
|
||||
let embassy_ip_fqdn: ResourceFqdn = ResourceFqdn::IpAddr;
|
||||
let embassy_fqdn: ResourceFqdn = "embassy.local".parse()?;
|
||||
|
||||
let localhost_fqdn = ResourceFqdn::LocalHost;
|
||||
|
||||
let install_ui_handler = install_ui_file_router(ctx.clone()).await?;
|
||||
|
||||
let mut install_http_server =
|
||||
EmbassyServiceHTTPServer::new([0, 0, 0, 0].into(), 80, None).await?;
|
||||
install_http_server
|
||||
.add_svc_handler_mapping(embassy_ip_fqdn, install_ui_handler.clone())
|
||||
.await?;
|
||||
install_http_server
|
||||
.add_svc_handler_mapping(embassy_fqdn, install_ui_handler.clone())
|
||||
.await?;
|
||||
|
||||
install_http_server
|
||||
.add_svc_handler_mapping(localhost_fqdn, install_ui_handler.clone())
|
||||
.await?;
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(1)).await; // let the record state that I hate this
|
||||
CHIME.play().await?;
|
||||
|
||||
ctx.shutdown
|
||||
.subscribe()
|
||||
.recv()
|
||||
.await
|
||||
.expect("context dropped");
|
||||
install_http_server.shutdown.send(()).unwrap();
|
||||
Command::new("reboot")
|
||||
.invoke(embassy::ErrorKind::Unknown)
|
||||
.await?;
|
||||
} else if tokio::fs::metadata("/media/embassy/config/disk.guid")
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
#[cfg(feature = "avahi")]
|
||||
let _mdns = MdnsController::init().await?;
|
||||
|
||||
let ctx = SetupContext::init(cfg_path).await?;
|
||||
|
||||
let embassy_ip_fqdn: ResourceFqdn = ResourceFqdn::IpAddr;
|
||||
let embassy_fqdn: ResourceFqdn = "embassy.local".parse()?;
|
||||
let localhost_fqdn = ResourceFqdn::LocalHost;
|
||||
|
||||
let setup_ui_handler = setup_ui_file_router(ctx.clone()).await?;
|
||||
|
||||
let mut setup_http_server =
|
||||
EmbassyServiceHTTPServer::new([0, 0, 0, 0].into(), 80, None).await?;
|
||||
setup_http_server
|
||||
.add_svc_handler_mapping(embassy_ip_fqdn, setup_ui_handler.clone())
|
||||
.await?;
|
||||
setup_http_server
|
||||
.add_svc_handler_mapping(embassy_fqdn, setup_ui_handler.clone())
|
||||
.await?;
|
||||
|
||||
setup_http_server
|
||||
.add_svc_handler_mapping(localhost_fqdn, setup_ui_handler.clone())
|
||||
.await?;
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(1)).await; // let the record state that I hate this
|
||||
CHIME.play().await?;
|
||||
ctx.shutdown
|
||||
.subscribe()
|
||||
.recv()
|
||||
.await
|
||||
.expect("context dropped");
|
||||
setup_http_server.shutdown.send(()).unwrap();
|
||||
tokio::task::yield_now().await;
|
||||
if let Err(e) = Command::new("killall")
|
||||
.arg("firefox-esr")
|
||||
.invoke(ErrorKind::NotFound)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to kill kiosk: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
} else {
|
||||
let cfg = RpcContextConfig::load(cfg_path).await?;
|
||||
let guid_string = tokio::fs::read_to_string("/media/embassy/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
||||
.await?;
|
||||
let guid = guid_string.trim();
|
||||
let requires_reboot = embassy::disk::main::import(
|
||||
guid,
|
||||
cfg.datadir(),
|
||||
if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() {
|
||||
RepairStrategy::Aggressive
|
||||
} else {
|
||||
RepairStrategy::Preen
|
||||
},
|
||||
DEFAULT_PASSWORD,
|
||||
)
|
||||
.await?;
|
||||
if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() {
|
||||
tokio::fs::remove_file(REPAIR_DISK_PATH)
|
||||
.await
|
||||
.with_ctx(|_| (embassy::ErrorKind::Filesystem, REPAIR_DISK_PATH))?;
|
||||
}
|
||||
if requires_reboot.0 {
|
||||
embassy::disk::main::export(guid, cfg.datadir()).await?;
|
||||
Command::new("reboot")
|
||||
.invoke(embassy::ErrorKind::Unknown)
|
||||
.await?;
|
||||
}
|
||||
tracing::info!("Loaded Disk");
|
||||
embassy::init::init(&cfg).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_script_if_exists<P: AsRef<Path>>(path: P) {
|
||||
let script = path.as_ref();
|
||||
if script.exists() {
|
||||
match Command::new("/bin/bash").arg(script).spawn() {
|
||||
Ok(mut c) => {
|
||||
if let Err(e) = c.wait().await {
|
||||
tracing::error!("Error Running {}: {}", script.display(), e);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error Running {}: {}", script.display(), e);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error> {
|
||||
if tokio::fs::metadata(STANDBY_MODE_PATH).await.is_ok() {
|
||||
tokio::fs::remove_file(STANDBY_MODE_PATH).await?;
|
||||
Command::new("sync").invoke(ErrorKind::Filesystem).await?;
|
||||
embassy::sound::SHUTDOWN.play().await?;
|
||||
futures::future::pending::<()>().await;
|
||||
}
|
||||
|
||||
embassy::sound::BEP.play().await?;
|
||||
|
||||
run_script_if_exists("/media/embassy/config/preinit.sh").await;
|
||||
|
||||
let res = if let Err(e) = setup_or_init(cfg_path.clone()).await {
|
||||
async move {
|
||||
tracing::error!("{}", e.source);
|
||||
tracing::debug!("{}", e.source);
|
||||
embassy::sound::BEETHOVEN.play().await?;
|
||||
#[cfg(feature = "avahi")]
|
||||
let _mdns = MdnsController::init().await?;
|
||||
|
||||
let ctx = DiagnosticContext::init(
|
||||
cfg_path,
|
||||
if tokio::fs::metadata("/media/embassy/config/disk.guid")
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
Some(Arc::new(
|
||||
tokio::fs::read_to_string("/media/embassy/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
||||
.await?
|
||||
.trim()
|
||||
.to_owned(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
e,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let embassy_ip_fqdn: ResourceFqdn = ResourceFqdn::IpAddr;
|
||||
let embassy_fqdn: ResourceFqdn = "embassy.local".parse()?;
|
||||
|
||||
let localhost_fqdn = ResourceFqdn::LocalHost;
|
||||
|
||||
let diag_ui_handler = diag_ui_file_router(ctx.clone()).await?;
|
||||
|
||||
let mut diag_http_server =
|
||||
EmbassyServiceHTTPServer::new([0, 0, 0, 0].into(), 80, None).await?;
|
||||
diag_http_server
|
||||
.add_svc_handler_mapping(embassy_ip_fqdn, diag_ui_handler.clone())
|
||||
.await?;
|
||||
diag_http_server
|
||||
.add_svc_handler_mapping(embassy_fqdn, diag_ui_handler.clone())
|
||||
.await?;
|
||||
|
||||
diag_http_server
|
||||
.add_svc_handler_mapping(localhost_fqdn, diag_ui_handler.clone())
|
||||
.await?;
|
||||
|
||||
let shutdown = ctx.shutdown.subscribe().recv().await.unwrap();
|
||||
diag_http_server.shutdown.send(()).unwrap();
|
||||
Ok(shutdown)
|
||||
}
|
||||
.await
|
||||
} else {
|
||||
Ok(None)
|
||||
};
|
||||
|
||||
run_script_if_exists("/media/embassy/config/postinit.sh").await;
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let matches = clap::App::new("embassy-init")
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
.short('c')
|
||||
.long("config")
|
||||
.takes_value(true),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
EmbassyLogger::init();
|
||||
|
||||
let cfg_path = matches.value_of("config").map(|p| Path::new(p).to_owned());
|
||||
let res = {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("failed to initialize runtime");
|
||||
rt.block_on(inner_main(cfg_path))
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok(Some(shutdown)) => shutdown.execute(),
|
||||
Ok(None) => (),
|
||||
Err(e) => {
|
||||
eprintln!("{}", e.source);
|
||||
tracing::debug!("{:?}", e.source);
|
||||
drop(e.source);
|
||||
std::process::exit(e.kind as i32)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ fn log_str_error(action: &str, e: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pub fn main() {
|
||||
let aliases: Vec<_> = std::env::args().skip(1).collect();
|
||||
unsafe {
|
||||
let simple_poll = avahi_sys::avahi_simple_poll_new();
|
||||
9
backend/src/bins/deprecated.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub fn renamed(old: &str, new: &str) -> ! {
|
||||
eprintln!("{old} has been renamed to {new}");
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
pub fn removed(name: &str) -> ! {
|
||||
eprintln!("{name} has been removed");
|
||||
std::process::exit(1)
|
||||
}
|
||||
59
backend/src/bins/mod.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(feature = "avahi-alias")]
|
||||
pub mod avahi_alias;
|
||||
pub mod deprecated;
|
||||
#[cfg(feature = "cli")]
|
||||
pub mod start_cli;
|
||||
#[cfg(feature = "js_engine")]
|
||||
pub mod start_deno;
|
||||
#[cfg(feature = "daemon")]
|
||||
pub mod start_init;
|
||||
#[cfg(feature = "sdk")]
|
||||
pub mod start_sdk;
|
||||
#[cfg(feature = "daemon")]
|
||||
pub mod startd;
|
||||
|
||||
fn select_executable(name: &str) -> Option<fn()> {
|
||||
match name {
|
||||
#[cfg(feature = "avahi-alias")]
|
||||
"avahi-alias" => Some(avahi_alias::main),
|
||||
#[cfg(feature = "js_engine")]
|
||||
"start-deno" => Some(start_deno::main),
|
||||
#[cfg(feature = "cli")]
|
||||
"start-cli" => Some(start_cli::main),
|
||||
#[cfg(feature = "sdk")]
|
||||
"start-sdk" => Some(start_sdk::main),
|
||||
#[cfg(feature = "daemon")]
|
||||
"startd" => Some(startd::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")),
|
||||
"embassy-init" => Some(|| deprecated::removed("embassy-init")),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn startbox() {
|
||||
let args = std::env::args().take(2).collect::<Vec<_>>();
|
||||
if let Some(x) = args
|
||||
.get(0)
|
||||
.and_then(|s| Path::new(&*s).file_name())
|
||||
.and_then(|s| s.to_str())
|
||||
.and_then(|s| select_executable(&s))
|
||||
{
|
||||
x()
|
||||
} else if let Some(x) = args.get(1).and_then(|s| select_executable(&s)) {
|
||||
x()
|
||||
} else {
|
||||
eprintln!(
|
||||
"unknown executable: {}",
|
||||
args.get(0)
|
||||
.filter(|x| &**x != "startbox")
|
||||
.or_else(|| args.get(1))
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("N/A")
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,22 @@
|
||||
use clap::Arg;
|
||||
use embassy::context::CliContext;
|
||||
use embassy::util::logger::EmbassyLogger;
|
||||
use embassy::version::{Current, VersionT};
|
||||
use embassy::Error;
|
||||
use rpc_toolkit::run_cli;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::context::CliContext;
|
||||
use crate::util::logger::EmbassyLogger;
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::Error;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref VERSION_STRING: String = Current::new().semver().to_string();
|
||||
}
|
||||
|
||||
fn inner_main() -> Result<(), Error> {
|
||||
run_cli!({
|
||||
command: embassy::main_api,
|
||||
command: crate::main_api,
|
||||
app: app => app
|
||||
.name("Embassy CLI")
|
||||
.name("StartOS CLI")
|
||||
.version(&**VERSION_STRING)
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
@@ -48,7 +49,7 @@ fn inner_main() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pub fn main() {
|
||||
match inner_main() {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
134
backend/src/bins/start_deno.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use rpc_toolkit::{command, run_cli, Context};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::procedure::js_scripts::ExecuteArgs;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::serde::{display_serializable, parse_stdin_deserializable};
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::Error;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref VERSION_STRING: String = Current::new().semver().to_string();
|
||||
}
|
||||
|
||||
struct DenoContext;
|
||||
impl Context for DenoContext {}
|
||||
|
||||
#[command(subcommands(execute, sandbox))]
|
||||
fn deno_api() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command(cli_only, display(display_serializable))]
|
||||
async fn execute(
|
||||
#[arg(stdin, parse(parse_stdin_deserializable))] arg: ExecuteArgs,
|
||||
) -> Result<Result<Value, (i32, String)>, Error> {
|
||||
let ExecuteArgs {
|
||||
procedure,
|
||||
directory,
|
||||
pkg_id,
|
||||
pkg_version,
|
||||
name,
|
||||
volumes,
|
||||
input,
|
||||
} = arg;
|
||||
PackageLogger::init(&pkg_id);
|
||||
procedure
|
||||
.execute_impl(&directory, &pkg_id, &pkg_version, name, &volumes, input)
|
||||
.await
|
||||
}
|
||||
#[command(cli_only, display(display_serializable))]
|
||||
async fn sandbox(
|
||||
#[arg(stdin, parse(parse_stdin_deserializable))] arg: ExecuteArgs,
|
||||
) -> Result<Result<Value, (i32, String)>, Error> {
|
||||
let ExecuteArgs {
|
||||
procedure,
|
||||
directory,
|
||||
pkg_id,
|
||||
pkg_version,
|
||||
name,
|
||||
volumes,
|
||||
input,
|
||||
} = arg;
|
||||
PackageLogger::init(&pkg_id);
|
||||
procedure
|
||||
.sandboxed_impl(&directory, &pkg_id, &pkg_version, &volumes, input, name)
|
||||
.await
|
||||
}
|
||||
|
||||
use tracing::Subscriber;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PackageLogger {}
|
||||
|
||||
impl PackageLogger {
|
||||
fn base_subscriber(id: &PackageId) -> impl Subscriber {
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
|
||||
let filter_layer = EnvFilter::default().add_directive(
|
||||
format!("{}=warn", std::module_path!().split("::").next().unwrap())
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
let fmt_layer = fmt::layer().with_writer(std::io::stderr).with_target(true);
|
||||
let journald_layer = tracing_journald::layer()
|
||||
.unwrap()
|
||||
.with_syslog_identifier(format!("{id}.embassy"));
|
||||
|
||||
let sub = tracing_subscriber::registry()
|
||||
.with(filter_layer)
|
||||
.with(fmt_layer)
|
||||
.with(journald_layer)
|
||||
.with(ErrorLayer::default());
|
||||
|
||||
sub
|
||||
}
|
||||
pub fn init(id: &PackageId) -> Self {
|
||||
Self::base_subscriber(id).init();
|
||||
color_eyre::install().unwrap_or_else(|_| tracing::warn!("tracing too many times"));
|
||||
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_main() -> Result<(), Error> {
|
||||
run_cli!({
|
||||
command: deno_api,
|
||||
app: app => app
|
||||
.name("StartOS Deno Executor")
|
||||
.version(&**VERSION_STRING),
|
||||
context: _m => DenoContext,
|
||||
exit: |e: RpcError| {
|
||||
match e.data {
|
||||
Some(Value::String(s)) => eprintln!("{}: {}", e.message, s),
|
||||
Some(Value::Object(o)) => if let Some(Value::String(s)) = o.get("details") {
|
||||
eprintln!("{}: {}", e.message, s);
|
||||
if let Some(Value::String(s)) = o.get("debug") {
|
||||
tracing::debug!("{}", s)
|
||||
}
|
||||
}
|
||||
Some(a) => eprintln!("{}: {}", e.message, a),
|
||||
None => eprintln!("{}", e.message),
|
||||
}
|
||||
|
||||
std::process::exit(e.code);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
match inner_main() {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
eprintln!("{}", e.source);
|
||||
tracing::debug!("{:?}", e.source);
|
||||
drop(e.source);
|
||||
std::process::exit(e.kind as i32)
|
||||
}
|
||||
}
|
||||
}
|
||||
268
backend/src/bins/start_init.rs
Normal file
@@ -0,0 +1,268 @@
|
||||
use std::net::{Ipv6Addr, SocketAddr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::rpc::RpcContextConfig;
|
||||
use crate::context::{DiagnosticContext, InstallContext, SetupContext};
|
||||
use crate::disk::fsck::RepairStrategy;
|
||||
use crate::disk::main::DEFAULT_PASSWORD;
|
||||
use crate::disk::REPAIR_DISK_PATH;
|
||||
use crate::firmware::update_firmware;
|
||||
use crate::init::STANDBY_MODE_PATH;
|
||||
use crate::net::web_server::WebServer;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::sound::CHIME;
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ErrorKind, ResultExt, PLATFORM};
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error> {
|
||||
if update_firmware().await?.0 {
|
||||
return Ok(Some(Shutdown {
|
||||
export_args: None,
|
||||
restart: true,
|
||||
}));
|
||||
}
|
||||
|
||||
Command::new("ln")
|
||||
.arg("-sf")
|
||||
.arg("/usr/lib/startos/scripts/fake-apt")
|
||||
.arg("/usr/local/bin/apt")
|
||||
.invoke(crate::ErrorKind::OpenSsh)
|
||||
.await?;
|
||||
Command::new("ln")
|
||||
.arg("-sf")
|
||||
.arg("/usr/lib/startos/scripts/fake-apt")
|
||||
.arg("/usr/local/bin/apt-get")
|
||||
.invoke(crate::ErrorKind::OpenSsh)
|
||||
.await?;
|
||||
Command::new("ln")
|
||||
.arg("-sf")
|
||||
.arg("/usr/lib/startos/scripts/fake-apt")
|
||||
.arg("/usr/local/bin/aptitude")
|
||||
.invoke(crate::ErrorKind::OpenSsh)
|
||||
.await?;
|
||||
|
||||
Command::new("make-ssl-cert")
|
||||
.arg("generate-default-snakeoil")
|
||||
.arg("--force-overwrite")
|
||||
.invoke(crate::ErrorKind::OpenSsl)
|
||||
.await?;
|
||||
|
||||
if tokio::fs::metadata("/run/live/medium").await.is_ok() {
|
||||
Command::new("sed")
|
||||
.arg("-i")
|
||||
.arg("s/PasswordAuthentication no/PasswordAuthentication yes/g")
|
||||
.arg("/etc/ssh/sshd_config")
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
Command::new("systemctl")
|
||||
.arg("reload")
|
||||
.arg("ssh")
|
||||
.invoke(crate::ErrorKind::OpenSsh)
|
||||
.await?;
|
||||
|
||||
let ctx = InstallContext::init(cfg_path).await?;
|
||||
|
||||
let server = WebServer::install(
|
||||
SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 80),
|
||||
ctx.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(1)).await; // let the record state that I hate this
|
||||
CHIME.play().await?;
|
||||
|
||||
ctx.shutdown
|
||||
.subscribe()
|
||||
.recv()
|
||||
.await
|
||||
.expect("context dropped");
|
||||
|
||||
server.shutdown().await;
|
||||
|
||||
Command::new("reboot")
|
||||
.invoke(crate::ErrorKind::Unknown)
|
||||
.await?;
|
||||
} else if tokio::fs::metadata("/media/embassy/config/disk.guid")
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
let ctx = SetupContext::init(cfg_path).await?;
|
||||
|
||||
let server = WebServer::setup(
|
||||
SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 80),
|
||||
ctx.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(1)).await; // let the record state that I hate this
|
||||
CHIME.play().await?;
|
||||
ctx.shutdown
|
||||
.subscribe()
|
||||
.recv()
|
||||
.await
|
||||
.expect("context dropped");
|
||||
|
||||
server.shutdown().await;
|
||||
|
||||
tokio::task::yield_now().await;
|
||||
if let Err(e) = Command::new("killall")
|
||||
.arg("firefox-esr")
|
||||
.invoke(ErrorKind::NotFound)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to kill kiosk: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
} else {
|
||||
let cfg = RpcContextConfig::load(cfg_path).await?;
|
||||
let guid_string = tokio::fs::read_to_string("/media/embassy/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
||||
.await?;
|
||||
let guid = guid_string.trim();
|
||||
let requires_reboot = crate::disk::main::import(
|
||||
guid,
|
||||
cfg.datadir(),
|
||||
if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() {
|
||||
RepairStrategy::Aggressive
|
||||
} else {
|
||||
RepairStrategy::Preen
|
||||
},
|
||||
if guid.ends_with("_UNENC") {
|
||||
None
|
||||
} else {
|
||||
Some(DEFAULT_PASSWORD)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() {
|
||||
tokio::fs::remove_file(REPAIR_DISK_PATH)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, REPAIR_DISK_PATH))?;
|
||||
}
|
||||
if requires_reboot.0 {
|
||||
crate::disk::main::export(guid, cfg.datadir()).await?;
|
||||
Command::new("reboot")
|
||||
.invoke(crate::ErrorKind::Unknown)
|
||||
.await?;
|
||||
}
|
||||
tracing::info!("Loaded Disk");
|
||||
crate::init::init(&cfg).await?;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn run_script_if_exists<P: AsRef<Path>>(path: P) {
|
||||
let script = path.as_ref();
|
||||
if script.exists() {
|
||||
match Command::new("/bin/bash").arg(script).spawn() {
|
||||
Ok(mut c) => {
|
||||
if let Err(e) = c.wait().await {
|
||||
tracing::error!("Error Running {}: {}", script.display(), e);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error Running {}: {}", script.display(), e);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error> {
|
||||
if &*PLATFORM == "raspberrypi" && tokio::fs::metadata(STANDBY_MODE_PATH).await.is_ok() {
|
||||
tokio::fs::remove_file(STANDBY_MODE_PATH).await?;
|
||||
Command::new("sync").invoke(ErrorKind::Filesystem).await?;
|
||||
crate::sound::SHUTDOWN.play().await?;
|
||||
futures::future::pending::<()>().await;
|
||||
}
|
||||
|
||||
crate::sound::BEP.play().await?;
|
||||
|
||||
run_script_if_exists("/media/embassy/config/preinit.sh").await;
|
||||
|
||||
let res = match setup_or_init(cfg_path.clone()).await {
|
||||
Err(e) => {
|
||||
async move {
|
||||
tracing::error!("{}", e.source);
|
||||
tracing::debug!("{}", e.source);
|
||||
crate::sound::BEETHOVEN.play().await?;
|
||||
|
||||
let ctx = DiagnosticContext::init(
|
||||
cfg_path,
|
||||
if tokio::fs::metadata("/media/embassy/config/disk.guid")
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
Some(Arc::new(
|
||||
tokio::fs::read_to_string("/media/embassy/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
||||
.await?
|
||||
.trim()
|
||||
.to_owned(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
e,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let server = WebServer::diagnostic(
|
||||
SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 80),
|
||||
ctx.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let shutdown = ctx.shutdown.subscribe().recv().await.unwrap();
|
||||
|
||||
server.shutdown().await;
|
||||
|
||||
Ok(shutdown)
|
||||
}
|
||||
.await
|
||||
}
|
||||
Ok(s) => Ok(s),
|
||||
};
|
||||
|
||||
run_script_if_exists("/media/embassy/config/postinit.sh").await;
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let matches = clap::App::new("start-init")
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
.short('c')
|
||||
.long("config")
|
||||
.takes_value(true),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let cfg_path = matches.value_of("config").map(|p| Path::new(p).to_owned());
|
||||
let res = {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("failed to initialize runtime");
|
||||
rt.block_on(inner_main(cfg_path))
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok(Some(shutdown)) => shutdown.execute(),
|
||||
Ok(None) => (),
|
||||
Err(e) => {
|
||||
eprintln!("{}", e.source);
|
||||
tracing::debug!("{:?}", e.source);
|
||||
drop(e.source);
|
||||
std::process::exit(e.kind as i32)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
use embassy::context::SdkContext;
|
||||
use embassy::util::logger::EmbassyLogger;
|
||||
use embassy::version::{Current, VersionT};
|
||||
use embassy::Error;
|
||||
use rpc_toolkit::run_cli;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::context::SdkContext;
|
||||
use crate::util::logger::EmbassyLogger;
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::Error;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref VERSION_STRING: String = Current::new().semver().to_string();
|
||||
}
|
||||
|
||||
fn inner_main() -> Result<(), Error> {
|
||||
run_cli!({
|
||||
command: embassy::portable_api,
|
||||
command: crate::portable_api,
|
||||
app: app => app
|
||||
.name("Embassy SDK")
|
||||
.name("StartOS SDK")
|
||||
.version(&**VERSION_STRING)
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
@@ -47,7 +48,7 @@ fn inner_main() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pub fn main() {
|
||||
match inner_main() {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
@@ -1,29 +1,22 @@
|
||||
use std::net::{Ipv6Addr, SocketAddr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use embassy::context::{DiagnosticContext, RpcContext};
|
||||
use embassy::net::embassy_service_http_server::EmbassyServiceHTTPServer;
|
||||
#[cfg(feature = "avahi")]
|
||||
use embassy::net::mdns::MdnsController;
|
||||
use embassy::net::net_controller::NetController;
|
||||
use embassy::net::net_utils::ResourceFqdn;
|
||||
use embassy::net::static_server::diag_ui_file_router;
|
||||
use embassy::net::tor::tor_health_check;
|
||||
use embassy::shutdown::Shutdown;
|
||||
use embassy::system::launch_metrics_task;
|
||||
use embassy::util::daemon;
|
||||
use embassy::util::logger::EmbassyLogger;
|
||||
use embassy::{Error, ErrorKind, ResultExt};
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
use reqwest::{Client, Proxy};
|
||||
use tokio::signal::unix::signal;
|
||||
use tracing::instrument;
|
||||
|
||||
#[instrument]
|
||||
use crate::context::{DiagnosticContext, RpcContext};
|
||||
use crate::net::web_server::WebServer;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::system::launch_metrics_task;
|
||||
use crate::util::logger::EmbassyLogger;
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error> {
|
||||
let (rpc_ctx, shutdown) = {
|
||||
let (rpc_ctx, server, shutdown) = async {
|
||||
let rpc_ctx = RpcContext::init(
|
||||
cfg_path,
|
||||
Arc::new(
|
||||
@@ -34,7 +27,12 @@ async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
NetController::setup_embassy_ui(rpc_ctx.clone()).await?;
|
||||
crate::hostname::sync_hostname(&rpc_ctx.account.read().await.hostname).await?;
|
||||
let server = WebServer::main(
|
||||
SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 80),
|
||||
rpc_ctx.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut shutdown_recv = rpc_ctx.shutdown.subscribe();
|
||||
|
||||
@@ -66,12 +64,6 @@ async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error
|
||||
.expect("send shutdown signal");
|
||||
});
|
||||
|
||||
{
|
||||
let mut db = rpc_ctx.db.handle();
|
||||
let receipts = embassy::context::rpc::RpcSetHostNameReceipts::new(&mut db).await?;
|
||||
embassy::hostname::sync_hostname(&mut db, &receipts.hostname_receipts).await?;
|
||||
}
|
||||
|
||||
let metrics_ctx = rpc_ctx.clone();
|
||||
let metrics_task = tokio::spawn(async move {
|
||||
launch_metrics_task(&metrics_ctx.metrics_cache, || {
|
||||
@@ -80,65 +72,45 @@ async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error
|
||||
.await
|
||||
});
|
||||
|
||||
let tor_health_ctx = rpc_ctx.clone();
|
||||
let tor_client = Client::builder()
|
||||
.proxy(
|
||||
Proxy::http(format!(
|
||||
"socks5h://{}:{}",
|
||||
rpc_ctx.tor_socks.ip(),
|
||||
rpc_ctx.tor_socks.port()
|
||||
))
|
||||
.with_kind(crate::ErrorKind::Network)?,
|
||||
)
|
||||
.build()
|
||||
.with_kind(crate::ErrorKind::Network)?;
|
||||
let tor_health_daemon = daemon(
|
||||
move || {
|
||||
let ctx = tor_health_ctx.clone();
|
||||
let client = tor_client.clone();
|
||||
async move { tor_health_check(&client, &ctx.net_controller.tor).await }
|
||||
},
|
||||
Duration::from_secs(300),
|
||||
rpc_ctx.shutdown.subscribe(),
|
||||
);
|
||||
crate::sound::CHIME.play().await?;
|
||||
|
||||
embassy::sound::CHIME.play().await?;
|
||||
|
||||
futures::try_join!(
|
||||
metrics_task
|
||||
.map_err(|e| Error::new(
|
||||
metrics_task
|
||||
.map_err(|e| {
|
||||
Error::new(
|
||||
eyre!("{}", e).wrap_err("Metrics daemon panicked!"),
|
||||
ErrorKind::Unknown
|
||||
))
|
||||
.map_ok(|_| tracing::debug!("Metrics daemon Shutdown")),
|
||||
tor_health_daemon
|
||||
.map_err(|e| Error::new(
|
||||
e.wrap_err("Tor Health daemon panicked!"),
|
||||
ErrorKind::Unknown
|
||||
))
|
||||
.map_ok(|_| tracing::debug!("Tor Health daemon Shutdown")),
|
||||
)?;
|
||||
ErrorKind::Unknown,
|
||||
)
|
||||
})
|
||||
.map_ok(|_| tracing::debug!("Metrics daemon Shutdown"))
|
||||
.await?;
|
||||
|
||||
let mut shutdown = shutdown_recv
|
||||
let shutdown = shutdown_recv
|
||||
.recv()
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Unknown)?;
|
||||
|
||||
sig_handler.abort();
|
||||
|
||||
if let Some(shutdown) = &mut shutdown {
|
||||
drop(shutdown.db_handle.take());
|
||||
}
|
||||
|
||||
(rpc_ctx, shutdown)
|
||||
};
|
||||
Ok::<_, Error>((rpc_ctx, server, shutdown))
|
||||
}
|
||||
.await?;
|
||||
server.shutdown().await;
|
||||
rpc_ctx.shutdown().await?;
|
||||
|
||||
tracing::info!("RPC Context is dropped");
|
||||
|
||||
Ok(shutdown)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let matches = clap::App::new("embassyd")
|
||||
pub fn main() {
|
||||
EmbassyLogger::init();
|
||||
|
||||
if !Path::new("/run/embassy/initialized").exists() {
|
||||
super::start_init::main();
|
||||
std::fs::write("/run/embassy/initialized", "").unwrap();
|
||||
}
|
||||
|
||||
let matches = clap::App::new("startd")
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
.short('c')
|
||||
@@ -147,8 +119,6 @@ fn main() {
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
EmbassyLogger::init();
|
||||
|
||||
let cfg_path = matches.value_of("config").map(|p| Path::new(p).to_owned());
|
||||
|
||||
let res = {
|
||||
@@ -160,12 +130,10 @@ fn main() {
|
||||
match inner_main(cfg_path.clone()).await {
|
||||
Ok(a) => Ok(a),
|
||||
Err(e) => {
|
||||
(|| async {
|
||||
async {
|
||||
tracing::error!("{}", e.source);
|
||||
tracing::debug!("{:?}", e.source);
|
||||
embassy::sound::BEETHOVEN.play().await?;
|
||||
#[cfg(feature = "avahi")]
|
||||
let _mdns = MdnsController::init().await?;
|
||||
crate::sound::BEETHOVEN.play().await?;
|
||||
let ctx = DiagnosticContext::init(
|
||||
cfg_path,
|
||||
if tokio::fs::metadata("/media/embassy/config/disk.guid")
|
||||
@@ -185,24 +153,21 @@ fn main() {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let embassy_ip_fqdn: ResourceFqdn = ResourceFqdn::IpAddr;
|
||||
let embassy_fqdn: ResourceFqdn = "embassy.local".parse()?;
|
||||
|
||||
let diag_ui_handler = diag_ui_file_router(ctx.clone()).await?;
|
||||
|
||||
let mut diag_http_server =
|
||||
EmbassyServiceHTTPServer::new([0, 0, 0, 0].into(), 80, None).await?;
|
||||
diag_http_server
|
||||
.add_svc_handler_mapping(embassy_ip_fqdn, diag_ui_handler.clone())
|
||||
.await?;
|
||||
diag_http_server
|
||||
.add_svc_handler_mapping(embassy_fqdn, diag_ui_handler)
|
||||
.await?;
|
||||
let server = WebServer::diagnostic(
|
||||
SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 80),
|
||||
ctx.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut shutdown = ctx.shutdown.subscribe();
|
||||
|
||||
shutdown.recv().await.with_kind(crate::ErrorKind::Unknown)
|
||||
})()
|
||||
let shutdown =
|
||||
shutdown.recv().await.with_kind(crate::ErrorKind::Unknown)?;
|
||||
|
||||
server.shutdown().await;
|
||||
|
||||
Ok::<_, Error>(shutdown)
|
||||
}
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use nix::sys::signal::Signal;
|
||||
use models::ImageId;
|
||||
use patch_db::HasModel;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
@@ -9,7 +9,7 @@ use tracing::instrument;
|
||||
use super::{Config, ConfigSpec};
|
||||
use crate::context::RpcContext;
|
||||
use crate::dependencies::Dependencies;
|
||||
use crate::id::ImageId;
|
||||
use crate::prelude::*;
|
||||
use crate::procedure::docker::DockerContainers;
|
||||
use crate::procedure::{PackageProcedure, ProcedureName};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
@@ -18,7 +18,7 @@ use crate::util::Version;
|
||||
use crate::volume::Volumes;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct ConfigRes {
|
||||
pub config: Option<Config>,
|
||||
@@ -26,28 +26,29 @@ pub struct ConfigRes {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct ConfigActions {
|
||||
pub get: PackageProcedure,
|
||||
pub set: PackageProcedure,
|
||||
}
|
||||
impl ConfigActions {
|
||||
#[instrument]
|
||||
#[instrument(skip_all)]
|
||||
pub fn validate(
|
||||
&self,
|
||||
container: &Option<DockerContainers>,
|
||||
_container: &Option<DockerContainers>,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
) -> Result<(), Error> {
|
||||
self.get
|
||||
.validate(container, eos_version, volumes, image_ids, true)
|
||||
.validate(eos_version, volumes, image_ids, true)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Config Get"))?;
|
||||
self.set
|
||||
.validate(container, eos_version, volumes, image_ids, true)
|
||||
.validate(eos_version, volumes, image_ids, true)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Config Set"))?;
|
||||
Ok(())
|
||||
}
|
||||
#[instrument(skip(ctx))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
@@ -71,7 +72,7 @@ impl ConfigActions {
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn set(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
@@ -99,7 +100,6 @@ impl ConfigActions {
|
||||
})
|
||||
})?;
|
||||
Ok(SetResult {
|
||||
signal: res.signal,
|
||||
depends_on: res
|
||||
.depends_on
|
||||
.into_iter()
|
||||
@@ -112,9 +112,5 @@ impl ConfigActions {
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct SetResult {
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "crate::util::serde::deserialize_from_str_opt")]
|
||||
#[serde(serialize_with = "crate::util::serde::serialize_display_opt")]
|
||||
pub signal: Option<Signal>,
|
||||
pub depends_on: BTreeMap<PackageId, BTreeSet<HealthCheckId>>,
|
||||
}
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::future::{BoxFuture, FutureExt};
|
||||
use indexmap::IndexSet;
|
||||
use itertools::Itertools;
|
||||
use patch_db::{DbHandle, LockReceipt, LockTarget, LockTargetId, LockType, Verifier};
|
||||
use rand::SeedableRng;
|
||||
use models::{ErrorKind, OptionExt};
|
||||
use patch_db::value::InternedString;
|
||||
use patch_db::Value;
|
||||
use regex::Regex;
|
||||
use rpc_toolkit::command;
|
||||
use serde_json::Value;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::{CurrentDependencies, CurrentDependencyInfo, CurrentDependents};
|
||||
use crate::dependencies::{
|
||||
add_dependent_to_current_dependents_lists, break_transitive, heal_all_dependents_transitive,
|
||||
BreakTransitiveReceipts, BreakageRes, Dependencies, DependencyConfig, DependencyError,
|
||||
DependencyErrors, DependencyReceipt, TaggedDependencyError, TryHealReceipts,
|
||||
};
|
||||
use crate::install::cleanup::{remove_from_current_dependents_lists, UpdateDependencyReceipts};
|
||||
use crate::procedure::docker::DockerContainers;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::display_none;
|
||||
use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat};
|
||||
use crate::Error;
|
||||
@@ -34,10 +27,10 @@ pub mod util;
|
||||
pub use spec::{ConfigSpec, Defaultable};
|
||||
use util::NumRange;
|
||||
|
||||
use self::action::{ConfigActions, ConfigRes};
|
||||
use self::spec::{ConfigPointerReceipts, PackagePointerSpec, ValueSpecPointer};
|
||||
use self::action::ConfigRes;
|
||||
use self::spec::ValueSpecPointer;
|
||||
|
||||
pub type Config = serde_json::Map<String, Value>;
|
||||
pub type Config = patch_db::value::InOMap<InternedString, Value>;
|
||||
pub trait TypeOf {
|
||||
fn type_of(&self) -> &'static str;
|
||||
}
|
||||
@@ -81,7 +74,7 @@ pub struct TimeoutError;
|
||||
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
pub struct NoMatchWithPath {
|
||||
pub path: Vec<String>,
|
||||
pub path: Vec<InternedString>,
|
||||
pub error: MatchError,
|
||||
}
|
||||
impl NoMatchWithPath {
|
||||
@@ -91,7 +84,7 @@ impl NoMatchWithPath {
|
||||
error,
|
||||
}
|
||||
}
|
||||
pub fn prepend(mut self, seg: String) -> Self {
|
||||
pub fn prepend(mut self, seg: InternedString) -> Self {
|
||||
self.path.push(seg);
|
||||
self
|
||||
}
|
||||
@@ -110,9 +103,9 @@ impl From<NoMatchWithPath> for Error {
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
pub enum MatchError {
|
||||
#[error("String {0:?} Does Not Match Pattern {1}")]
|
||||
Pattern(String, Regex),
|
||||
Pattern(Arc<String>, Regex),
|
||||
#[error("String {0:?} Is Not In Enum {1:?}")]
|
||||
Enum(String, IndexSet<String>),
|
||||
Enum(Arc<String>, IndexSet<String>),
|
||||
#[error("Field Is Not Nullable")]
|
||||
NotNullable,
|
||||
#[error("Length Mismatch: expected {0}, actual: {1}")]
|
||||
@@ -124,11 +117,11 @@ pub enum MatchError {
|
||||
#[error("Number Is Not Integral: {0}")]
|
||||
NonIntegral(f64),
|
||||
#[error("Variant {0:?} Is Not In Union {1:?}")]
|
||||
Union(String, IndexSet<String>),
|
||||
Union(Arc<String>, IndexSet<String>),
|
||||
#[error("Variant Is Missing Tag {0:?}")]
|
||||
MissingTag(String),
|
||||
MissingTag(InternedString),
|
||||
#[error("Property {0:?} Of Variant {1:?} Conflicts With Union Tag")]
|
||||
PropertyMatchesUnionTag(String, String),
|
||||
PropertyMatchesUnionTag(InternedString, String),
|
||||
#[error("Name of Property {0:?} Conflicts With Map Tag Name")]
|
||||
PropertyNameMatchesMapTag(String),
|
||||
#[error("Pointer Is Invalid: {0}")]
|
||||
@@ -164,57 +157,8 @@ pub fn config(#[arg] id: PackageId) -> Result<PackageId, Error> {
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub struct ConfigGetReceipts {
|
||||
manifest_volumes: LockReceipt<crate::volume::Volumes, ()>,
|
||||
manifest_version: LockReceipt<crate::util::Version, ()>,
|
||||
manifest_config: LockReceipt<Option<ConfigActions>, ()>,
|
||||
}
|
||||
|
||||
impl ConfigGetReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks, id);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<LockTargetId>,
|
||||
id: &PackageId,
|
||||
) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
|
||||
let manifest_version = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.manifest().version())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let manifest_volumes = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.manifest().volumes())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let manifest_config = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.manifest().config())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
manifest_volumes: manifest_volumes.verify(skeleton_key)?,
|
||||
manifest_version: manifest_version.verify(skeleton_key)?,
|
||||
manifest_config: manifest_config.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[command(display(display_serializable))]
|
||||
#[instrument(skip(ctx))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get(
|
||||
#[context] ctx: RpcContext,
|
||||
#[parent_data] id: PackageId,
|
||||
@@ -222,16 +166,21 @@ pub async fn get(
|
||||
#[arg(long = "format")]
|
||||
format: Option<IoFormat>,
|
||||
) -> Result<ConfigRes, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let receipts = ConfigGetReceipts::new(&mut db, &id).await?;
|
||||
let action = receipts
|
||||
.manifest_config
|
||||
.get(&mut db)
|
||||
.await?
|
||||
let db = ctx.db.peek().await;
|
||||
let manifest = db
|
||||
.as_package_data()
|
||||
.as_idx(&id)
|
||||
.or_not_found(&id)?
|
||||
.as_installed()
|
||||
.or_not_found(&id)?
|
||||
.as_manifest();
|
||||
let action = manifest
|
||||
.as_config()
|
||||
.de()?
|
||||
.ok_or_else(|| Error::new(eyre!("{} has no config", id), crate::ErrorKind::NotFound))?;
|
||||
|
||||
let volumes = receipts.manifest_volumes.get(&mut db).await?;
|
||||
let version = receipts.manifest_version.get(&mut db).await?;
|
||||
let volumes = manifest.as_volumes().de()?;
|
||||
let version = manifest.as_version().de()?;
|
||||
action.get(&ctx, &id, &version, &volumes).await
|
||||
}
|
||||
|
||||
@@ -240,7 +189,7 @@ pub async fn get(
|
||||
display(display_none),
|
||||
metadata(sync_db = true)
|
||||
)]
|
||||
#[instrument]
|
||||
#[instrument(skip_all)]
|
||||
pub fn set(
|
||||
#[parent_data] id: PackageId,
|
||||
#[allow(unused_variables)]
|
||||
@@ -252,586 +201,87 @@ pub fn set(
|
||||
Ok((id, config, timeout.map(|d| *d)))
|
||||
}
|
||||
|
||||
/// So, the new locking finds all the possible locks and lifts them up into a bundle of locks.
|
||||
/// Then this bundle will be passed down into the functions that will need to touch the db, and
|
||||
/// instead of doing the locks down in the system, we have already done the locks and can
|
||||
/// do the operation on the db.
|
||||
/// An UnlockedLock has two types, the type of setting and getting from the db, and the second type
|
||||
/// is the keys that we need to insert on getting/setting because we have included wild cards into the paths.
|
||||
pub struct ConfigReceipts {
|
||||
pub dependency_receipt: DependencyReceipt,
|
||||
pub config_receipts: ConfigPointerReceipts,
|
||||
pub update_dependency_receipts: UpdateDependencyReceipts,
|
||||
pub try_heal_receipts: TryHealReceipts,
|
||||
pub break_transitive_receipts: BreakTransitiveReceipts,
|
||||
configured: LockReceipt<bool, String>,
|
||||
config_actions: LockReceipt<ConfigActions, String>,
|
||||
dependencies: LockReceipt<Dependencies, String>,
|
||||
volumes: LockReceipt<crate::volume::Volumes, String>,
|
||||
version: LockReceipt<crate::util::Version, String>,
|
||||
manifest: LockReceipt<Manifest, String>,
|
||||
system_pointers: LockReceipt<Vec<spec::SystemPointerSpec>, String>,
|
||||
pub current_dependents: LockReceipt<CurrentDependents, String>,
|
||||
pub current_dependencies: LockReceipt<CurrentDependencies, String>,
|
||||
dependency_errors: LockReceipt<DependencyErrors, String>,
|
||||
manifest_dependencies_config: LockReceipt<DependencyConfig, (String, String)>,
|
||||
docker_containers: LockReceipt<DockerContainers, String>,
|
||||
}
|
||||
|
||||
impl ConfigReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(locks: &mut Vec<LockTargetId>) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
|
||||
let dependency_receipt = DependencyReceipt::setup(locks);
|
||||
let config_receipts = ConfigPointerReceipts::setup(locks);
|
||||
let update_dependency_receipts = UpdateDependencyReceipts::setup(locks);
|
||||
let break_transitive_receipts = BreakTransitiveReceipts::setup(locks);
|
||||
let try_heal_receipts = TryHealReceipts::setup(locks);
|
||||
|
||||
let configured: LockTarget<bool, String> = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.status().configured())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let config_actions = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.and_then(|x| x.manifest().config())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let dependencies = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest().dependencies())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let volumes = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest().volumes())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let version = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest().version())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let manifest = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let system_pointers = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.system_pointers())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let current_dependents = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.current_dependents())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let current_dependencies = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.current_dependencies())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let dependency_errors = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.status().dependency_errors())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let manifest_dependencies_config = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.and_then(|x| x.manifest().dependencies().star().config())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let docker_containers = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.and_then(|x| x.manifest().containers())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
dependency_receipt: dependency_receipt(skeleton_key)?,
|
||||
config_receipts: config_receipts(skeleton_key)?,
|
||||
try_heal_receipts: try_heal_receipts(skeleton_key)?,
|
||||
break_transitive_receipts: break_transitive_receipts(skeleton_key)?,
|
||||
update_dependency_receipts: update_dependency_receipts(skeleton_key)?,
|
||||
configured: configured.verify(skeleton_key)?,
|
||||
config_actions: config_actions.verify(skeleton_key)?,
|
||||
dependencies: dependencies.verify(skeleton_key)?,
|
||||
volumes: volumes.verify(skeleton_key)?,
|
||||
version: version.verify(skeleton_key)?,
|
||||
manifest: manifest.verify(skeleton_key)?,
|
||||
system_pointers: system_pointers.verify(skeleton_key)?,
|
||||
current_dependents: current_dependents.verify(skeleton_key)?,
|
||||
current_dependencies: current_dependencies.verify(skeleton_key)?,
|
||||
dependency_errors: dependency_errors.verify(skeleton_key)?,
|
||||
manifest_dependencies_config: manifest_dependencies_config.verify(skeleton_key)?,
|
||||
docker_containers: docker_containers.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[command(rename = "dry", display(display_serializable))]
|
||||
#[instrument(skip(ctx))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn set_dry(
|
||||
#[context] ctx: RpcContext,
|
||||
#[parent_data] (id, config, timeout): (PackageId, Option<Config>, Option<Duration>),
|
||||
) -> Result<BreakageRes, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let mut tx = db.begin().await?;
|
||||
let mut breakages = BTreeMap::new();
|
||||
let locks = ConfigReceipts::new(&mut tx).await?;
|
||||
configure(
|
||||
&ctx,
|
||||
&mut tx,
|
||||
&id,
|
||||
config,
|
||||
&timeout,
|
||||
true,
|
||||
&mut BTreeMap::new(),
|
||||
&mut breakages,
|
||||
&locks,
|
||||
)
|
||||
.await?;
|
||||
) -> Result<BTreeMap<PackageId, String>, Error> {
|
||||
let breakages = BTreeMap::new();
|
||||
let overrides = Default::default();
|
||||
|
||||
locks.configured.set(&mut tx, true, &id).await?;
|
||||
tx.abort().await?;
|
||||
Ok(BreakageRes(breakages))
|
||||
let configure_context = ConfigureContext {
|
||||
breakages,
|
||||
timeout,
|
||||
config,
|
||||
dry_run: true,
|
||||
overrides,
|
||||
};
|
||||
let breakages = configure(&ctx, &id, configure_context).await?;
|
||||
|
||||
Ok(breakages)
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx))]
|
||||
pub struct ConfigureContext {
|
||||
pub breakages: BTreeMap<PackageId, String>,
|
||||
pub timeout: Option<Duration>,
|
||||
pub config: Option<Config>,
|
||||
pub overrides: BTreeMap<PackageId, Config>,
|
||||
pub dry_run: bool,
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn set_impl(
|
||||
ctx: RpcContext,
|
||||
(id, config, timeout): (PackageId, Option<Config>, Option<Duration>),
|
||||
) -> Result<(), Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let mut tx = db.begin().await?;
|
||||
let mut breakages = BTreeMap::new();
|
||||
let locks = ConfigReceipts::new(&mut tx).await?;
|
||||
configure(
|
||||
&ctx,
|
||||
&mut tx,
|
||||
&id,
|
||||
let breakages = BTreeMap::new();
|
||||
let overrides = Default::default();
|
||||
|
||||
let configure_context = ConfigureContext {
|
||||
breakages,
|
||||
timeout,
|
||||
config,
|
||||
&timeout,
|
||||
false,
|
||||
&mut BTreeMap::new(),
|
||||
&mut breakages,
|
||||
&locks,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
dry_run: false,
|
||||
overrides,
|
||||
};
|
||||
configure(&ctx, &id, configure_context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, db, receipts))]
|
||||
pub async fn configure<'a, Db: DbHandle>(
|
||||
#[instrument(skip_all)]
|
||||
pub async fn configure(
|
||||
ctx: &RpcContext,
|
||||
db: &'a mut Db,
|
||||
id: &PackageId,
|
||||
config: Option<Config>,
|
||||
timeout: &Option<Duration>,
|
||||
dry_run: bool,
|
||||
overrides: &mut BTreeMap<PackageId, Config>,
|
||||
breakages: &mut BTreeMap<PackageId, TaggedDependencyError>,
|
||||
receipts: &ConfigReceipts,
|
||||
) -> Result<(), Error> {
|
||||
configure_rec(
|
||||
ctx, db, id, config, timeout, dry_run, overrides, breakages, receipts,
|
||||
)
|
||||
.await?;
|
||||
receipts.configured.set(db, true, &id).await?;
|
||||
Ok(())
|
||||
configure_context: ConfigureContext,
|
||||
) -> Result<BTreeMap<PackageId, String>, Error> {
|
||||
let db = ctx.db.peek().await;
|
||||
let package = db
|
||||
.as_package_data()
|
||||
.as_idx(id)
|
||||
.or_not_found(&id)?
|
||||
.as_installed()
|
||||
.or_not_found(&id)?;
|
||||
let version = package.as_manifest().as_version().de()?;
|
||||
ctx.managers
|
||||
.get(&(id.clone(), version.clone()))
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("There is no manager running for {id:?} and {version:?}"),
|
||||
ErrorKind::Unknown,
|
||||
)
|
||||
})?
|
||||
.configure(configure_context)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, db, receipts))]
|
||||
pub fn configure_rec<'a, Db: DbHandle>(
|
||||
ctx: &'a RpcContext,
|
||||
db: &'a mut Db,
|
||||
id: &'a PackageId,
|
||||
config: Option<Config>,
|
||||
timeout: &'a Option<Duration>,
|
||||
dry_run: bool,
|
||||
overrides: &'a mut BTreeMap<PackageId, Config>,
|
||||
breakages: &'a mut BTreeMap<PackageId, TaggedDependencyError>,
|
||||
receipts: &'a ConfigReceipts,
|
||||
) -> BoxFuture<'a, Result<(), Error>> {
|
||||
async move {
|
||||
// fetch data from db
|
||||
let action = receipts
|
||||
.config_actions
|
||||
.get(db, id)
|
||||
.await?
|
||||
.ok_or_else(not_found)?;
|
||||
let dependencies = receipts
|
||||
.dependencies
|
||||
.get(db, id)
|
||||
.await?
|
||||
.ok_or_else(not_found)?;
|
||||
let volumes = receipts.volumes.get(db, id).await?.ok_or_else(not_found)?;
|
||||
let is_needs_config = !receipts
|
||||
.configured
|
||||
.get(db, id)
|
||||
.await?
|
||||
.ok_or_else(not_found)?;
|
||||
let version = receipts.version.get(db, id).await?.ok_or_else(not_found)?;
|
||||
|
||||
// get current config and current spec
|
||||
let ConfigRes {
|
||||
config: old_config,
|
||||
spec,
|
||||
} = action.get(ctx, id, &version, &volumes).await?;
|
||||
|
||||
// determine new config to use
|
||||
let mut config = if let Some(config) = config.or_else(|| old_config.clone()) {
|
||||
config
|
||||
} else {
|
||||
spec.gen(&mut rand::rngs::StdRng::from_entropy(), timeout)?
|
||||
};
|
||||
|
||||
let manifest = receipts.manifest.get(db, id).await?.ok_or_else(not_found)?;
|
||||
|
||||
spec.validate(&manifest)?;
|
||||
spec.matches(&config)?; // check that new config matches spec
|
||||
spec.update(
|
||||
ctx,
|
||||
db,
|
||||
&manifest,
|
||||
&*overrides,
|
||||
&mut config,
|
||||
&receipts.config_receipts,
|
||||
macro_rules! not_found {
|
||||
($x:expr) => {
|
||||
crate::Error::new(
|
||||
color_eyre::eyre::eyre!("Could not find {} at {}:{}", $x, module_path!(), line!()),
|
||||
crate::ErrorKind::Incoherent,
|
||||
)
|
||||
.await?; // dereference pointers in the new config
|
||||
|
||||
// create backreferences to pointers
|
||||
let mut sys = receipts
|
||||
.system_pointers
|
||||
.get(db, &id)
|
||||
.await?
|
||||
.ok_or_else(not_found)?;
|
||||
sys.truncate(0);
|
||||
let mut current_dependencies: CurrentDependencies = CurrentDependencies(
|
||||
dependencies
|
||||
.0
|
||||
.iter()
|
||||
.filter_map(|(id, info)| {
|
||||
if info.requirement.required() {
|
||||
Some((id.clone(), CurrentDependencyInfo::default()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
for ptr in spec.pointers(&config)? {
|
||||
match ptr {
|
||||
ValueSpecPointer::Package(pkg_ptr) => {
|
||||
if let Some(current_dependency) =
|
||||
current_dependencies.0.get_mut(pkg_ptr.package_id())
|
||||
{
|
||||
current_dependency.pointers.push(pkg_ptr);
|
||||
} else {
|
||||
current_dependencies.0.insert(
|
||||
pkg_ptr.package_id().to_owned(),
|
||||
CurrentDependencyInfo {
|
||||
pointers: vec![pkg_ptr],
|
||||
health_checks: BTreeSet::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
ValueSpecPointer::System(s) => sys.push(s),
|
||||
}
|
||||
}
|
||||
receipts.system_pointers.set(db, sys, &id).await?;
|
||||
|
||||
let signal = if !dry_run {
|
||||
// run config action
|
||||
let res = action
|
||||
.set(ctx, id, &version, &dependencies, &volumes, &config)
|
||||
.await?;
|
||||
|
||||
// track dependencies with no pointers
|
||||
for (package_id, health_checks) in res.depends_on.into_iter() {
|
||||
if let Some(current_dependency) = current_dependencies.0.get_mut(&package_id) {
|
||||
current_dependency.health_checks.extend(health_checks);
|
||||
} else {
|
||||
current_dependencies.0.insert(
|
||||
package_id,
|
||||
CurrentDependencyInfo {
|
||||
pointers: Vec::new(),
|
||||
health_checks,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// track dependency health checks
|
||||
current_dependencies = current_dependencies.map(|x| {
|
||||
x.into_iter()
|
||||
.filter(|(dep_id, _)| {
|
||||
if dep_id != id && !manifest.dependencies.0.contains_key(dep_id) {
|
||||
tracing::warn!("Illegal dependency specified: {}", dep_id);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
res.signal
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// update dependencies
|
||||
let prev_current_dependencies = receipts
|
||||
.current_dependencies
|
||||
.get(db, &id)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
remove_from_current_dependents_lists(
|
||||
db,
|
||||
id,
|
||||
&prev_current_dependencies,
|
||||
&receipts.current_dependents,
|
||||
)
|
||||
.await?; // remove previous
|
||||
add_dependent_to_current_dependents_lists(
|
||||
db,
|
||||
id,
|
||||
¤t_dependencies,
|
||||
&receipts.current_dependents,
|
||||
)
|
||||
.await?; // add new
|
||||
current_dependencies.0.remove(id);
|
||||
receipts
|
||||
.current_dependencies
|
||||
.set(db, current_dependencies.clone(), &id)
|
||||
.await?;
|
||||
|
||||
let errs = receipts
|
||||
.dependency_errors
|
||||
.get(db, &id)
|
||||
.await?
|
||||
.ok_or_else(not_found)?;
|
||||
tracing::warn!("Dependency Errors: {:?}", errs);
|
||||
let errs = DependencyErrors::init(
|
||||
ctx,
|
||||
db,
|
||||
&manifest,
|
||||
¤t_dependencies,
|
||||
&receipts.dependency_receipt.try_heal,
|
||||
)
|
||||
.await?;
|
||||
receipts.dependency_errors.set(db, errs, &id).await?;
|
||||
|
||||
// cache current config for dependents
|
||||
overrides.insert(id.clone(), config.clone());
|
||||
|
||||
// handle dependents
|
||||
let dependents = receipts
|
||||
.current_dependents
|
||||
.get(db, id)
|
||||
.await?
|
||||
.ok_or_else(not_found)?;
|
||||
let prev = if is_needs_config { None } else { old_config }
|
||||
.map(Value::Object)
|
||||
.unwrap_or_default();
|
||||
let next = Value::Object(config.clone());
|
||||
for (dependent, dep_info) in dependents.0.iter().filter(|(dep_id, _)| dep_id != &id) {
|
||||
let dependent_container = receipts.docker_containers.get(db, &dependent).await?;
|
||||
let dependent_container = &dependent_container;
|
||||
// check if config passes dependent check
|
||||
if let Some(cfg) = receipts
|
||||
.manifest_dependencies_config
|
||||
.get(db, (&dependent, &id))
|
||||
.await?
|
||||
{
|
||||
let manifest = receipts
|
||||
.manifest
|
||||
.get(db, &dependent)
|
||||
.await?
|
||||
.ok_or_else(not_found)?;
|
||||
if let Err(error) = cfg
|
||||
.check(
|
||||
ctx,
|
||||
dependent_container,
|
||||
dependent,
|
||||
&manifest.version,
|
||||
&manifest.volumes,
|
||||
id,
|
||||
&config,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
let dep_err = DependencyError::ConfigUnsatisfied { error };
|
||||
break_transitive(
|
||||
db,
|
||||
dependent,
|
||||
id,
|
||||
dep_err,
|
||||
breakages,
|
||||
&receipts.break_transitive_receipts,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// handle backreferences
|
||||
for ptr in &dep_info.pointers {
|
||||
if let PackagePointerSpec::Config(cfg_ptr) = ptr {
|
||||
if cfg_ptr.select(&next) != cfg_ptr.select(&prev) {
|
||||
if let Err(e) = configure_rec(
|
||||
ctx, db, dependent, None, timeout, dry_run, overrides, breakages,
|
||||
receipts,
|
||||
)
|
||||
.await
|
||||
{
|
||||
if e.kind == crate::ErrorKind::ConfigRulesViolation {
|
||||
break_transitive(
|
||||
db,
|
||||
dependent,
|
||||
id,
|
||||
DependencyError::ConfigUnsatisfied {
|
||||
error: format!("{}", e),
|
||||
},
|
||||
breakages,
|
||||
&receipts.break_transitive_receipts,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
heal_all_dependents_transitive(ctx, db, id, &receipts.dependency_receipt).await?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(signal) = signal {
|
||||
match ctx.managers.get(&(id.clone(), version.clone())).await {
|
||||
None => {
|
||||
// in theory this should never happen, which indicates this function should be moved behind the
|
||||
// Manager interface
|
||||
return Err(Error::new(
|
||||
eyre!("Manager Not Found for package being configured"),
|
||||
crate::ErrorKind::Incoherent,
|
||||
));
|
||||
}
|
||||
Some(m) => {
|
||||
m.signal(&signal).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
#[instrument]
|
||||
pub fn not_found() -> Error {
|
||||
Error::new(eyre!("Could not find"), crate::ErrorKind::Incoherent)
|
||||
}
|
||||
|
||||
/// We want to have a double check that the paths are what we expect them to be.
|
||||
/// Found that earlier the paths where not what we expected them to be.
|
||||
#[tokio::test]
|
||||
async fn ensure_creation_of_config_paths_makes_sense() {
|
||||
let mut fake = patch_db::test_utils::NoOpDb();
|
||||
let config_locks = ConfigReceipts::new(&mut fake).await.unwrap();
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.configured.lock.glob),
|
||||
"/package-data/*/installed/status/configured"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.config_actions.lock.glob),
|
||||
"/package-data/*/installed/manifest/config"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.dependencies.lock.glob),
|
||||
"/package-data/*/installed/manifest/dependencies"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.volumes.lock.glob),
|
||||
"/package-data/*/installed/manifest/volumes"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.version.lock.glob),
|
||||
"/package-data/*/installed/manifest/version"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.volumes.lock.glob),
|
||||
"/package-data/*/installed/manifest/volumes"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.manifest.lock.glob),
|
||||
"/package-data/*/installed/manifest"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.manifest.lock.glob),
|
||||
"/package-data/*/installed/manifest"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.system_pointers.lock.glob),
|
||||
"/package-data/*/installed/system-pointers"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.current_dependents.lock.glob),
|
||||
"/package-data/*/installed/current-dependents"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.dependency_errors.lock.glob),
|
||||
"/package-data/*/installed/status/dependency-errors"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.manifest_dependencies_config.lock.glob),
|
||||
"/package-data/*/installed/manifest/dependencies/*/config"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.system_pointers.lock.glob),
|
||||
"/package-data/*/installed/system-pointers"
|
||||
);
|
||||
};
|
||||
}
|
||||
pub(crate) use not_found;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
@@ -9,15 +9,16 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use imbl::Vector;
|
||||
use imbl_value::InternedString;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use itertools::Itertools;
|
||||
use jsonpath_lib::Compiled as CompiledJsonPath;
|
||||
use patch_db::{DbHandle, LockReceipt, LockType};
|
||||
use patch_db::value::{Number, Value};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use regex::Regex;
|
||||
use serde::de::{MapAccess, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json::{Number, Value};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use super::util::{self, CharSet, NumRange, UniqueBy, STATIC_NULL};
|
||||
@@ -25,8 +26,9 @@ use super::{Config, MatchError, NoMatchWithPath, TimeoutError, TypeOf};
|
||||
use crate::config::ConfigurationError;
|
||||
use crate::context::RpcContext;
|
||||
use crate::net::interface::InterfaceId;
|
||||
use crate::net::keys::Key;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::Error;
|
||||
|
||||
// Config Value Specifications
|
||||
#[async_trait]
|
||||
@@ -38,14 +40,12 @@ pub trait ValueSpec {
|
||||
// since not all inVariant can be checked by the type
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath>;
|
||||
// update is to fill in values for environment pointers recursively
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError>;
|
||||
// returns all pointers that are live in the provided config
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath>;
|
||||
@@ -105,7 +105,7 @@ where
|
||||
rng: &mut R,
|
||||
timeout: &Option<Duration>,
|
||||
) -> Result<Value, Self::Error> {
|
||||
self.gen_with(self.default_spec().borrow(), rng, timeout)
|
||||
self.gen_with(self.default_spec(), rng, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,17 +155,15 @@ where
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
self.inner.validate(manifest)
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
self.inner
|
||||
.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.update(ctx, manifest, config_overrides, value)
|
||||
.await
|
||||
}
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -200,17 +198,15 @@ where
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
self.inner.validate(manifest)
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
self.inner
|
||||
.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.update(ctx, manifest, config_overrides, value)
|
||||
.await
|
||||
}
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -278,17 +274,15 @@ where
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
self.inner.validate(manifest)
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
self.inner
|
||||
.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.update(ctx, manifest, config_overrides, value)
|
||||
.await
|
||||
}
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -393,48 +387,22 @@ impl ValueSpec for ValueSpecAny {
|
||||
ValueSpecAny::Pointer(a) => a.validate(manifest),
|
||||
}
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
match self {
|
||||
ValueSpecAny::Boolean(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::Enum(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::List(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::Number(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::Object(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::String(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::Union(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::Pointer(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::Boolean(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::Enum(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::List(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::Number(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::Object(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::String(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::Union(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::Pointer(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
}
|
||||
}
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -512,14 +480,12 @@ impl ValueSpec for ValueSpecBoolean {
|
||||
fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
Ok(())
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
_ctx: &RpcContext,
|
||||
_db: &mut Db,
|
||||
_manifest: &Manifest,
|
||||
_config_overrides: &BTreeMap<PackageId, Config>,
|
||||
_value: &mut Value,
|
||||
_receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -583,7 +549,7 @@ impl ValueSpec for ValueSpecEnum {
|
||||
fn matches(&self, val: &Value) -> Result<(), NoMatchWithPath> {
|
||||
match val {
|
||||
Value::String(b) => {
|
||||
if self.values.contains(b) {
|
||||
if self.values.contains(&**b) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(NoMatchWithPath::new(MatchError::Enum(
|
||||
@@ -602,14 +568,12 @@ impl ValueSpec for ValueSpecEnum {
|
||||
fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
Ok(())
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
_ctx: &RpcContext,
|
||||
_db: &mut Db,
|
||||
_manifest: &Manifest,
|
||||
_config_overrides: &BTreeMap<PackageId, Config>,
|
||||
_value: &mut Value,
|
||||
_receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -627,7 +591,7 @@ impl ValueSpec for ValueSpecEnum {
|
||||
}
|
||||
}
|
||||
impl DefaultableWith for ValueSpecEnum {
|
||||
type DefaultSpec = String;
|
||||
type DefaultSpec = Arc<String>;
|
||||
type Error = crate::util::Never;
|
||||
|
||||
fn gen_with<R: Rng + CryptoRng + Sync + Send + Send>(
|
||||
@@ -665,13 +629,13 @@ where
|
||||
.map(|(i, v)| {
|
||||
self.spec
|
||||
.matches(v)
|
||||
.map_err(|e| e.prepend(format!("{}", i)))?;
|
||||
.map_err(|e| e.prepend(InternedString::from_display(&i)))?;
|
||||
if l.iter()
|
||||
.enumerate()
|
||||
.any(|(i2, v2)| i != i2 && self.spec.eq(v, v2))
|
||||
{
|
||||
Err(NoMatchWithPath::new(MatchError::ListUniquenessViolation)
|
||||
.prepend(format!("{}", i)))
|
||||
.prepend(InternedString::from_display(&i)))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -689,25 +653,19 @@ where
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
self.spec.validate(manifest)
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
if let Value::Array(ref mut ls) = value {
|
||||
for (i, val) in ls.into_iter().enumerate() {
|
||||
match self
|
||||
.spec
|
||||
.update(ctx, db, manifest, config_overrides, val, receipts)
|
||||
.await
|
||||
{
|
||||
Err(ConfigurationError::NoMatch(e)) => {
|
||||
Err(ConfigurationError::NoMatch(e.prepend(format!("{}", i))))
|
||||
}
|
||||
for (i, val) in ls.iter_mut().enumerate() {
|
||||
match self.spec.update(ctx, manifest, config_overrides, val).await {
|
||||
Err(ConfigurationError::NoMatch(e)) => Err(ConfigurationError::NoMatch(
|
||||
e.prepend(InternedString::from_display(&i)),
|
||||
)),
|
||||
a => a,
|
||||
}?;
|
||||
}
|
||||
@@ -754,9 +712,9 @@ where
|
||||
rng: &mut R,
|
||||
timeout: &Option<Duration>,
|
||||
) -> Result<Value, Self::Error> {
|
||||
let mut res = Vec::new();
|
||||
let mut res = Vector::new();
|
||||
for spec_member in spec.iter() {
|
||||
res.push(self.spec.gen_with(spec_member, rng, timeout)?);
|
||||
res.push_back(self.spec.gen_with(spec_member, rng, timeout)?);
|
||||
}
|
||||
Ok(Value::Array(res))
|
||||
}
|
||||
@@ -797,36 +755,19 @@ impl ValueSpec for ValueSpecList {
|
||||
ValueSpecList::Union(a) => a.validate(manifest),
|
||||
}
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
match self {
|
||||
ValueSpecList::Enum(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecList::Number(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecList::Object(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecList::String(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecList::Union(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecList::Enum(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecList::Number(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecList::Object(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecList::String(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecList::Union(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
}
|
||||
}
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -884,7 +825,7 @@ impl Defaultable for ValueSpecList {
|
||||
)
|
||||
.contains(&ret.len())
|
||||
{
|
||||
ret.push(
|
||||
ret.push_back(
|
||||
a.inner
|
||||
.inner
|
||||
.spec
|
||||
@@ -940,14 +881,12 @@ impl ValueSpec for ValueSpecNumber {
|
||||
fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
Ok(())
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
_ctx: &RpcContext,
|
||||
_db: &mut Db,
|
||||
_manifest: &Manifest,
|
||||
_config_overrides: &BTreeMap<PackageId, Config>,
|
||||
_value: &mut Value,
|
||||
_receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -1004,19 +943,15 @@ impl ValueSpec for ValueSpecObject {
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
self.spec.validate(manifest)
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
if let Value::Object(o) = value {
|
||||
self.spec
|
||||
.update(ctx, db, manifest, config_overrides, o, receipts)
|
||||
.await
|
||||
self.spec.update(ctx, manifest, config_overrides, o).await
|
||||
} else {
|
||||
Err(ConfigurationError::NoMatch(NoMatchWithPath::new(
|
||||
MatchError::InvalidType("object", value.type_of()),
|
||||
@@ -1073,11 +1008,11 @@ impl Defaultable for ValueSpecObject {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct ConfigSpec(pub IndexMap<String, ValueSpecAny>);
|
||||
pub struct ConfigSpec(pub IndexMap<InternedString, ValueSpecAny>);
|
||||
impl ConfigSpec {
|
||||
pub fn matches(&self, value: &Config) -> Result<(), NoMatchWithPath> {
|
||||
for (key, val) in self.0.iter() {
|
||||
if let Some(v) = value.get(key) {
|
||||
if let Some(v) = value.get(&**key) {
|
||||
val.matches(v).map_err(|e| e.prepend(key.clone()))?;
|
||||
} else {
|
||||
val.matches(&Value::Null)
|
||||
@@ -1107,27 +1042,21 @@ impl ConfigSpec {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update<Db: DbHandle>(
|
||||
pub async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
cfg: &mut Config,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
for (k, vs) in self.0.iter() {
|
||||
match cfg.get_mut(k) {
|
||||
None => {
|
||||
let mut v = Value::Null;
|
||||
vs.update(ctx, db, manifest, config_overrides, &mut v, receipts)
|
||||
.await?;
|
||||
vs.update(ctx, manifest, config_overrides, &mut v).await?;
|
||||
cfg.insert(k.clone(), v);
|
||||
}
|
||||
Some(v) => match vs
|
||||
.update(ctx, db, manifest, config_overrides, v, receipts)
|
||||
.await
|
||||
{
|
||||
Some(v) => match vs.update(ctx, manifest, config_overrides, v).await {
|
||||
Err(ConfigurationError::NoMatch(e)) => {
|
||||
Err(ConfigurationError::NoMatch(e.prepend(k.clone())))
|
||||
}
|
||||
@@ -1246,7 +1175,7 @@ impl<'de> Deserialize<'de> for ValueSpecString {
|
||||
})
|
||||
}
|
||||
}
|
||||
const FIELDS: &'static [&'static str] = &[
|
||||
const FIELDS: &[&str] = &[
|
||||
"pattern",
|
||||
"pattern-description",
|
||||
"textarea",
|
||||
@@ -1267,7 +1196,7 @@ impl ValueSpec for ValueSpecString {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(NoMatchWithPath::new(MatchError::Pattern(
|
||||
s.to_owned(),
|
||||
s.clone(),
|
||||
pattern.pattern.clone(),
|
||||
)))
|
||||
}
|
||||
@@ -1285,14 +1214,12 @@ impl ValueSpec for ValueSpecString {
|
||||
fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
Ok(())
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
_ctx: &RpcContext,
|
||||
_db: &mut Db,
|
||||
_manifest: &Manifest,
|
||||
_config_overrides: &BTreeMap<PackageId, Config>,
|
||||
_value: &mut Value,
|
||||
_receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -1351,11 +1278,11 @@ pub enum DefaultString {
|
||||
Entropy(Entropy),
|
||||
}
|
||||
impl DefaultString {
|
||||
pub fn gen<R: Rng + CryptoRng + Sync + Send>(&self, rng: &mut R) -> String {
|
||||
match self {
|
||||
pub fn gen<R: Rng + CryptoRng + Sync + Send>(&self, rng: &mut R) -> Arc<String> {
|
||||
Arc::new(match self {
|
||||
DefaultString::Literal(s) => s.clone(),
|
||||
DefaultString::Entropy(e) => e.gen(rng),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1379,7 +1306,7 @@ impl Entropy {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct UnionTag {
|
||||
pub id: String,
|
||||
pub id: InternedString,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub variant_names: BTreeMap<String, String>,
|
||||
@@ -1400,7 +1327,7 @@ impl<'de> serde::de::Deserialize<'de> for ValueSpecUnion {
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[serde(untagged)]
|
||||
pub enum _UnionTag {
|
||||
Old(String),
|
||||
Old(InternedString),
|
||||
New(UnionTag),
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
@@ -1418,7 +1345,7 @@ impl<'de> serde::de::Deserialize<'de> for ValueSpecUnion {
|
||||
tag: match u.tag {
|
||||
_UnionTag::Old(id) => UnionTag {
|
||||
id: id.clone(),
|
||||
name: id,
|
||||
name: id.to_string(),
|
||||
description: None,
|
||||
variant_names: u
|
||||
.variants
|
||||
@@ -1460,10 +1387,10 @@ impl ValueSpec for ValueSpecUnion {
|
||||
fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> {
|
||||
match value {
|
||||
Value::Object(o) => {
|
||||
if let Some(Value::String(ref tag)) = o.get(&self.tag.id) {
|
||||
if let Some(obj_spec) = self.variants.get(tag) {
|
||||
if let Some(Value::String(ref tag)) = o.get(&*self.tag.id) {
|
||||
if let Some(obj_spec) = self.variants.get(&**tag) {
|
||||
let mut without_tag = o.clone();
|
||||
without_tag.remove(&self.tag.id);
|
||||
without_tag.remove(&*self.tag.id);
|
||||
obj_spec.matches(&without_tag)
|
||||
} else {
|
||||
Err(NoMatchWithPath::new(MatchError::Union(
|
||||
@@ -1486,7 +1413,7 @@ impl ValueSpec for ValueSpecUnion {
|
||||
}
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
for (name, variant) in &self.variants {
|
||||
if variant.0.get(&self.tag.id).is_some() {
|
||||
if variant.0.get(&*self.tag.id).is_some() {
|
||||
return Err(NoMatchWithPath::new(MatchError::PropertyMatchesUnionTag(
|
||||
self.tag.id.clone(),
|
||||
name.clone(),
|
||||
@@ -1496,28 +1423,23 @@ impl ValueSpec for ValueSpecUnion {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
if let Value::Object(o) = value {
|
||||
match o.get(&self.tag.id) {
|
||||
match o.get(&*self.tag.id) {
|
||||
None => Err(ConfigurationError::NoMatch(NoMatchWithPath::new(
|
||||
MatchError::MissingTag(self.tag.id.clone()),
|
||||
))),
|
||||
Some(Value::String(tag)) => match self.variants.get(tag) {
|
||||
Some(Value::String(tag)) => match self.variants.get(&**tag) {
|
||||
None => Err(ConfigurationError::NoMatch(NoMatchWithPath::new(
|
||||
MatchError::Union(tag.clone(), self.variants.keys().cloned().collect()),
|
||||
))),
|
||||
Some(spec) => {
|
||||
spec.update(ctx, db, manifest, config_overrides, o, receipts)
|
||||
.await
|
||||
}
|
||||
Some(spec) => spec.update(ctx, manifest, config_overrides, o).await,
|
||||
},
|
||||
Some(other) => Err(ConfigurationError::NoMatch(
|
||||
NoMatchWithPath::new(MatchError::InvalidType("string", other.type_of()))
|
||||
@@ -1532,11 +1454,11 @@ impl ValueSpec for ValueSpecUnion {
|
||||
}
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
if let Value::Object(o) = value {
|
||||
match o.get(&self.tag.id) {
|
||||
match o.get(&*self.tag.id) {
|
||||
None => Err(NoMatchWithPath::new(MatchError::MissingTag(
|
||||
self.tag.id.clone(),
|
||||
))),
|
||||
Some(Value::String(tag)) => match self.variants.get(tag) {
|
||||
Some(Value::String(tag)) => match self.variants.get(&**tag) {
|
||||
None => Err(NoMatchWithPath::new(MatchError::Union(
|
||||
tag.clone(),
|
||||
self.variants.keys().cloned().collect(),
|
||||
@@ -1558,8 +1480,8 @@ impl ValueSpec for ValueSpecUnion {
|
||||
}
|
||||
fn requires(&self, id: &PackageId, value: &Value) -> bool {
|
||||
if let Value::Object(o) = value {
|
||||
match o.get(&self.tag.id) {
|
||||
Some(Value::String(tag)) => match self.variants.get(tag) {
|
||||
match o.get(&*self.tag.id) {
|
||||
Some(Value::String(tag)) => match self.variants.get(&**tag) {
|
||||
None => false,
|
||||
Some(spec) => spec.requires(id, o),
|
||||
},
|
||||
@@ -1577,7 +1499,7 @@ impl ValueSpec for ValueSpecUnion {
|
||||
}
|
||||
}
|
||||
impl DefaultableWith for ValueSpecUnion {
|
||||
type DefaultSpec = String;
|
||||
type DefaultSpec = Arc<String>;
|
||||
type Error = ConfigurationError;
|
||||
|
||||
fn gen_with<R: Rng + CryptoRng + Sync + Send>(
|
||||
@@ -1586,7 +1508,7 @@ impl DefaultableWith for ValueSpecUnion {
|
||||
rng: &mut R,
|
||||
timeout: &Option<Duration>,
|
||||
) -> Result<Value, Self::Error> {
|
||||
let variant = if let Some(v) = self.variants.get(spec) {
|
||||
let variant = if let Some(v) = self.variants.get(&**spec) {
|
||||
v
|
||||
} else {
|
||||
return Err(ConfigurationError::NoMatch(NoMatchWithPath::new(
|
||||
@@ -1642,24 +1564,16 @@ impl ValueSpec for ValueSpecPointer {
|
||||
ValueSpecPointer::System(a) => a.validate(manifest),
|
||||
}
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
match self {
|
||||
ValueSpecPointer::Package(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecPointer::System(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecPointer::Package(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecPointer::System(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
}
|
||||
}
|
||||
fn pointers(&self, _value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -1696,23 +1610,17 @@ impl PackagePointerSpec {
|
||||
PackagePointerSpec::Config(ConfigPointer { package_id, .. }) => package_id,
|
||||
}
|
||||
}
|
||||
async fn deref<Db: DbHandle>(
|
||||
async fn deref(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<Value, ConfigurationError> {
|
||||
match &self {
|
||||
PackagePointerSpec::TorKey(key) => key.deref(&manifest.id, &ctx.secret_store).await,
|
||||
PackagePointerSpec::TorAddress(tor) => {
|
||||
tor.deref(db, &receipts.interface_addresses_receipt).await
|
||||
}
|
||||
PackagePointerSpec::LanAddress(lan) => {
|
||||
lan.deref(db, &receipts.interface_addresses_receipt).await
|
||||
}
|
||||
PackagePointerSpec::Config(cfg) => cfg.deref(ctx, db, config_overrides, receipts).await,
|
||||
PackagePointerSpec::TorAddress(tor) => tor.deref(ctx).await,
|
||||
PackagePointerSpec::LanAddress(lan) => lan.deref(ctx).await,
|
||||
PackagePointerSpec::Config(cfg) => cfg.deref(ctx, config_overrides).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1753,18 +1661,14 @@ impl ValueSpec for PackagePointerSpec {
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
*value = self
|
||||
.deref(ctx, db, manifest, config_overrides, receipts)
|
||||
.await?;
|
||||
*value = self.deref(ctx, manifest, config_overrides).await?;
|
||||
Ok(())
|
||||
}
|
||||
fn pointers(&self, _value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -1787,18 +1691,19 @@ pub struct TorAddressPointer {
|
||||
interface: InterfaceId,
|
||||
}
|
||||
impl TorAddressPointer {
|
||||
async fn deref<Db: DbHandle>(
|
||||
&self,
|
||||
db: &mut Db,
|
||||
receipt: &InterfaceAddressesReceipt,
|
||||
) -> Result<Value, ConfigurationError> {
|
||||
let addr = receipt
|
||||
.interface_addresses
|
||||
.get(db, (&self.package_id, &self.interface))
|
||||
async fn deref(&self, ctx: &RpcContext) -> Result<Value, ConfigurationError> {
|
||||
let addr = ctx
|
||||
.db
|
||||
.peek()
|
||||
.await
|
||||
.map_err(|e| ConfigurationError::SystemError(Error::from(e)))?
|
||||
.and_then(|addresses| addresses.tor_address);
|
||||
Ok(addr.to_owned().map(Value::String).unwrap_or(Value::Null))
|
||||
.as_package_data()
|
||||
.as_idx(&self.package_id)
|
||||
.and_then(|pde| pde.as_installed())
|
||||
.and_then(|i| i.as_interface_addresses().as_idx(&self.interface))
|
||||
.and_then(|a| a.as_tor_address().de().transpose())
|
||||
.transpose()
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?;
|
||||
Ok(addr.map(Arc::new).map(Value::String).unwrap_or(Value::Null))
|
||||
}
|
||||
}
|
||||
impl fmt::Display for TorAddressPointer {
|
||||
@@ -1812,39 +1717,6 @@ impl fmt::Display for TorAddressPointer {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InterfaceAddressesReceipt {
|
||||
interface_addresses: LockReceipt<crate::db::model::InterfaceAddresses, (String, String)>,
|
||||
}
|
||||
|
||||
impl InterfaceAddressesReceipt {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
// let cleanup_receipts = CleanupFailedReceipts::setup(locks);
|
||||
|
||||
let interface_addresses = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.interface_addresses().star())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
// cleanup_receipts: cleanup_receipts(skeleton_key)?,
|
||||
interface_addresses: interface_addresses.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct LanAddressPointer {
|
||||
@@ -1861,73 +1733,26 @@ impl fmt::Display for LanAddressPointer {
|
||||
}
|
||||
}
|
||||
impl LanAddressPointer {
|
||||
async fn deref<Db: DbHandle>(
|
||||
&self,
|
||||
db: &mut Db,
|
||||
receipts: &InterfaceAddressesReceipt,
|
||||
) -> Result<Value, ConfigurationError> {
|
||||
let addr = receipts
|
||||
.interface_addresses
|
||||
.get(db, (&self.package_id, &self.interface))
|
||||
async fn deref(&self, ctx: &RpcContext) -> Result<Value, ConfigurationError> {
|
||||
let addr = ctx
|
||||
.db
|
||||
.peek()
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|x| x.lan_address);
|
||||
Ok(addr.to_owned().map(Value::String).unwrap_or(Value::Null))
|
||||
.as_package_data()
|
||||
.as_idx(&self.package_id)
|
||||
.and_then(|pde| pde.as_installed())
|
||||
.and_then(|i| i.as_interface_addresses().as_idx(&self.interface))
|
||||
.and_then(|a| a.as_lan_address().de().transpose())
|
||||
.transpose()
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?;
|
||||
Ok(addr
|
||||
.to_owned()
|
||||
.map(Arc::new)
|
||||
.map(Value::String)
|
||||
.unwrap_or(Value::Null))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConfigPointerReceipts {
|
||||
interface_addresses_receipt: InterfaceAddressesReceipt,
|
||||
manifest_volumes: LockReceipt<crate::volume::Volumes, String>,
|
||||
manifest_version: LockReceipt<crate::util::Version, String>,
|
||||
config_actions: LockReceipt<super::action::ConfigActions, String>,
|
||||
}
|
||||
|
||||
impl ConfigPointerReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
let interface_addresses_receipt = InterfaceAddressesReceipt::setup(locks);
|
||||
|
||||
let manifest_volumes = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest().volumes())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
let manifest_version = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest().version())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
let config_actions = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.and_then(|x| x.manifest().config())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
interface_addresses_receipt: interface_addresses_receipt(skeleton_key)?,
|
||||
manifest_volumes: manifest_volumes.verify(skeleton_key)?,
|
||||
config_actions: config_actions.verify(skeleton_key)?,
|
||||
manifest_version: manifest_version.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct ConfigPointer {
|
||||
@@ -1939,25 +1764,34 @@ impl ConfigPointer {
|
||||
pub fn select(&self, val: &Value) -> Value {
|
||||
self.selector.select(self.multi, val)
|
||||
}
|
||||
async fn deref<Db: DbHandle>(
|
||||
async fn deref(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<Value, ConfigurationError> {
|
||||
if let Some(cfg) = config_overrides.get(&self.package_id) {
|
||||
Ok(self.select(&Value::Object(cfg.clone())))
|
||||
} else {
|
||||
let id = &self.package_id;
|
||||
let version = receipts.manifest_version.get(db, id).await.ok().flatten();
|
||||
let cfg_actions = receipts.config_actions.get(db, id).await.ok().flatten();
|
||||
let volumes = receipts.manifest_volumes.get(db, id).await.ok().flatten();
|
||||
if let (Some(version), Some(cfg_actions), Some(volumes)) =
|
||||
(&version, &cfg_actions, &volumes)
|
||||
{
|
||||
let db = ctx.db.peek().await;
|
||||
let manifest = db.as_package_data().as_idx(id).map(|pde| pde.as_manifest());
|
||||
let cfg_actions = manifest.and_then(|m| m.as_config().transpose_ref());
|
||||
if let (Some(manifest), Some(cfg_actions)) = (manifest, cfg_actions) {
|
||||
let cfg_res = cfg_actions
|
||||
.get(ctx, &self.package_id, version, volumes)
|
||||
.de()
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?
|
||||
.get(
|
||||
ctx,
|
||||
&self.package_id,
|
||||
&manifest
|
||||
.as_version()
|
||||
.de()
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?,
|
||||
&manifest
|
||||
.as_volumes()
|
||||
.de()
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?;
|
||||
if let Some(cfg) = cfg_res.config {
|
||||
@@ -1989,7 +1823,7 @@ pub struct ConfigSelector {
|
||||
}
|
||||
impl ConfigSelector {
|
||||
fn select(&self, multi: bool, val: &Value) -> Value {
|
||||
let selected = self.compiled.select(&val).ok().unwrap_or_else(Vec::new);
|
||||
let selected = self.compiled.select(&val).ok().unwrap_or_else(Vector::new);
|
||||
if multi {
|
||||
Value::Array(selected.into_iter().cloned().collect())
|
||||
} else {
|
||||
@@ -2059,22 +1893,20 @@ impl TorKeyPointer {
|
||||
ValueSpecPointer::Package(PackagePointerSpec::TorKey(self.clone())),
|
||||
));
|
||||
}
|
||||
let x = sqlx::query!(
|
||||
"SELECT key FROM tor WHERE package = $1 AND interface = $2",
|
||||
*self.package_id,
|
||||
*self.interface
|
||||
let key = Key::for_interface(
|
||||
secrets
|
||||
.acquire()
|
||||
.await
|
||||
.map_err(|e| ConfigurationError::SystemError(e.into()))?
|
||||
.as_mut(),
|
||||
Some((self.package_id.clone(), self.interface.clone())),
|
||||
)
|
||||
.fetch_optional(secrets)
|
||||
.await
|
||||
.map_err(|e| ConfigurationError::SystemError(e.into()))?;
|
||||
if let Some(x) = x {
|
||||
Ok(Value::String(base32::encode(
|
||||
base32::Alphabet::RFC4648 { padding: false },
|
||||
&x.key,
|
||||
)))
|
||||
} else {
|
||||
Ok(Value::Null)
|
||||
}
|
||||
.map_err(ConfigurationError::SystemError)?;
|
||||
Ok(Value::String(Arc::new(base32::encode(
|
||||
base32::Alphabet::RFC4648 { padding: false },
|
||||
&key.tor_key().as_bytes(),
|
||||
))))
|
||||
}
|
||||
}
|
||||
impl fmt::Display for TorKeyPointer {
|
||||
@@ -2094,7 +1926,7 @@ impl fmt::Display for SystemPointerSpec {
|
||||
}
|
||||
}
|
||||
impl SystemPointerSpec {
|
||||
async fn deref<Db: DbHandle>(&self, _db: &mut Db) -> Result<Value, ConfigurationError> {
|
||||
async fn deref(&self, _ctx: &RpcContext) -> Result<Value, ConfigurationError> {
|
||||
#[allow(unreachable_code)]
|
||||
Ok(match *self {})
|
||||
}
|
||||
@@ -2117,17 +1949,14 @@ impl ValueSpec for SystemPointerSpec {
|
||||
fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
Ok(())
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
_ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
ctx: &RpcContext,
|
||||
_manifest: &Manifest,
|
||||
_config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
|
||||
_receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
*value = self.deref(db).await?;
|
||||
*value = self.deref(ctx).await?;
|
||||
Ok(())
|
||||
}
|
||||
fn pointers(&self, _value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::borrow::Cow;
|
||||
use std::ops::{Bound, RangeBounds, RangeInclusive};
|
||||
|
||||
use patch_db::Value;
|
||||
use rand::distributions::Distribution;
|
||||
use rand::Rng;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::Config;
|
||||
|
||||
@@ -321,7 +321,7 @@ impl UniqueBy {
|
||||
match self {
|
||||
UniqueBy::Any(any) => any.iter().any(|u| u.eq(lhs, rhs)),
|
||||
UniqueBy::All(all) => all.iter().all(|u| u.eq(lhs, rhs)),
|
||||
UniqueBy::Exactly(key) => lhs.get(key) == rhs.get(key),
|
||||
UniqueBy::Exactly(key) => lhs.get(&**key) == rhs.get(&**key),
|
||||
UniqueBy::NotUnique => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use cookie_store::CookieStore;
|
||||
use cookie_store::{CookieStore, RawCookie};
|
||||
use josekit::jwk::Jwk;
|
||||
use reqwest::Proxy;
|
||||
use reqwest_cookie_store::CookieStoreMutex;
|
||||
@@ -16,11 +16,11 @@ use rpc_toolkit::Context;
|
||||
use serde::Deserialize;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::setup::CURRENT_SECRET;
|
||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
||||
use crate::util::config::{load_config_from_paths, local_config_path};
|
||||
use crate::ResultExt;
|
||||
|
||||
use super::setup::CURRENT_SECRET;
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct CliContextConfig {
|
||||
@@ -52,7 +52,8 @@ impl Drop for CliContextSeed {
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
let store = self.cookie_store.lock().unwrap();
|
||||
let mut store = self.cookie_store.lock().unwrap();
|
||||
store.remove("localhost", "", "local");
|
||||
store.save_json(&mut *writer).unwrap();
|
||||
writer.sync_all().unwrap();
|
||||
std::fs::rename(tmp, &self.cookie_path).unwrap();
|
||||
@@ -66,7 +67,7 @@ const DEFAULT_PORT: u16 = 5959;
|
||||
pub struct CliContext(Arc<CliContextSeed>);
|
||||
impl CliContext {
|
||||
/// BLOCKING
|
||||
#[instrument(skip(matches))]
|
||||
#[instrument(skip_all)]
|
||||
pub fn init(matches: &ArgMatches) -> Result<Self, crate::Error> {
|
||||
let local_config_path = local_config_path();
|
||||
let base: CliContextConfig = load_config_from_paths(
|
||||
@@ -83,7 +84,7 @@ impl CliContext {
|
||||
} else if let Some(host) = base.host {
|
||||
host
|
||||
} else {
|
||||
format!("http://localhost").parse()?
|
||||
"http://localhost".parse()?
|
||||
};
|
||||
let proxy = if let Some(proxy) = matches.value_of("proxy") {
|
||||
Some(proxy.parse()?)
|
||||
@@ -99,13 +100,25 @@ impl CliContext {
|
||||
.unwrap_or(Path::new("/"))
|
||||
.join(".cookies.json")
|
||||
});
|
||||
let cookie_store = Arc::new(CookieStoreMutex::new(if cookie_path.exists() {
|
||||
CookieStore::load_json(BufReader::new(File::open(&cookie_path)?))
|
||||
.map_err(|e| eyre!("{}", e))
|
||||
.with_kind(crate::ErrorKind::Deserialization)?
|
||||
} else {
|
||||
CookieStore::default()
|
||||
let cookie_store = Arc::new(CookieStoreMutex::new({
|
||||
let mut store = if cookie_path.exists() {
|
||||
CookieStore::load_json(BufReader::new(File::open(&cookie_path)?))
|
||||
.map_err(|e| eyre!("{}", e))
|
||||
.with_kind(crate::ErrorKind::Deserialization)?
|
||||
} 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 {
|
||||
base_url: url.clone(),
|
||||
rpc_url: {
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct DiagnosticContextConfig {
|
||||
pub datadir: Option<PathBuf>,
|
||||
}
|
||||
impl DiagnosticContextConfig {
|
||||
#[instrument(skip(path))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn load<P: AsRef<Path> + Send + 'static>(path: Option<P>) -> Result<Self, Error> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
load_config_from_paths(
|
||||
@@ -52,7 +52,7 @@ pub struct DiagnosticContextSeed {
|
||||
#[derive(Clone)]
|
||||
pub struct DiagnosticContext(Arc<DiagnosticContextSeed>);
|
||||
impl DiagnosticContext {
|
||||
#[instrument(skip(path))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn init<P: AsRef<Path> + Send + 'static>(
|
||||
path: Option<P>,
|
||||
disk_guid: Option<Arc<String>>,
|
||||
|
||||
@@ -7,7 +7,7 @@ use serde::Deserialize;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::os_install::find_eth_iface;
|
||||
use crate::net::utils::find_eth_iface;
|
||||
use crate::util::config::load_config_from_paths;
|
||||
use crate::Error;
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::Error;
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct InstallContextConfig {}
|
||||
impl InstallContextConfig {
|
||||
#[instrument(skip(path))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn load<P: AsRef<Path> + Send + 'static>(path: Option<P>) -> Result<Self, Error> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
load_config_from_paths(
|
||||
@@ -38,7 +38,7 @@ pub struct InstallContextSeed {
|
||||
#[derive(Clone)]
|
||||
pub struct InstallContext(Arc<InstallContextSeed>);
|
||||
impl InstallContext {
|
||||
#[instrument(skip(path))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn init<P: AsRef<Path> + Send + 'static>(path: Option<P>) -> Result<Self, Error> {
|
||||
let _cfg = InstallContextConfig::load(path.as_ref().map(|p| p.as_ref().to_owned())).await?;
|
||||
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
||||
|
||||
@@ -1,44 +1,45 @@
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use bollard::Docker;
|
||||
use helpers::to_tmp_path;
|
||||
use josekit::jwk::Jwk;
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use patch_db::{DbHandle, LockReceipt, LockType, PatchDb, Revision};
|
||||
use reqwest::Url;
|
||||
use patch_db::PatchDb;
|
||||
use reqwest::{Client, Proxy, Url};
|
||||
use rpc_toolkit::Context;
|
||||
use serde::Deserialize;
|
||||
use sqlx::postgres::PgConnectOptions;
|
||||
use sqlx::PgPool;
|
||||
use tokio::sync::{broadcast, oneshot, Mutex, RwLock};
|
||||
use tokio::time::Instant;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::setup::CURRENT_SECRET;
|
||||
use crate::account::AccountInfo;
|
||||
use crate::core::rpc_continuations::{RequestGuid, RestHandler, RpcContinuation};
|
||||
use crate::db::model::{Database, InstalledPackageDataEntry, PackageDataEntry};
|
||||
use crate::db::model::{CurrentDependents, Database, PackageDataEntryMatchModelRef};
|
||||
use crate::db::prelude::PatchDbExt;
|
||||
use crate::dependencies::compute_dependency_config_errs;
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::hostname::HostNameReceipt;
|
||||
use crate::init::{init_postgres, pgloader};
|
||||
use crate::install::cleanup::{cleanup_failed, uninstall, CleanupFailedReceipts};
|
||||
use crate::init::init_postgres;
|
||||
use crate::install::cleanup::{cleanup_failed, uninstall};
|
||||
use crate::manager::ManagerMap;
|
||||
use crate::middleware::auth::HashSessionToken;
|
||||
use crate::net::net_controller::NetController;
|
||||
use crate::net::tor::os_key;
|
||||
use crate::net::ssl::{root_ca_start_time, SslManager};
|
||||
use crate::net::wifi::WpaCli;
|
||||
use crate::notifications::NotificationManager;
|
||||
use crate::setup::password_hash;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::status::{MainStatus, Status};
|
||||
use crate::status::MainStatus;
|
||||
use crate::system::get_mem_info;
|
||||
use crate::util::config::load_config_from_paths;
|
||||
use crate::util::lshw::{lshw, LshwDevice};
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
use super::setup::CURRENT_SECRET;
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct RpcContextConfig {
|
||||
@@ -76,24 +77,18 @@ impl RpcContextConfig {
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| Path::new("/embassy-data"))
|
||||
}
|
||||
pub async fn db(&self, secret_store: &PgPool) -> Result<PatchDb, Error> {
|
||||
pub async fn db(&self, account: &AccountInfo) -> Result<PatchDb, Error> {
|
||||
let db_path = self.datadir().join("main").join("embassy.db");
|
||||
let db = PatchDb::open(&db_path)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, db_path.display().to_string()))?;
|
||||
if !db.exists(&<JsonPointer>::default()).await {
|
||||
db.put(
|
||||
&<JsonPointer>::default(),
|
||||
&Database::init(
|
||||
&os_key(&mut secret_store.acquire().await?).await?,
|
||||
password_hash(&mut secret_store.acquire().await?).await?,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
db.put(&<JsonPointer>::default(), &Database::init(account))
|
||||
.await?;
|
||||
}
|
||||
Ok(db)
|
||||
}
|
||||
#[instrument]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn secret_store(&self) -> Result<PgPool, Error> {
|
||||
init_postgres(self.datadir()).await?;
|
||||
let secret_store =
|
||||
@@ -103,15 +98,6 @@ impl RpcContextConfig {
|
||||
.run(&secret_store)
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Database)?;
|
||||
let old_db_path = self.datadir().join("main/secrets.db");
|
||||
if tokio::fs::metadata(&old_db_path).await.is_ok() {
|
||||
pgloader(
|
||||
&old_db_path,
|
||||
self.migration_batch_rows.unwrap_or(25000),
|
||||
self.migration_prefetch_rows.unwrap_or(100_000),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(secret_store)
|
||||
}
|
||||
}
|
||||
@@ -125,11 +111,9 @@ pub struct RpcContextSeed {
|
||||
pub disk_guid: Arc<String>,
|
||||
pub db: PatchDb,
|
||||
pub secret_store: PgPool,
|
||||
pub docker: Docker,
|
||||
pub net_controller: NetController,
|
||||
pub account: RwLock<AccountInfo>,
|
||||
pub net_controller: Arc<NetController>,
|
||||
pub managers: ManagerMap,
|
||||
pub revision_cache_size: usize,
|
||||
pub revision_cache: RwLock<VecDeque<Arc<Revision>>>,
|
||||
pub metrics_cache: RwLock<Option<crate::system::Metrics>>,
|
||||
pub shutdown: broadcast::Sender<Option<Shutdown>>,
|
||||
pub tor_socks: SocketAddr,
|
||||
@@ -138,82 +122,21 @@ pub struct RpcContextSeed {
|
||||
pub rpc_stream_continuations: Mutex<BTreeMap<RequestGuid, RpcContinuation>>,
|
||||
pub wifi_manager: Option<Arc<RwLock<WpaCli>>>,
|
||||
pub current_secret: Arc<Jwk>,
|
||||
pub client: Client,
|
||||
pub hardware: Hardware,
|
||||
pub start_time: Instant,
|
||||
}
|
||||
|
||||
pub struct RpcCleanReceipts {
|
||||
cleanup_receipts: CleanupFailedReceipts,
|
||||
packages: LockReceipt<crate::db::model::AllPackageData, ()>,
|
||||
package: LockReceipt<crate::db::model::PackageDataEntry, String>,
|
||||
}
|
||||
|
||||
impl RpcCleanReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
let cleanup_receipts = CleanupFailedReceipts::setup(locks);
|
||||
|
||||
let packages = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let package = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
cleanup_receipts: cleanup_receipts(skeleton_key)?,
|
||||
packages: packages.verify(skeleton_key)?,
|
||||
package: package.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RpcSetHostNameReceipts {
|
||||
pub hostname_receipts: HostNameReceipt,
|
||||
#[allow(dead_code)]
|
||||
server_info: LockReceipt<crate::db::model::ServerInfo, ()>,
|
||||
}
|
||||
|
||||
impl RpcSetHostNameReceipts {
|
||||
pub async fn new(db: &'_ mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
let hostname_receipts = HostNameReceipt::setup(locks);
|
||||
let server_info = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
hostname_receipts: hostname_receipts(skeleton_key)?,
|
||||
server_info: server_info.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
pub struct Hardware {
|
||||
pub devices: Vec<LshwDevice>,
|
||||
pub ram: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RpcContext(Arc<RpcContextSeed>);
|
||||
impl RpcContext {
|
||||
#[instrument(skip(cfg_path))]
|
||||
pub async fn init<P: AsRef<Path> + Send + 'static>(
|
||||
#[instrument(skip_all)]
|
||||
pub async fn init<P: AsRef<Path> + Send + Sync + 'static>(
|
||||
cfg_path: Option<P>,
|
||||
disk_guid: Arc<String>,
|
||||
) -> Result<Self, Error> {
|
||||
@@ -226,30 +149,31 @@ impl RpcContext {
|
||||
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
||||
let secret_store = base.secret_store().await?;
|
||||
tracing::info!("Opened Pg DB");
|
||||
let db = base.db(&secret_store).await?;
|
||||
let account = AccountInfo::load(&secret_store).await?;
|
||||
let db = base.db(&account).await?;
|
||||
tracing::info!("Opened PatchDB");
|
||||
let mut docker = Docker::connect_with_unix_defaults()?;
|
||||
docker.set_timeout(Duration::from_secs(600));
|
||||
tracing::info!("Connected to Docker");
|
||||
let net_controller = NetController::init(
|
||||
([0, 0, 0, 0], 80).into(),
|
||||
crate::net::tor::os_key(&mut secret_store.acquire().await?).await?,
|
||||
base.tor_control
|
||||
.unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))),
|
||||
base.dns_bind
|
||||
.as_ref()
|
||||
.map(|v| v.as_slice())
|
||||
.unwrap_or(&[SocketAddr::from(([127, 0, 0, 1], 53))]),
|
||||
secret_store.clone(),
|
||||
&mut db.handle(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let net_controller = Arc::new(
|
||||
NetController::init(
|
||||
base.tor_control
|
||||
.unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))),
|
||||
tor_proxy,
|
||||
base.dns_bind
|
||||
.as_deref()
|
||||
.unwrap_or(&[SocketAddr::from(([127, 0, 0, 1], 53))]),
|
||||
SslManager::new(&account, root_ca_start_time().await?)?,
|
||||
&account.hostname,
|
||||
&account.key,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
tracing::info!("Initialized Net Controller");
|
||||
let managers = ManagerMap::default();
|
||||
let metrics_cache = RwLock::new(None);
|
||||
let metrics_cache = RwLock::<Option<crate::system::Metrics>>::new(None);
|
||||
let notification_manager = NotificationManager::new(secret_store.clone());
|
||||
tracing::info!("Initialized Notification Manager");
|
||||
let tor_proxy_url = format!("socks5h://{tor_proxy}");
|
||||
let devices = lshw().await?;
|
||||
let ram = get_mem_info().await?.total.0 as u64 * 1024 * 1024;
|
||||
let seed = Arc::new(RpcContextSeed {
|
||||
is_closed: AtomicBool::new(false),
|
||||
datadir: base.datadir().to_path_buf(),
|
||||
@@ -259,11 +183,9 @@ impl RpcContext {
|
||||
disk_guid,
|
||||
db,
|
||||
secret_store,
|
||||
docker,
|
||||
account: RwLock::new(account),
|
||||
net_controller,
|
||||
managers,
|
||||
revision_cache_size: base.revision_cache_size.unwrap_or(512),
|
||||
revision_cache: RwLock::new(VecDeque::new()),
|
||||
metrics_cache,
|
||||
shutdown,
|
||||
tor_socks: tor_proxy,
|
||||
@@ -283,110 +205,184 @@ impl RpcContext {
|
||||
)
|
||||
})?,
|
||||
),
|
||||
client: Client::builder()
|
||||
.proxy(Proxy::custom(move |url| {
|
||||
if url.host_str().map_or(false, |h| h.ends_with(".onion")) {
|
||||
Some(tor_proxy_url.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}))
|
||||
.build()
|
||||
.with_kind(crate::ErrorKind::ParseUrl)?,
|
||||
hardware: Hardware { devices, ram },
|
||||
start_time: Instant::now(),
|
||||
});
|
||||
|
||||
let res = Self(seed);
|
||||
res.cleanup().await?;
|
||||
let res = Self(seed.clone());
|
||||
res.cleanup_and_initialize().await?;
|
||||
tracing::info!("Cleaned up transient states");
|
||||
res.managers
|
||||
.init(
|
||||
&res,
|
||||
&mut res.db.handle(),
|
||||
&mut res.secret_store.acquire().await?,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Initialized Package Managers");
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn shutdown(self) -> Result<(), Error> {
|
||||
self.managers.empty().await?;
|
||||
self.secret_store.close().await;
|
||||
self.is_closed.store(true, Ordering::SeqCst);
|
||||
tracing::info!("RPC Context is shutdown");
|
||||
// TODO: shutdown http servers
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub async fn cleanup(&self) -> Result<(), Error> {
|
||||
let mut db = self.db.handle();
|
||||
let receipts = RpcCleanReceipts::new(&mut db).await?;
|
||||
for (package_id, package) in receipts.packages.get(&mut db).await?.0 {
|
||||
if let Err(e) = async {
|
||||
match package {
|
||||
PackageDataEntry::Installing { .. }
|
||||
| PackageDataEntry::Restoring { .. }
|
||||
| PackageDataEntry::Updating { .. } => {
|
||||
cleanup_failed(self, &mut db, &package_id, &receipts.cleanup_receipts)
|
||||
.await?;
|
||||
}
|
||||
PackageDataEntry::Removing { .. } => {
|
||||
uninstall(
|
||||
self,
|
||||
&mut db,
|
||||
&mut self.secret_store.acquire().await?,
|
||||
&package_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
PackageDataEntry::Installed {
|
||||
installed,
|
||||
static_files,
|
||||
manifest,
|
||||
} => {
|
||||
for (volume_id, volume_info) in &*manifest.volumes {
|
||||
let tmp_path = to_tmp_path(volume_info.path_for(
|
||||
&self.datadir,
|
||||
&package_id,
|
||||
&manifest.version,
|
||||
&volume_id,
|
||||
))
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
if tokio::fs::metadata(&tmp_path).await.is_ok() {
|
||||
tokio::fs::remove_dir_all(&tmp_path).await?;
|
||||
}
|
||||
}
|
||||
let status = installed.status;
|
||||
let main = match status.main {
|
||||
MainStatus::BackingUp { started, .. } => {
|
||||
if let Some(_) = started {
|
||||
MainStatus::Starting { restarting: false }
|
||||
} else {
|
||||
MainStatus::Stopped
|
||||
}
|
||||
}
|
||||
MainStatus::Running { .. } => {
|
||||
MainStatus::Starting { restarting: false }
|
||||
}
|
||||
a => a.clone(),
|
||||
};
|
||||
let new_package = PackageDataEntry::Installed {
|
||||
installed: InstalledPackageDataEntry {
|
||||
status: Status { main, ..status },
|
||||
..installed
|
||||
},
|
||||
static_files,
|
||||
manifest,
|
||||
};
|
||||
receipts
|
||||
.package
|
||||
.set(&mut db, new_package, &package_id)
|
||||
.await?;
|
||||
pub async fn cleanup_and_initialize(&self) -> Result<(), Error> {
|
||||
self.db
|
||||
.mutate(|f| {
|
||||
let mut current_dependents = f
|
||||
.as_package_data()
|
||||
.keys()?
|
||||
.into_iter()
|
||||
.map(|k| (k.clone(), BTreeMap::new()))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
for (package_id, package) in f.as_package_data_mut().as_entries_mut()? {
|
||||
for (k, v) in package
|
||||
.as_installed_mut()
|
||||
.into_iter()
|
||||
.flat_map(|i| i.clone().into_current_dependencies().into_entries())
|
||||
.flatten()
|
||||
{
|
||||
let mut entry: BTreeMap<_, _> =
|
||||
current_dependents.remove(&k).unwrap_or_default();
|
||||
entry.insert(package_id.clone(), v.de()?);
|
||||
current_dependents.insert(k, entry);
|
||||
}
|
||||
}
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
for (package_id, current_dependents) in current_dependents {
|
||||
if let Some(deps) = f
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(&package_id)
|
||||
.and_then(|pde| pde.expect_as_installed_mut().ok())
|
||||
.map(|i| i.as_installed_mut().as_current_dependents_mut())
|
||||
{
|
||||
deps.ser(&CurrentDependents(current_dependents))?;
|
||||
} else if let Some(deps) = f
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(&package_id)
|
||||
.and_then(|pde| pde.expect_as_removing_mut().ok())
|
||||
.map(|i| i.as_removing_mut().as_current_dependents_mut())
|
||||
{
|
||||
deps.ser(&CurrentDependents(current_dependents))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
let peek = self.db.peek().await;
|
||||
|
||||
for (package_id, package) in peek.as_package_data().as_entries()?.into_iter() {
|
||||
let action = match package.as_match() {
|
||||
PackageDataEntryMatchModelRef::Installing(_)
|
||||
| PackageDataEntryMatchModelRef::Restoring(_)
|
||||
| PackageDataEntryMatchModelRef::Updating(_) => {
|
||||
cleanup_failed(self, &package_id).await
|
||||
}
|
||||
PackageDataEntryMatchModelRef::Removing(_) => {
|
||||
uninstall(
|
||||
self,
|
||||
self.secret_store.acquire().await?.as_mut(),
|
||||
&package_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
PackageDataEntryMatchModelRef::Installed(m) => {
|
||||
let version = m.as_manifest().as_version().clone().de()?;
|
||||
let volumes = m.as_manifest().as_volumes().de()?;
|
||||
for (volume_id, volume_info) in &*volumes {
|
||||
let tmp_path = to_tmp_path(volume_info.path_for(
|
||||
&self.datadir,
|
||||
&package_id,
|
||||
&version,
|
||||
volume_id,
|
||||
))
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
if tokio::fs::metadata(&tmp_path).await.is_ok() {
|
||||
tokio::fs::remove_dir_all(&tmp_path).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
if let Err(e) = action {
|
||||
tracing::error!("Failed to clean up package {}: {}", package_id, e);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
}
|
||||
let peek = self
|
||||
.db
|
||||
.mutate(|v| {
|
||||
for (_, pde) in v.as_package_data_mut().as_entries_mut()? {
|
||||
let status = pde
|
||||
.expect_as_installed_mut()?
|
||||
.as_installed_mut()
|
||||
.as_status_mut()
|
||||
.as_main_mut();
|
||||
let running = status.clone().de()?.running();
|
||||
status.ser(&if running {
|
||||
MainStatus::Starting
|
||||
} else {
|
||||
MainStatus::Stopped
|
||||
})?;
|
||||
}
|
||||
Ok(v.clone())
|
||||
})
|
||||
.await?;
|
||||
self.managers.init(self.clone(), peek.clone()).await?;
|
||||
tracing::info!("Initialized Package Managers");
|
||||
|
||||
let mut all_dependency_config_errs = BTreeMap::new();
|
||||
for (package_id, package) in peek.as_package_data().as_entries()?.into_iter() {
|
||||
let package = package.clone();
|
||||
if let Some(current_dependencies) = package
|
||||
.as_installed()
|
||||
.and_then(|x| x.as_current_dependencies().de().ok())
|
||||
{
|
||||
let manifest = package.as_manifest().de()?;
|
||||
all_dependency_config_errs.insert(
|
||||
package_id.clone(),
|
||||
compute_dependency_config_errs(
|
||||
self,
|
||||
&peek,
|
||||
&manifest,
|
||||
¤t_dependencies,
|
||||
&Default::default(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
}
|
||||
self.db
|
||||
.mutate(|v| {
|
||||
for (package_id, errs) in all_dependency_config_errs {
|
||||
if let Some(config_errors) = v
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(&package_id)
|
||||
.and_then(|pde| pde.as_installed_mut())
|
||||
.map(|i| i.as_status_mut().as_dependency_config_errors_mut())
|
||||
{
|
||||
config_errors.ser(&errs)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn clean_continuations(&self) {
|
||||
let mut continuations = self.rpc_stream_continuations.lock().await;
|
||||
let mut to_remove = Vec::new();
|
||||
@@ -400,7 +396,7 @@ impl RpcContext {
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, handler))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn add_continuation(&self, guid: RequestGuid, handler: RpcContinuation) {
|
||||
self.clean_continuations().await;
|
||||
self.rpc_stream_continuations
|
||||
@@ -440,7 +436,7 @@ impl RpcContext {
|
||||
}
|
||||
impl AsRef<Jwk> for RpcContext {
|
||||
fn as_ref(&self) -> &Jwk {
|
||||
&*CURRENT_SECRET
|
||||
&CURRENT_SECRET
|
||||
}
|
||||
}
|
||||
impl Context for RpcContext {}
|
||||
@@ -454,7 +450,7 @@ impl Deref for RpcContext {
|
||||
tracing_error::SpanTrace::capture()
|
||||
);
|
||||
}
|
||||
&*self.0
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl Drop for RpcContext {
|
||||
|
||||