mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
Compare commits
41 Commits
v0.4.0-alp
...
v0.4.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f41710c892 | ||
|
|
df3f79f282 | ||
|
|
f8df692865 | ||
|
|
0c6d3b188d | ||
|
|
e7a38863ab | ||
|
|
720e0fcdab | ||
|
|
bf8ff84522 | ||
|
|
5a9510238e | ||
|
|
7b3c74179b | ||
|
|
cd70fa4c32 | ||
|
|
83133ced6a | ||
|
|
6c5179a179 | ||
|
|
e33ab39b85 | ||
|
|
9567bcec1b | ||
|
|
550b16dc0b | ||
|
|
5d8331b7f7 | ||
|
|
e35b643e51 | ||
|
|
bc6a92677b | ||
|
|
f52072e6ec | ||
|
|
9c43c43a46 | ||
|
|
0430e0f930 | ||
|
|
b945243d1a | ||
|
|
d8484a8b26 | ||
|
|
3c27499795 | ||
|
|
7c772e873d | ||
|
|
db2fab245e | ||
|
|
a9c9917f1a | ||
|
|
23e2e9e9cc | ||
|
|
2369e92460 | ||
|
|
a53b15f2a3 | ||
|
|
72eb8b1eb6 | ||
|
|
4db54f3b83 | ||
|
|
24eb27f005 | ||
|
|
009d76ea35 | ||
|
|
6e8a425eb1 | ||
|
|
66188d791b | ||
|
|
015ff02d71 | ||
|
|
10bfaf5415 | ||
|
|
e3e0b85e0c | ||
|
|
ad0632892e | ||
|
|
f26791ba39 |
118
.github/workflows/start-cli.yaml
vendored
Normal file
118
.github/workflows/start-cli.yaml
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
name: start-cli
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
type: choice
|
||||
description: Environment
|
||||
options:
|
||||
- NONE
|
||||
- dev
|
||||
- unstable
|
||||
- dev-unstable
|
||||
runner:
|
||||
type: choice
|
||||
description: Runner
|
||||
options:
|
||||
- standard
|
||||
- fast
|
||||
arch:
|
||||
type: choice
|
||||
description: Architecture
|
||||
options:
|
||||
- ALL
|
||||
- x86_64
|
||||
- x86_64-apple
|
||||
- aarch64
|
||||
- aarch64-apple
|
||||
- riscv64
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- next/*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- next/*
|
||||
|
||||
env:
|
||||
NODEJS_VERSION: "24.11.0"
|
||||
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||
|
||||
jobs:
|
||||
compile:
|
||||
name: Build Debian Package
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
triple: >-
|
||||
${{
|
||||
fromJson('{
|
||||
"x86_64": ["x86_64-unknown-linux-musl"],
|
||||
"x86_64-apple": ["x86_64-apple-darwin"],
|
||||
"aarch64": ["aarch64-unknown-linux-musl"],
|
||||
"x86_64-apple": ["aarch64-apple-darwin"],
|
||||
"riscv64": ["riscv64gc-unknown-linux-musl"],
|
||||
"ALL": ["x86_64-unknown-linux-musl", "x86_64-apple-darwin", "aarch64-unknown-linux-musl", "aarch64-apple-darwin", "riscv64gc-unknown-linux-musl"]
|
||||
}')[github.event.inputs.platform || 'ALL']
|
||||
}}
|
||||
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||
steps:
|
||||
- name: Cleaning up unnecessary files
|
||||
run: |
|
||||
sudo apt-get remove --purge -y mono-* \
|
||||
ghc* cabal-install* \
|
||||
dotnet* \
|
||||
php* \
|
||||
ruby* \
|
||||
mysql-* \
|
||||
postgresql-* \
|
||||
azure-cli \
|
||||
powershell \
|
||||
google-cloud-sdk \
|
||||
msodbcsql* mssql-tools* \
|
||||
imagemagick* \
|
||||
libgl1-mesa-dri \
|
||||
google-chrome-stable \
|
||||
firefox
|
||||
sudo apt-get autoremove -y
|
||||
sudo apt-get clean
|
||||
|
||||
- run: |
|
||||
sudo mount -t tmpfs tmpfs .
|
||||
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODEJS_VERSION }}
|
||||
|
||||
- name: Set up docker QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Configure sccache
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
|
||||
- name: Make
|
||||
run: TARGET=${{ matrix.triple }} make cli
|
||||
env:
|
||||
PLATFORM: ${{ matrix.arch }}
|
||||
SCCACHE_GHA_ENABLED: on
|
||||
SCCACHE_GHA_VERSION: 0
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: start-cli_${{ matrix.triple }}
|
||||
path: core/target/${{ matrix.triple }}/release/start-cli
|
||||
203
.github/workflows/start-registry.yaml
vendored
Normal file
203
.github/workflows/start-registry.yaml
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
name: Start-Registry
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
type: choice
|
||||
description: Environment
|
||||
options:
|
||||
- NONE
|
||||
- dev
|
||||
- unstable
|
||||
- dev-unstable
|
||||
runner:
|
||||
type: choice
|
||||
description: Runner
|
||||
options:
|
||||
- standard
|
||||
- fast
|
||||
arch:
|
||||
type: choice
|
||||
description: Architecture
|
||||
options:
|
||||
- ALL
|
||||
- x86_64
|
||||
- aarch64
|
||||
- riscv64
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- next/*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- next/*
|
||||
|
||||
env:
|
||||
NODEJS_VERSION: "24.11.0"
|
||||
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||
|
||||
jobs:
|
||||
compile:
|
||||
name: Build Debian Package
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
arch: >-
|
||||
${{
|
||||
fromJson('{
|
||||
"x86_64": ["x86_64"],
|
||||
"aarch64": ["aarch64"],
|
||||
"riscv64": ["riscv64"],
|
||||
"ALL": ["x86_64", "aarch64", "riscv64"]
|
||||
}')[github.event.inputs.platform || 'ALL']
|
||||
}}
|
||||
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||
steps:
|
||||
- name: Cleaning up unnecessary files
|
||||
run: |
|
||||
sudo apt-get remove --purge -y mono-* \
|
||||
ghc* cabal-install* \
|
||||
dotnet* \
|
||||
php* \
|
||||
ruby* \
|
||||
mysql-* \
|
||||
postgresql-* \
|
||||
azure-cli \
|
||||
powershell \
|
||||
google-cloud-sdk \
|
||||
msodbcsql* mssql-tools* \
|
||||
imagemagick* \
|
||||
libgl1-mesa-dri \
|
||||
google-chrome-stable \
|
||||
firefox
|
||||
sudo apt-get autoremove -y
|
||||
sudo apt-get clean
|
||||
|
||||
- run: |
|
||||
sudo mount -t tmpfs tmpfs .
|
||||
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODEJS_VERSION }}
|
||||
|
||||
- name: Set up docker QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Configure sccache
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
|
||||
- name: Make
|
||||
run: make registry-deb
|
||||
env:
|
||||
PLATFORM: ${{ matrix.arch }}
|
||||
SCCACHE_GHA_ENABLED: on
|
||||
SCCACHE_GHA_VERSION: 0
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: start-registry_${{ matrix.arch }}.deb
|
||||
path: results/start-registry-*_${{ matrix.arch }}.deb
|
||||
|
||||
create-image:
|
||||
name: Create Docker Image
|
||||
needs: [compile]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||
steps:
|
||||
- name: Cleaning up unnecessary files
|
||||
run: |
|
||||
sudo apt-get remove --purge -y google-chrome-stable firefox mono-devel
|
||||
sudo apt-get autoremove -y
|
||||
sudo apt-get clean
|
||||
|
||||
- run: |
|
||||
sudo mount -t tmpfs tmpfs .
|
||||
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||
|
||||
- name: Set up docker QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: "Login to GitHub Container Registry"
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{github.actor}}
|
||||
password: ${{secrets.GITHUB_TOKEN}}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/Start9Labs/startos-registry
|
||||
tags: |
|
||||
type=raw,value=${{ github.ref_name }}
|
||||
|
||||
- name: Download debian package
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: start-registry_*.deb
|
||||
|
||||
- name: Map matrix.arch to docker platform
|
||||
run: |
|
||||
platforms=""
|
||||
for deb in *.deb; do
|
||||
filename=$(basename "$deb" .deb)
|
||||
arch="${filename#*_}"
|
||||
case "$arch" in
|
||||
x86_64)
|
||||
platform="linux/amd64"
|
||||
;;
|
||||
aarch64)
|
||||
platform="linux/arm64"
|
||||
;;
|
||||
riscv64)
|
||||
platform="linux/riscv64"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown architecture: $arch" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
if [ -z "$platforms" ]; then
|
||||
platforms="$platform"
|
||||
else
|
||||
platforms="$platforms,$platform"
|
||||
fi
|
||||
done
|
||||
echo "DOCKER_PLATFORM=$platforms" >> "$GITHUB_ENV"
|
||||
|
||||
- run: |
|
||||
cat | docker buildx build --platform "$DOCKER_PLATFORM" --push -t ${{ steps.meta.outputs.tags }} -f - . << 'EOF'
|
||||
FROM debian:trixie
|
||||
|
||||
ADD *.deb .
|
||||
|
||||
RUN apt-get install -y ./*_$(uname -m).deb && rm *.deb
|
||||
|
||||
VOLUME /var/lib/startos
|
||||
|
||||
ENV RUST_LOG=startos=debug
|
||||
|
||||
ENTRYPOINT ["start-registryd"]
|
||||
|
||||
EOF
|
||||
114
.github/workflows/start-tunnel.yaml
vendored
Normal file
114
.github/workflows/start-tunnel.yaml
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
name: Start-Tunnel
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
type: choice
|
||||
description: Environment
|
||||
options:
|
||||
- NONE
|
||||
- dev
|
||||
- unstable
|
||||
- dev-unstable
|
||||
runner:
|
||||
type: choice
|
||||
description: Runner
|
||||
options:
|
||||
- standard
|
||||
- fast
|
||||
arch:
|
||||
type: choice
|
||||
description: Architecture
|
||||
options:
|
||||
- ALL
|
||||
- x86_64
|
||||
- aarch64
|
||||
- riscv64
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- next/*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- next/*
|
||||
|
||||
env:
|
||||
NODEJS_VERSION: "24.11.0"
|
||||
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||
|
||||
jobs:
|
||||
compile:
|
||||
name: Build Debian Package
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
arch: >-
|
||||
${{
|
||||
fromJson('{
|
||||
"x86_64": ["x86_64"],
|
||||
"aarch64": ["aarch64"],
|
||||
"riscv64": ["riscv64"],
|
||||
"ALL": ["x86_64", "aarch64", "riscv64"]
|
||||
}')[github.event.inputs.platform || 'ALL']
|
||||
}}
|
||||
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||
steps:
|
||||
- name: Cleaning up unnecessary files
|
||||
run: |
|
||||
sudo apt-get remove --purge -y mono-* \
|
||||
ghc* cabal-install* \
|
||||
dotnet* \
|
||||
php* \
|
||||
ruby* \
|
||||
mysql-* \
|
||||
postgresql-* \
|
||||
azure-cli \
|
||||
powershell \
|
||||
google-cloud-sdk \
|
||||
msodbcsql* mssql-tools* \
|
||||
imagemagick* \
|
||||
libgl1-mesa-dri \
|
||||
google-chrome-stable \
|
||||
firefox
|
||||
sudo apt-get autoremove -y
|
||||
sudo apt-get clean
|
||||
|
||||
- run: |
|
||||
sudo mount -t tmpfs tmpfs .
|
||||
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODEJS_VERSION }}
|
||||
|
||||
- name: Set up docker QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Configure sccache
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
|
||||
- name: Make
|
||||
run: make tunnel-deb
|
||||
env:
|
||||
PLATFORM: ${{ matrix.arch }}
|
||||
SCCACHE_GHA_ENABLED: on
|
||||
SCCACHE_GHA_VERSION: 0
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: start-tunnel_${{ matrix.arch }}.deb
|
||||
path: results/start-tunnel-*_${{ matrix.arch }}.deb
|
||||
73
.github/workflows/startos-iso.yaml
vendored
73
.github/workflows/startos-iso.yaml
vendored
@@ -64,11 +64,47 @@ jobs:
|
||||
"aarch64-nonfree": ["aarch64"],
|
||||
"raspberrypi": ["aarch64"],
|
||||
"riscv64": ["riscv64"],
|
||||
"ALL": ["x86_64", "aarch64"]
|
||||
"ALL": ["x86_64", "aarch64", "riscv64"]
|
||||
}')[github.event.inputs.platform || 'ALL']
|
||||
}}
|
||||
runs-on: ${{ fromJson('["ubuntu-22.04", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||
runs-on: >-
|
||||
${{
|
||||
fromJson(
|
||||
format(
|
||||
'["{0}", "{1}"]',
|
||||
fromJson('{
|
||||
"x86_64": "ubuntu-latest",
|
||||
"aarch64": "ubuntu-24.04-arm",
|
||||
"riscv64": "ubuntu-latest"
|
||||
}')[matrix.arch],
|
||||
fromJson('{
|
||||
"x86_64": "buildjet-32vcpu-ubuntu-2204",
|
||||
"aarch64": "buildjet-32vcpu-ubuntu-2204-arm",
|
||||
"riscv64": "buildjet-32vcpu-ubuntu-2204"
|
||||
}')[matrix.arch]
|
||||
)
|
||||
)[github.event.inputs.runner == 'fast']
|
||||
}}
|
||||
steps:
|
||||
- name: Cleaning up unnecessary files
|
||||
run: |
|
||||
sudo apt-get remove --purge -y azure-cli || true
|
||||
sudo apt-get remove --purge -y firefox || true
|
||||
sudo apt-get remove --purge -y ghc-* || true
|
||||
sudo apt-get remove --purge -y google-cloud-sdk || true
|
||||
sudo apt-get remove --purge -y google-chrome-stable || true
|
||||
sudo apt-get remove --purge -y powershell || true
|
||||
sudo apt-get remove --purge -y php* || true
|
||||
sudo apt-get remove --purge -y ruby* || true
|
||||
sudo apt-get remove --purge -y mono-* || true
|
||||
sudo apt-get autoremove -y
|
||||
sudo apt-get clean
|
||||
sudo rm -rf /usr/lib/jvm # All JDKs
|
||||
sudo rm -rf /usr/local/.ghcup # Haskell toolchain
|
||||
sudo rm -rf /usr/local/lib/android # Android SDK/NDK, emulator
|
||||
sudo rm -rf /usr/share/dotnet # .NET SDKs
|
||||
sudo rm -rf /usr/share/swift # Swift toolchain (if present)
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY" # Pre-cached tool cache (Go, Node, etc.)
|
||||
- run: |
|
||||
sudo mount -t tmpfs tmpfs .
|
||||
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||
@@ -124,7 +160,7 @@ jobs:
|
||||
format(
|
||||
'[
|
||||
["{0}"],
|
||||
["x86_64", "x86_64-nonfree", "aarch64", "aarch64-nonfree", "raspberrypi"]
|
||||
["x86_64", "x86_64-nonfree", "aarch64", "aarch64-nonfree", "riscv64", "raspberrypi"]
|
||||
]',
|
||||
github.event.inputs.platform || 'ALL'
|
||||
)
|
||||
@@ -134,7 +170,15 @@ jobs:
|
||||
${{
|
||||
fromJson(
|
||||
format(
|
||||
'["ubuntu-22.04", "{0}"]',
|
||||
'["{0}", "{1}"]',
|
||||
fromJson('{
|
||||
"x86_64": "ubuntu-latest",
|
||||
"x86_64-nonfree": "ubuntu-latest",
|
||||
"aarch64": "ubuntu-24.04-arm",
|
||||
"aarch64-nonfree": "ubuntu-24.04-arm",
|
||||
"raspberrypi": "ubuntu-24.04-arm",
|
||||
"riscv64": "ubuntu-24.04-arm",
|
||||
}')[matrix.platform],
|
||||
fromJson('{
|
||||
"x86_64": "buildjet-8vcpu-ubuntu-2204",
|
||||
"x86_64-nonfree": "buildjet-8vcpu-ubuntu-2204",
|
||||
@@ -160,7 +204,24 @@ jobs:
|
||||
}}
|
||||
steps:
|
||||
- name: Free space
|
||||
run: rm -rf /opt/hostedtoolcache*
|
||||
run: |
|
||||
sudo apt-get remove --purge -y azure-cli || true
|
||||
sudo apt-get remove --purge -y firefox || true
|
||||
sudo apt-get remove --purge -y ghc-* || true
|
||||
sudo apt-get remove --purge -y google-cloud-sdk || true
|
||||
sudo apt-get remove --purge -y google-chrome-stable || true
|
||||
sudo apt-get remove --purge -y powershell || true
|
||||
sudo apt-get remove --purge -y php* || true
|
||||
sudo apt-get remove --purge -y ruby* || true
|
||||
sudo apt-get remove --purge -y mono-* || true
|
||||
sudo apt-get autoremove -y
|
||||
sudo apt-get clean
|
||||
sudo rm -rf /usr/lib/jvm # All JDKs
|
||||
sudo rm -rf /usr/local/.ghcup # Haskell toolchain
|
||||
sudo rm -rf /usr/local/lib/android # Android SDK/NDK, emulator
|
||||
sudo rm -rf /usr/share/dotnet # .NET SDKs
|
||||
sudo rm -rf /usr/share/swift # Swift toolchain (if present)
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY" # Pre-cached tool cache (Go, Node, etc.)
|
||||
if: ${{ github.event.inputs.runner != 'fast' }}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
@@ -267,7 +328,7 @@ jobs:
|
||||
index:
|
||||
if: ${{ github.event.inputs.deploy != '' && github.event.inputs.deploy != 'NONE' }}
|
||||
needs: [image]
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: >-
|
||||
curl "https://${{
|
||||
|
||||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@@ -17,7 +17,7 @@ env:
|
||||
jobs:
|
||||
test:
|
||||
name: Run Automated Tests
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
44
Makefile
44
Makefile
@@ -27,7 +27,7 @@ WEB_START_TUNNEL_SRC := $(call ls-files, web/projects/start-tunnel)
|
||||
PATCH_DB_CLIENT_SRC := $(shell git ls-files --recurse-submodules patch-db/client)
|
||||
GZIP_BIN := $(shell which pigz || which gzip)
|
||||
TAR_BIN := $(shell which gtar || which tar)
|
||||
COMPILED_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox core/target/$(RUST_ARCH)-unknown-linux-musl/release/containerbox container-runtime/rootfs.$(ARCH).squashfs
|
||||
COMPILED_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox core/target/$(RUST_ARCH)-unknown-linux-musl/release/start-container container-runtime/rootfs.$(ARCH).squashfs
|
||||
STARTOS_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) $(COMPILED_TARGETS) cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs $(PLATFORM_FILE) \
|
||||
$(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then \
|
||||
echo cargo-deps/aarch64-unknown-linux-musl/release/pi-beep; \
|
||||
@@ -40,7 +40,6 @@ STARTOS_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_
|
||||
fi')
|
||||
REGISTRY_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox core/startos/start-registryd.service
|
||||
TUNNEL_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox core/startos/start-tunneld.service
|
||||
REBUILD_TYPES = 1
|
||||
|
||||
ifeq ($(REMOTE),)
|
||||
mkdir = mkdir -p $1
|
||||
@@ -63,7 +62,7 @@ endif
|
||||
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
.PHONY: all metadata install clean format cli uis ui reflash deb $(IMAGE_TYPE) squashfs wormhole wormhole-deb test test-core test-sdk test-container-runtime registry install-registry tunnel install-tunnel
|
||||
.PHONY: all metadata install clean format install-cli cli uis ui reflash deb $(IMAGE_TYPE) squashfs wormhole wormhole-deb test test-core test-sdk test-container-runtime registry install-registry tunnel install-tunnel ts-bindings
|
||||
|
||||
all: $(STARTOS_TARGETS)
|
||||
|
||||
@@ -113,8 +112,11 @@ test-sdk: $(call ls-files, sdk) sdk/base/lib/osBindings/index.ts
|
||||
test-container-runtime: container-runtime/node_modules/.package-lock.json $(call ls-files, container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
||||
cd container-runtime && npm test
|
||||
|
||||
cli:
|
||||
./core/install-cli.sh
|
||||
install-cli: $(GIT_HASH_FILE)
|
||||
./core/build-cli.sh --install
|
||||
|
||||
cli: $(GIT_HASH_FILE)
|
||||
./core/build-cli.sh
|
||||
|
||||
registry: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox
|
||||
|
||||
@@ -159,7 +161,7 @@ results/$(REGISTRY_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-reg
|
||||
|
||||
tunnel-deb: results/$(TUNNEL_BASENAME).deb
|
||||
|
||||
results/$(TUNNEL_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-tunnel) $(TUNNEL_TARGETS)
|
||||
results/$(TUNNEL_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-tunnel) $(TUNNEL_TARGETS) build/lib/scripts/forward-port
|
||||
PROJECT=start-tunnel PLATFORM=$(ARCH) REQUIRES=debian DEPENDS=wireguard-tools,iptables,conntrack ./build/os-compat/run-compat.sh ./dpkg-build.sh
|
||||
|
||||
$(IMAGE_TYPE): results/$(BASENAME).$(IMAGE_TYPE)
|
||||
@@ -277,10 +279,9 @@ container-runtime/node_modules/.package-lock.json: container-runtime/package-loc
|
||||
npm --prefix container-runtime ci
|
||||
touch container-runtime/node_modules/.package-lock.json
|
||||
|
||||
sdk/base/lib/osBindings/index.ts: $(shell if [ "$(REBUILD_TYPES)" -ne 0 ]; then echo core/startos/bindings/index.ts; fi)
|
||||
ts-bindings: core/startos/bindings/index.ts
|
||||
mkdir -p sdk/base/lib/osBindings
|
||||
rsync -ac --delete core/startos/bindings/ sdk/base/lib/osBindings/
|
||||
touch sdk/base/lib/osBindings/index.ts
|
||||
|
||||
core/startos/bindings/index.ts: $(call ls-files, core) $(ENVIRONMENT_FILE)
|
||||
rm -rf core/startos/bindings
|
||||
@@ -302,8 +303,8 @@ container-runtime/dist/node_modules/.package-lock.json container-runtime/dist/pa
|
||||
./container-runtime/install-dist-deps.sh
|
||||
touch container-runtime/dist/node_modules/.package-lock.json
|
||||
|
||||
container-runtime/rootfs.$(ARCH).squashfs: container-runtime/debian.$(ARCH).squashfs container-runtime/container-runtime.service container-runtime/update-image.sh container-runtime/deb-install.sh container-runtime/dist/index.js container-runtime/dist/node_modules/.package-lock.json core/target/$(RUST_ARCH)-unknown-linux-musl/release/containerbox
|
||||
ARCH=$(ARCH) REQUIRES=linux ./build/os-compat/run-compat.sh ./container-runtime/update-image.sh
|
||||
container-runtime/rootfs.$(ARCH).squashfs: container-runtime/debian.$(ARCH).squashfs container-runtime/container-runtime.service container-runtime/update-image.sh container-runtime/deb-install.sh container-runtime/dist/index.js container-runtime/dist/node_modules/.package-lock.json core/target/$(RUST_ARCH)-unknown-linux-musl/release/start-container
|
||||
ARCH=$(ARCH) REQUIRES=qemu ./build/os-compat/run-compat.sh ./container-runtime/update-image.sh
|
||||
|
||||
build/lib/depends build/lib/conflicts: $(ENVIRONMENT_FILE) $(PLATFORM_FILE) $(shell ls build/dpkg-deps/*)
|
||||
PLATFORM=$(PLATFORM) ARCH=$(ARCH) build/dpkg-deps/generate.sh
|
||||
@@ -315,9 +316,9 @@ core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox: $(CORE_SRC) $(C
|
||||
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-startbox.sh
|
||||
touch core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox
|
||||
|
||||
core/target/$(RUST_ARCH)-unknown-linux-musl/release/containerbox: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||
ARCH=$(ARCH) ./core/build-containerbox.sh
|
||||
touch core/target/$(RUST_ARCH)-unknown-linux-musl/release/containerbox
|
||||
core/target/$(RUST_ARCH)-unknown-linux-musl/release/start-container: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||
ARCH=$(ARCH) ./core/build-start-container.sh
|
||||
touch core/target/$(RUST_ARCH)-unknown-linux-musl/release/start-container
|
||||
|
||||
web/package-lock.json: web/package.json sdk/baseDist/package.json
|
||||
npm --prefix web i
|
||||
@@ -373,14 +374,17 @@ uis: $(WEB_UIS)
|
||||
# this is a convenience step to build the UI
|
||||
ui: web/dist/raw/ui
|
||||
|
||||
cargo-deps/aarch64-unknown-linux-musl/release/pi-beep:
|
||||
cargo-deps/aarch64-unknown-linux-musl/release/pi-beep: ./build-cargo-dep.sh
|
||||
ARCH=aarch64 ./build-cargo-dep.sh pi-beep
|
||||
|
||||
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/tokio-console:
|
||||
ARCH=$(ARCH) PREINSTALL="apk add musl-dev pkgconfig" ./build-cargo-dep.sh tokio-console
|
||||
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/tokio-console: ./build-cargo-dep.sh
|
||||
ARCH=$(ARCH) ./build-cargo-dep.sh tokio-console
|
||||
touch $@
|
||||
|
||||
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs:
|
||||
ARCH=$(ARCH) PREINSTALL="apk add fuse3 fuse3-dev fuse3-static musl-dev pkgconfig" ./build-cargo-dep.sh --git https://github.com/Start9Labs/start-fs.git startos-backup-fs
|
||||
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs: ./build-cargo-dep.sh
|
||||
ARCH=$(ARCH) ./build-cargo-dep.sh --git https://github.com/Start9Labs/start-fs.git startos-backup-fs
|
||||
touch $@
|
||||
|
||||
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph:
|
||||
ARCH=$(ARCH) PREINSTALL="apk add musl-dev pkgconfig" ./build-cargo-dep.sh flamegraph
|
||||
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph: ./build-cargo-dep.sh
|
||||
ARCH=$(ARCH) ./build-cargo-dep.sh flamegraph
|
||||
touch $@
|
||||
|
||||
@@ -14,14 +14,6 @@ Use it for private remote access to self-hosted services running on a personal s
|
||||
|
||||
- **Forward Ports**: Forwarding a port creates a "reverse tunnel", exposing a specific port on a specific device to the public Internet.
|
||||
|
||||
## Features
|
||||
|
||||
- **Create Subnets**: Each subnet creates a private, virtual local area network (VLAN), similar to the LAN created by a home router.
|
||||
|
||||
- **Add Devices**: When you add a device (server, phone, laptop) to a subnet, it receives a LAN IP address on that subnet as well as a unique Wireguard config that must be copied, downloaded, or scanned into the device.
|
||||
|
||||
- **Forward Ports**: Forwarding a port creates a "reverse tunnel", exposing a specific port on a specific device to the public Internet.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Rent a low cost VPS. For most use cases, the cheapest option should be enough.
|
||||
@@ -34,18 +26,18 @@ Use it for private remote access to self-hosted services running on a personal s
|
||||
|
||||
1. Access the VPS via SSH.
|
||||
|
||||
1. Install StartTunnel:
|
||||
1. Run the StartTunnel install script:
|
||||
|
||||
```sh
|
||||
TMP_DIR=$(mktemp -d) && (cd $TMP_DIR && wget https://github.com/Start9Labs/start-os/releases/download/v0.4.0-alpha.12/start-tunnel-0.4.0-alpha.12-unknown.dev_$(uname -m).deb && apt-get install -y ./start-tunnel-0.4.0-alpha.12-unknown.dev_$(uname -m).deb) && rm -rf $TMP_DIR && systemctl start start-tunneld && echo "Installation Succeeded"
|
||||
```
|
||||
curl -fsSL https://start9labs.github.io/start-tunnel | sh
|
||||
|
||||
5. [Initialize the web interface](#web-interface) (recommended)
|
||||
1. [Initialize the web interface](#web-interface) (recommended)
|
||||
|
||||
## Updating
|
||||
|
||||
Simply re-run the install command:
|
||||
|
||||
```sh
|
||||
TMP_DIR=$(mktemp -d) && (cd $TMP_DIR && wget https://github.com/Start9Labs/start-os/releases/download/v0.4.0-alpha.12/start-tunnel-0.4.0-alpha.12-unknown.dev_$(uname -m).deb && apt-get install --reinstall -y ./start-tunnel-0.4.0-alpha.12-unknown.dev_$(uname -m).deb) && rm -rf $TMP_DIR && systemctl daemon-reload && systemctl restart start-tunneld && echo "Update Succeeded"
|
||||
curl -fsSL https://start9labs.github.io/start-tunnel | sh
|
||||
```
|
||||
|
||||
## CLI
|
||||
@@ -58,20 +50,46 @@ start-tunnel --help
|
||||
|
||||
## Web Interface
|
||||
|
||||
If you choose to enable the web interface (recommended in most cases), StartTunnel can be accessed as a website from the browser, or programmatically via API.
|
||||
Enable the web interface (recommended in most cases) to access your StartTunnel from the browser or via API.
|
||||
|
||||
1. Initialize the web interface.
|
||||
|
||||
start-tunnel web init
|
||||
|
||||
1. When prompted, select the IP address at which to host the web interface. In many cases, there will be only one IP address.
|
||||
1. If your VPS has multiple public IP addresses, you will be prompted to select the IP address at which to host the web interface.
|
||||
|
||||
1. When prompted, enter the port at which to host the web interface. The default is 8443, and we recommend using it. If you change the default, choose an uncommon port to avoid conflicts.
|
||||
1. When prompted, enter the port at which to host the web interface. The default is 8443, and we recommend using it. If you change the default, choose an uncommon port to avoid future conflicts.
|
||||
|
||||
1. Select whether to autogenerate a self-signed certificate or provide your own certificate and key. If you choose to autogenerate, you will be asked to list all IP addresses and domains for which to sign the certificate. For example, if you intend to access your StartTunnel web UI at a domain, include the domain in the list.
|
||||
1. To access your StartTunnel web interface securely over HTTPS, you need an SSL certificate. When prompted, select whether to autogenerate a certificate or provide your own. _This is only for accessing your StartTunnel web interface_.
|
||||
|
||||
1. You will receive a success message with 3 pieces of information:
|
||||
|
||||
- <https://IP:port>: the URL where you can reach your personal web interface.
|
||||
- Password: an autogenerated password for your interface. If you lose/forget it, you can reset using the CLI.
|
||||
- Root Certificate Authority: the Root CA of your StartTunnel instance. If not already, trust it in your browser or system keychain.
|
||||
- **<https://IP:port>**: the URL where you can reach your personal web interface.
|
||||
- **Password**: an autogenerated password for your interface. If you lose/forget it, you can reset it using the start-tunnel CLI.
|
||||
- **Root Certificate Authority**: the Root CA of your StartTunnel instance.
|
||||
|
||||
1. If you autogenerated your SSL certificate, visiting the `https://IP:port` URL in the browser will warn you that the website is insecure. This is expected. You have two options for getting past this warning:
|
||||
- option 1 (recommended): [Trust your StartTunnel Root CA on your connecting device](#trusting-your-starttunnel-root-ca).
|
||||
- Option 2: bypass the warning in the browser, creating a one-time security exception.
|
||||
|
||||
### Trusting your StartTunnel Root CA
|
||||
|
||||
1. Copy the contents of your Root CA (starting with -----BEGIN CERTIFICATE----- and ending with -----END CERTIFICATE-----).
|
||||
|
||||
2. Open a text editor:
|
||||
|
||||
- Linux: gedit, nano, or any editor
|
||||
- Mac: TextEdit
|
||||
- Windows: Notepad
|
||||
|
||||
3. Paste the contents of your Root CA.
|
||||
|
||||
4. Save the file with a `.crt` extension (e.g. `start-tunnel.crt`) (make sure it saves as plain text, not rich text).
|
||||
|
||||
5. Trust the Root CA on your client device(s):
|
||||
|
||||
- [Linux](https://staging.docs.start9.com/device-guides/linux/ca.html)
|
||||
- [Mac](https://staging.docs.start9.com/device-guides/mac/ca.html)
|
||||
- [Windows](https://staging.docs.start9.com/device-guides/windows/ca.html)
|
||||
- [Android/Graphene](https://staging.docs.start9.com/device-guides/android/ca.html)
|
||||
- [iOS](https://staging.docs.start9.com/device-guides/ios/ca.html)
|
||||
|
||||
201
agents/VERSION_BUMP.md
Normal file
201
agents/VERSION_BUMP.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# StartOS Version Bump Guide
|
||||
|
||||
This document explains how to bump the StartOS version across the entire codebase.
|
||||
|
||||
## Overview
|
||||
|
||||
When bumping from version `X.Y.Z-alpha.N` to `X.Y.Z-alpha.N+1`, you need to update files in multiple locations across the repository. The `// VERSION_BUMP` comment markers indicate where changes are needed.
|
||||
|
||||
## Files to Update
|
||||
|
||||
### 1. Core Rust Crate Version
|
||||
|
||||
**File: `core/startos/Cargo.toml`**
|
||||
|
||||
Update the version string (line ~18):
|
||||
|
||||
```toml
|
||||
version = "0.4.0-alpha.15" # VERSION_BUMP
|
||||
```
|
||||
|
||||
**File: `core/Cargo.lock`**
|
||||
|
||||
This file is auto-generated. After updating `Cargo.toml`, run:
|
||||
|
||||
```bash
|
||||
cd core
|
||||
cargo check
|
||||
```
|
||||
|
||||
This will update the version in `Cargo.lock` automatically.
|
||||
|
||||
### 2. Create New Version Migration Module
|
||||
|
||||
**File: `core/startos/src/version/vX_Y_Z_alpha_N+1.rs`**
|
||||
|
||||
Create a new version file by copying the previous version and updating:
|
||||
|
||||
```rust
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{VersionT, v0_4_0_alpha_14}; // Update to previous version
|
||||
use crate::prelude::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref V0_4_0_alpha_15: exver::Version = exver::Version::new(
|
||||
[0, 4, 0],
|
||||
[PreReleaseSegment::String("alpha".into()), 15.into()] // Update number
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Version;
|
||||
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_4_0_alpha_14::Version; // Update to previous version
|
||||
type PreUpRes = ();
|
||||
|
||||
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn semver(self) -> exver::Version {
|
||||
V0_4_0_alpha_15.clone() // Update version name
|
||||
}
|
||||
fn compat(self) -> &'static VersionRange {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||
// Add migration logic here if needed
|
||||
Ok(Value::Null)
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
// Add rollback logic here if needed
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Update Version Module Registry
|
||||
|
||||
**File: `core/startos/src/version/mod.rs`**
|
||||
|
||||
Make changes in **5 locations**:
|
||||
|
||||
#### Location 1: Module Declaration (~line 57)
|
||||
|
||||
Add the new module after the previous version:
|
||||
|
||||
```rust
|
||||
mod v0_4_0_alpha_14;
|
||||
mod v0_4_0_alpha_15; // Add this
|
||||
```
|
||||
|
||||
#### Location 2: Current Type Alias (~line 59)
|
||||
|
||||
Update the `Current` type and move the `// VERSION_BUMP` comment:
|
||||
|
||||
```rust
|
||||
pub type Current = v0_4_0_alpha_15::Version; // VERSION_BUMP
|
||||
```
|
||||
|
||||
#### Location 3: Version Enum (~line 175)
|
||||
|
||||
Remove `// VERSION_BUMP` from the previous version, add new variant, add comment:
|
||||
|
||||
```rust
|
||||
V0_4_0_alpha_14(Wrapper<v0_4_0_alpha_14::Version>),
|
||||
V0_4_0_alpha_15(Wrapper<v0_4_0_alpha_15::Version>), // VERSION_BUMP
|
||||
Other(exver::Version),
|
||||
```
|
||||
|
||||
#### Location 4: as_version_t() Match (~line 233)
|
||||
|
||||
Remove `// VERSION_BUMP`, add new match arm, add comment:
|
||||
|
||||
```rust
|
||||
Self::V0_4_0_alpha_14(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_15(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||
Self::Other(v) => {
|
||||
```
|
||||
|
||||
#### Location 5: as_exver() Match (~line 284, inside #[cfg(test)])
|
||||
|
||||
Remove `// VERSION_BUMP`, add new match arm, add comment:
|
||||
|
||||
```rust
|
||||
Version::V0_4_0_alpha_14(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_15(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||
Version::Other(x) => x.clone(),
|
||||
```
|
||||
|
||||
### 4. SDK TypeScript Version
|
||||
|
||||
**File: `sdk/package/lib/StartSdk.ts`**
|
||||
|
||||
Update the OSVersion constant (~line 64):
|
||||
|
||||
```typescript
|
||||
export const OSVersion = testTypeVersion("0.4.0-alpha.15");
|
||||
```
|
||||
|
||||
### 5. Web UI Package Version
|
||||
|
||||
**File: `web/package.json`**
|
||||
|
||||
Update the version field:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "startos-ui",
|
||||
"version": "0.4.0-alpha.15",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**File: `web/package-lock.json`**
|
||||
|
||||
This file is auto-generated, but it's faster to update manually. Find all instances of "startos-ui" and update the version field.
|
||||
|
||||
## Verification Step
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
## VERSION_BUMP Comment Pattern
|
||||
|
||||
The `// VERSION_BUMP` comment serves as a marker for where to make changes next time:
|
||||
|
||||
- Always **remove** it from the old location
|
||||
- **Add** the new version entry
|
||||
- **Move** the comment to mark the new location
|
||||
|
||||
This pattern helps you quickly find all the places that need updating in the next version bump.
|
||||
|
||||
## Summary Checklist
|
||||
|
||||
- [ ] Update `core/startos/Cargo.toml` version
|
||||
- [ ] Create new `core/startos/src/version/vX_Y_Z_alpha_N+1.rs` file
|
||||
- [ ] Update `core/startos/src/version/mod.rs` in 5 locations
|
||||
- [ ] Run `cargo check` to update `core/Cargo.lock`
|
||||
- [ ] Update `sdk/package/lib/StartSdk.ts` OSVersion
|
||||
- [ ] Update `web/package.json` and `web/package-lock.json` version
|
||||
- [ ] Verify all changes compile/build successfully
|
||||
|
||||
## Migration Logic
|
||||
|
||||
The `up()` and `down()` methods in the version file handle database migrations:
|
||||
|
||||
- **up()**: Migrates the database from the previous version to this version
|
||||
- **down()**: Rolls back from this version to the previous version
|
||||
- **pre_up()**: Runs before migration, useful for pre-migration checks or data gathering
|
||||
|
||||
If no migration is needed, return `Ok(Value::Null)` for `up()` and `Ok(())` for `down()`.
|
||||
|
||||
For complex migrations, you may need to:
|
||||
|
||||
1. Update `type PreUpRes` to pass data between `pre_up()` and `up()`
|
||||
2. Implement database transformations in the `up()` method
|
||||
3. Implement reverse transformations in `down()` for rollback support
|
||||
@@ -8,27 +8,22 @@ if [ "$0" != "./build-cargo-dep.sh" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
USE_TTY=
|
||||
if tty -s; then
|
||||
USE_TTY="-it"
|
||||
fi
|
||||
|
||||
if [ -z "$ARCH" ]; then
|
||||
ARCH=$(uname -m)
|
||||
fi
|
||||
|
||||
DOCKER_PLATFORM="linux/${ARCH}"
|
||||
if [ "$ARCH" = aarch64 ] || [ "$ARCH" = arm64 ]; then
|
||||
DOCKER_PLATFORM="linux/arm64"
|
||||
elif [ "$ARCH" = x86_64 ]; then
|
||||
DOCKER_PLATFORM="linux/amd64"
|
||||
RUST_ARCH="$ARCH"
|
||||
if [ "$ARCH" = "riscv64" ]; then
|
||||
RUST_ARCH="riscv64gc"
|
||||
fi
|
||||
|
||||
mkdir -p cargo-deps
|
||||
alias 'rust-musl-builder'='docker run $USE_TTY --platform=${DOCKER_PLATFORM} --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)"/cargo-deps:/home/rust/src -w /home/rust/src -P rust:alpine'
|
||||
|
||||
PREINSTALL=${PREINSTALL:-true}
|
||||
source core/builder-alias.sh
|
||||
|
||||
rust-musl-builder sh -c "$PREINSTALL && cargo install $* --target-dir /home/rust/src --target=$ARCH-unknown-linux-musl"
|
||||
sudo chown -R $USER cargo-deps
|
||||
sudo chown -R $USER ~/.cargo
|
||||
RUSTFLAGS="-C target-feature=+crt-static"
|
||||
|
||||
rust-zig-builder cargo-zigbuild install $* --target-dir /workdir/cargo-deps/ --target=$RUST_ARCH-unknown-linux-musl
|
||||
if [ "$(ls -nd "cargo-deps/$RUST_ARCH-unknown-linux-musl/release/${!#}" | awk '{ print $3 }')" != "$UID" ]; then
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID cargo-deps && chown -R $UID:$UID /usr/local/cargo"
|
||||
fi
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
- grub-common
|
||||
- grub-efi
|
||||
+ parted
|
||||
+ raspberrypi-net-mods
|
||||
|
||||
@@ -22,7 +22,7 @@ parse_essential_db_info() {
|
||||
RAM_GB="unknown"
|
||||
fi
|
||||
|
||||
RUNNING_SERVICES=$(jq -r '[.value.packageData[] | select(.status.main == "running")] | length' "$DB_DUMP" 2>/dev/null)
|
||||
RUNNING_SERVICES=$(jq -r '[.value.packageData[] | select(.statusInfo.started != null)] | length' "$DB_DUMP" 2>/dev/null)
|
||||
TOTAL_SERVICES=$(jq -r '.value.packageData | length' "$DB_DUMP" 2>/dev/null)
|
||||
|
||||
rm -f "$DB_DUMP"
|
||||
|
||||
@@ -1,29 +1,51 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -z "$sip" ] || [ -z "$dip" ] || [ -z "$sport" ] || [ -z "$dport" ]; then
|
||||
if [ -z "$sip" ] || [ -z "$dip" ] || [ -z "$dprefix" ] || [ -z "$sport" ] || [ -z "$dport" ]; then
|
||||
>&2 echo 'missing required env var'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rule_exists() {
|
||||
iptables -t nat -C "$@" 2>/dev/null
|
||||
}
|
||||
NAME="F$(echo "$sip:$sport -> $dip/$dprefix:$dport" | sha256sum | head -c 15)"
|
||||
|
||||
apply_rule() {
|
||||
if [ "$UNDO" = "1" ]; then
|
||||
if rule_exists "$@"; then
|
||||
iptables -t nat -D "$@"
|
||||
fi
|
||||
else
|
||||
if ! rule_exists "$@"; then
|
||||
iptables -t nat -A "$@"
|
||||
fi
|
||||
for kind in INPUT FORWARD ACCEPT; do
|
||||
if ! iptables -C $kind -j "${NAME}_${kind}" 2> /dev/null; then
|
||||
iptables -N "${NAME}_${kind}" 2> /dev/null
|
||||
iptables -A $kind -j "${NAME}_${kind}"
|
||||
fi
|
||||
}
|
||||
done
|
||||
for kind in PREROUTING INPUT OUTPUT POSTROUTING; do
|
||||
if ! iptables -t nat -C $kind -j "${NAME}_${kind}" 2> /dev/null; then
|
||||
iptables -t nat -N "${NAME}_${kind}" 2> /dev/null
|
||||
iptables -t nat -A $kind -j "${NAME}_${kind}"
|
||||
fi
|
||||
done
|
||||
|
||||
apply_rule PREROUTING -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport
|
||||
apply_rule OUTPUT -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport
|
||||
err=0
|
||||
trap 'err=1' ERR
|
||||
|
||||
for kind in INPUT FORWARD ACCEPT; do
|
||||
iptables -F "${NAME}_${kind}" 2> /dev/null
|
||||
done
|
||||
for kind in PREROUTING INPUT OUTPUT POSTROUTING; do
|
||||
iptables -t nat -F "${NAME}_${kind}" 2> /dev/null
|
||||
done
|
||||
if [ "$UNDO" = 1 ]; then
|
||||
conntrack -D -p tcp -d $sip --dport $sport
|
||||
fi
|
||||
conntrack -D -p tcp -d $sip --dport $sport || true # conntrack returns exit 1 if no connections are active
|
||||
conntrack -D -p udp -d $sip --dport $sport || true # conntrack returns exit 1 if no connections are active
|
||||
exit $err
|
||||
fi
|
||||
|
||||
iptables -t nat -A ${NAME}_PREROUTING -d "$sip" -p tcp --dport "$sport" -j DNAT --to-destination "$dip:$dport"
|
||||
iptables -t nat -A ${NAME}_PREROUTING -d "$sip" -p udp --dport "$sport" -j DNAT --to-destination "$dip:$dport"
|
||||
iptables -t nat -A ${NAME}_OUTPUT -d "$sip" -p tcp --dport "$sport" -j DNAT --to-destination "$dip:$dport"
|
||||
iptables -t nat -A ${NAME}_OUTPUT -d "$sip" -p udp --dport "$sport" -j DNAT --to-destination "$dip:$dport"
|
||||
|
||||
iptables -t nat -A ${NAME}_PREROUTING -s "$dip/$dprefix" -d "$sip" -p tcp --dport "$sport" -j DNAT --to-destination "$dip:$dport"
|
||||
iptables -t nat -A ${NAME}_PREROUTING -s "$dip/$dprefix" -d "$sip" -p udp --dport "$sport" -j DNAT --to-destination "$dip:$dport"
|
||||
iptables -t nat -A ${NAME}_POSTROUTING -s "$dip/$dprefix" -d "$dip" -p tcp --dport "$dport" -j MASQUERADE
|
||||
iptables -t nat -A ${NAME}_POSTROUTING -s "$dip/$dprefix" -d "$dip" -p udp --dport "$dport" -j MASQUERADE
|
||||
|
||||
iptables -A ${NAME}_FORWARD -d $dip -p tcp --dport $dport -m state --state NEW -j ACCEPT
|
||||
iptables -A ${NAME}_FORWARD -d $dip -p udp --dport $dport -m state --state NEW -j ACCEPT
|
||||
|
||||
exit $err
|
||||
@@ -1,36 +1,64 @@
|
||||
#!/bin/bash
|
||||
|
||||
fail=$(printf " [\033[31m fail \033[0m]")
|
||||
pass=$(printf " [\033[32m pass \033[0m]")
|
||||
# --- Config ---
|
||||
# Colors (using printf to ensure compatibility)
|
||||
GRAY=$(printf '\033[90m')
|
||||
GREEN=$(printf '\033[32m')
|
||||
RED=$(printf '\033[31m')
|
||||
NC=$(printf '\033[0m') # No Color
|
||||
|
||||
# Proxies to test
|
||||
proxies=(
|
||||
"Host Tor|127.0.1.1:9050"
|
||||
"Startd Tor|10.0.3.1:9050"
|
||||
)
|
||||
|
||||
# Default URLs
|
||||
onion_list=(
|
||||
"The Tor Project|http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion"
|
||||
"Start9|http://privacy34kn4ez3y3nijweec6w4g54i3g54sdv7r5mr6soma3w4begyd.onion"
|
||||
"Mempool|http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion"
|
||||
"DuckDuckGo|https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion"
|
||||
"Brave Search|https://search.brave4u7jddbv7cyviptqjc7jusxh72uik7zt6adtckl5f4nwy2v72qd.onion"
|
||||
)
|
||||
|
||||
# Check if ~/.startos/tor-check.list exists and read its contents if available
|
||||
if [ -f ~/.startos/tor-check.list ]; then
|
||||
while IFS= read -r line; do
|
||||
# Check if the line starts with a #
|
||||
if [[ ! "$line" =~ ^# ]]; then
|
||||
onion_list+=("$line")
|
||||
# Load custom list
|
||||
[ -f ~/.startos/tor-check.list ] && readarray -t custom_list < <(grep -v '^#' ~/.startos/tor-check.list) && onion_list+=("${custom_list[@]}")
|
||||
|
||||
# --- Functions ---
|
||||
print_line() { printf "${GRAY}────────────────────────────────────────${NC}\n"; }
|
||||
|
||||
# --- Main ---
|
||||
echo "Testing Onion Connections..."
|
||||
|
||||
for proxy_info in "${proxies[@]}"; do
|
||||
proxy_name="${proxy_info%%|*}"
|
||||
proxy_addr="${proxy_info#*|}"
|
||||
|
||||
print_line
|
||||
printf "${GRAY}Proxy: %s (%s)${NC}\n" "$proxy_name" "$proxy_addr"
|
||||
|
||||
for data in "${onion_list[@]}"; do
|
||||
name="${data%%|*}"
|
||||
url="${data#*|}"
|
||||
|
||||
# Capture verbose output + http code.
|
||||
# --no-progress-meter: Suppresses the "0 0 0" stats but keeps -v output
|
||||
output=$(curl -v --no-progress-meter --max-time 15 --socks5-hostname "$proxy_addr" "$url" 2>&1)
|
||||
exit_code=$?
|
||||
|
||||
if [ $exit_code -eq 0 ]; then
|
||||
printf " ${GREEN}[pass]${NC} %s (%s)\n" "$name" "$url"
|
||||
else
|
||||
printf " ${RED}[fail]${NC} %s (%s)\n" "$name" "$url"
|
||||
printf " ${RED}↳ Curl Error %s${NC}\n" "$exit_code"
|
||||
|
||||
# Print the last 4 lines of verbose log to show the specific handshake error
|
||||
# We look for lines starting with '*' or '>' or '<' to filter out junk if any remains
|
||||
echo "$output" | tail -n 4 | sed "s/^/ ${GRAY}/"
|
||||
fi
|
||||
done < ~/.startos/tor-check.list
|
||||
fi
|
||||
|
||||
echo "Testing connection to Onion Pages ..."
|
||||
|
||||
for data in "${onion_list[@]}"; do
|
||||
name="${data%%|*}"
|
||||
url="${data#*|}"
|
||||
if curl --socks5-hostname localhost:9050 "$url" > /dev/null 2>&1; then
|
||||
echo " ${pass}: $name ($url) "
|
||||
else
|
||||
echo " ${fail}: $name ($url) "
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
echo
|
||||
echo "Done."
|
||||
print_line
|
||||
# Reset color just in case
|
||||
printf "${NC}"
|
||||
|
||||
@@ -61,7 +61,7 @@ fi
|
||||
|
||||
chroot /media/startos/next bash -e << "EOF"
|
||||
|
||||
if dpkg -s grub-common 2>&1 > /dev/null; then
|
||||
if [ -f /boot/grub/grub.cfg ]; then
|
||||
grub-install /dev/$(eval $(lsblk -o MOUNTPOINT,PKNAME -P | grep 'MOUNTPOINT="/media/startos/root"') && echo $PKNAME)
|
||||
update-grub
|
||||
fi
|
||||
@@ -70,7 +70,7 @@ EOF
|
||||
|
||||
sync
|
||||
|
||||
umount -R /media/startos/next
|
||||
umount -Rl /media/startos/next
|
||||
umount /media/startos/upper
|
||||
umount /media/startos/lower
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:bookworm
|
||||
FROM debian:forky
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
@@ -25,7 +25,8 @@ RUN apt-get update && \
|
||||
systemd-container \
|
||||
systemd-sysv \
|
||||
dbus \
|
||||
dbus-user-session
|
||||
dbus-user-session \
|
||||
nodejs
|
||||
|
||||
RUN systemctl mask \
|
||||
systemd-firstboot.service \
|
||||
@@ -38,17 +39,6 @@ RUN git config --global --add safe.directory /root/start-os
|
||||
RUN mkdir -p /etc/debspawn && \
|
||||
echo "AllowUnsafePermissions=true" > /etc/debspawn/global.toml
|
||||
|
||||
ENV NVM_DIR=~/.nvm
|
||||
ENV NODE_VERSION=22
|
||||
RUN mkdir -p $NVM_DIR && \
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash && \
|
||||
. $NVM_DIR/nvm.sh \
|
||||
nvm install $NODE_VERSION && \
|
||||
nvm use $NODE_VERSION && \
|
||||
nvm alias default $NODE_VERSION && \
|
||||
ln -s $(which node) /usr/bin/node && \
|
||||
ln -s $(which npm) /usr/bin/npm
|
||||
|
||||
RUN mkdir -p /root/start-os
|
||||
WORKDIR /root/start-os
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ "$FORCE_COMPAT" = 1 ] || ( [ "$REQUIRES" = "linux" ] && [ "$(uname -s)" != "Linux" ] ) || ( [ "$REQUIRES" = "debian" ] && ! which dpkg > /dev/null ); then
|
||||
if [ "$FORCE_COMPAT" = 1 ] || ( [ "$REQUIRES" = "linux" ] && [ "$(uname -s)" != "Linux" ] ) || ( [ "$REQUIRES" = "debian" ] && ! which dpkg > /dev/null ) || ( [ "$REQUIRES" = "qemu" ] && ! which qemu-$ARCH > /dev/null ); then
|
||||
project_pwd="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)/"
|
||||
pwd="$(pwd)/"
|
||||
if ! [[ "$pwd" = "$project_pwd"* ]]; then
|
||||
@@ -20,7 +20,7 @@ if [ "$FORCE_COMPAT" = 1 ] || ( [ "$REQUIRES" = "linux" ] && [ "$(uname -s)" !=
|
||||
while ! docker exec os-compat systemctl is-active --quiet multi-user.target 2> /dev/null; do sleep .5; done
|
||||
docker exec -eARCH -eENVIRONMENT -ePLATFORM -eGIT_BRANCH_AS_HASH -ePROJECT -eDEPENDS -eCONFLICTS $USE_TTY -w "/root/start-os${rel_pwd}" os-compat $@
|
||||
code=$?
|
||||
docker stop os-compat
|
||||
docker stop os-compat > /dev/null
|
||||
exit $code
|
||||
else
|
||||
exec $@
|
||||
|
||||
@@ -4,6 +4,7 @@ OnFailure=container-runtime-failure.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment=RUST_LOG=startos=debug
|
||||
ExecStart=/usr/bin/node --experimental-detect-module --trace-warnings --unhandled-rejections=warn /usr/lib/startos/init/index.js
|
||||
Restart=no
|
||||
|
||||
|
||||
2
container-runtime/package-lock.json
generated
2
container-runtime/package-lock.json
generated
@@ -38,7 +38,7 @@
|
||||
},
|
||||
"../sdk/dist": {
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.43",
|
||||
"version": "0.4.0-beta.45",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^3.0.0",
|
||||
|
||||
@@ -289,6 +289,7 @@ export function makeEffects(context: EffectContext): Effects {
|
||||
getStatus(...[o]: Parameters<T.Effects["getStatus"]>) {
|
||||
return rpcRound("get-status", o) as ReturnType<T.Effects["getStatus"]>
|
||||
},
|
||||
/// DEPRECATED
|
||||
setMainStatus(o: { status: "running" | "stopped" }): Promise<null> {
|
||||
return rpcRound("set-main-status", o) as ReturnType<
|
||||
T.Effects["setHealth"]
|
||||
|
||||
@@ -146,6 +146,7 @@ const handleRpc = (id: IdType, result: Promise<RpcResult>) =>
|
||||
|
||||
const hasId = object({ id: idType }).test
|
||||
export class RpcListener {
|
||||
shouldExit = false
|
||||
unixSocketServer = net.createServer(async (server) => {})
|
||||
private _system: System | undefined
|
||||
private callbacks: CallbackHolder | undefined
|
||||
@@ -158,6 +159,8 @@ export class RpcListener {
|
||||
|
||||
this.unixSocketServer.listen(SOCKET_PATH)
|
||||
|
||||
console.log("Listening on %s", SOCKET_PATH)
|
||||
|
||||
this.unixSocketServer.on("connection", (s) => {
|
||||
let id: IdType = null
|
||||
const captureId = <X>(x: X) => {
|
||||
@@ -208,6 +211,11 @@ export class RpcListener {
|
||||
.catch(mapError)
|
||||
.then(logData("response"))
|
||||
.then(writeDataToSocket)
|
||||
.then((_) => {
|
||||
if (this.shouldExit) {
|
||||
process.exit(0)
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(`Major error in socket handling: ${e}`)
|
||||
console.debug(`Data in: ${a.toString()}`)
|
||||
@@ -284,10 +292,13 @@ export class RpcListener {
|
||||
)
|
||||
})
|
||||
.when(stopType, async ({ id }) => {
|
||||
this.callbacks?.removeChild("main")
|
||||
return handleRpc(
|
||||
id,
|
||||
this.system.stop().then((result) => ({ result })),
|
||||
this.system.stop().then((result) => {
|
||||
this.callbacks?.removeChild("main")
|
||||
|
||||
return { result }
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(exitType, async ({ id, params }) => {
|
||||
@@ -308,6 +319,7 @@ export class RpcListener {
|
||||
}),
|
||||
target,
|
||||
)
|
||||
this.shouldExit = true
|
||||
}
|
||||
})().then((result) => ({ result })),
|
||||
)
|
||||
|
||||
@@ -118,7 +118,7 @@ export class DockerProcedureContainer extends Drop {
|
||||
subpath: volumeMount.path,
|
||||
readonly: volumeMount.readonly,
|
||||
volumeId: volumeMount["volume-id"],
|
||||
filetype: "directory",
|
||||
idmap: [],
|
||||
},
|
||||
})
|
||||
} else if (volumeMount.type === "backup") {
|
||||
|
||||
@@ -120,6 +120,7 @@ export class MainLoop {
|
||||
? {
|
||||
preferredExternalPort: lanConf.external,
|
||||
alpn: { specified: ["http/1.1"] },
|
||||
addXForwardedHeaders: false,
|
||||
}
|
||||
: null,
|
||||
})
|
||||
@@ -133,7 +134,7 @@ export class MainLoop {
|
||||
delete this.mainEvent
|
||||
delete this.healthLoops
|
||||
await main?.daemon
|
||||
.stop()
|
||||
.term()
|
||||
.catch((e: unknown) => console.error(`Main loop error`, utils.asError(e)))
|
||||
this.effects.setMainStatus({ status: "stopped" })
|
||||
if (healthLoops) healthLoops.forEach((x) => clearInterval(x.interval))
|
||||
|
||||
@@ -456,6 +456,7 @@ export class SystemForEmbassy implements System {
|
||||
addSsl = {
|
||||
preferredExternalPort: lanPortNum,
|
||||
alpn: { specified: [] },
|
||||
addXForwardedHeaders: false,
|
||||
}
|
||||
}
|
||||
return [
|
||||
@@ -888,7 +889,6 @@ export class SystemForEmbassy implements System {
|
||||
effects: Effects,
|
||||
timeoutMs: number | null,
|
||||
): Promise<PropertiesReturn> {
|
||||
// TODO BLU-J set the properties ever so often
|
||||
const setConfigValue = this.manifest.properties
|
||||
if (!setConfigValue) throw new Error("There is no properties")
|
||||
if (setConfigValue.type === "docker") {
|
||||
@@ -1043,7 +1043,7 @@ export class SystemForEmbassy implements System {
|
||||
volumeId: "embassy",
|
||||
subpath: null,
|
||||
readonly: true,
|
||||
filetype: "directory",
|
||||
idmap: [],
|
||||
},
|
||||
})
|
||||
configFile
|
||||
@@ -1191,7 +1191,7 @@ async function updateConfig(
|
||||
volumeId: "embassy",
|
||||
subpath: null,
|
||||
readonly: true,
|
||||
filetype: "directory",
|
||||
idmap: [],
|
||||
},
|
||||
})
|
||||
const remoteConfig = configFile
|
||||
@@ -1241,11 +1241,11 @@ async function updateConfig(
|
||||
: catchFn(
|
||||
() =>
|
||||
(specValue.target === "lan-address"
|
||||
? filled.addressInfo!.localHostnames[0] ||
|
||||
filled.addressInfo!.onionHostnames[0]
|
||||
: filled.addressInfo!.onionHostnames[0] ||
|
||||
filled.addressInfo!.localHostnames[0]
|
||||
).hostname.value,
|
||||
? filled.addressInfo!.filter({ kind: "mdns" }) ||
|
||||
filled.addressInfo!.onion
|
||||
: filled.addressInfo!.onion ||
|
||||
filled.addressInfo!.filter({ kind: "mdns" })
|
||||
).hostnames[0].hostname.value,
|
||||
) || ""
|
||||
mutConfigValue[key] = url
|
||||
}
|
||||
|
||||
@@ -68,22 +68,18 @@ export class SystemForStartOs implements System {
|
||||
try {
|
||||
if (this.runningMain || this.starting) return
|
||||
this.starting = true
|
||||
effects.constRetry = utils.once(() => effects.restart())
|
||||
effects.constRetry = utils.once(() => {
|
||||
console.debug(".const() triggered")
|
||||
effects.restart()
|
||||
})
|
||||
let mainOnTerm: () => Promise<void> | undefined
|
||||
const started = async (onTerm: () => Promise<void>) => {
|
||||
await effects.setMainStatus({ status: "running" })
|
||||
mainOnTerm = onTerm
|
||||
return null
|
||||
}
|
||||
const daemons = await (
|
||||
await this.abi.main({
|
||||
effects,
|
||||
started,
|
||||
})
|
||||
).build()
|
||||
this.runningMain = {
|
||||
stop: async () => {
|
||||
if (mainOnTerm) await mainOnTerm()
|
||||
await daemons.term()
|
||||
},
|
||||
}
|
||||
|
||||
@@ -7,6 +7,12 @@ const getDependencies: AllGetDependencies = {
|
||||
system: getSystem,
|
||||
}
|
||||
|
||||
for (let s of ["SIGTERM", "SIGINT", "SIGHUP"]) {
|
||||
process.on(s, (s) => {
|
||||
console.log(`Caught ${s}`)
|
||||
})
|
||||
}
|
||||
|
||||
new RpcListener(getDependencies)
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,15 +26,15 @@ fi
|
||||
|
||||
QEMU=
|
||||
if [ "$ARCH" != "$(uname -m)" ]; then
|
||||
QEMU=/usr/bin/qemu-${ARCH}-static
|
||||
if ! which qemu-$ARCH-static > /dev/null; then
|
||||
>&2 echo qemu-user-static is required for cross-platform builds
|
||||
QEMU=/usr/bin/qemu-${ARCH}
|
||||
if ! which qemu-$ARCH > /dev/null; then
|
||||
>&2 echo qemu-user is required for cross-platform builds
|
||||
sudo umount tmp/combined
|
||||
sudo umount tmp/lower
|
||||
sudo rm -rf tmp
|
||||
exit 1
|
||||
fi
|
||||
sudo cp $(which qemu-$ARCH-static) tmp/combined${QEMU}
|
||||
sudo cp $(which qemu-$ARCH) tmp/combined${QEMU}
|
||||
fi
|
||||
|
||||
sudo mkdir -p tmp/combined/usr/lib/startos/
|
||||
@@ -44,7 +44,7 @@ sudo cp container-runtime.service tmp/combined/lib/systemd/system/container-runt
|
||||
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime.service
|
||||
sudo cp container-runtime-failure.service tmp/combined/lib/systemd/system/container-runtime-failure.service
|
||||
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime-failure.service
|
||||
sudo cp ../core/target/${RUST_ARCH}-unknown-linux-musl/release/containerbox tmp/combined/usr/bin/start-container
|
||||
sudo cp ../core/target/${RUST_ARCH}-unknown-linux-musl/release/start-container tmp/combined/usr/bin/start-container
|
||||
echo -e '#!/bin/bash\nexec start-container "$@"' | sudo tee tmp/combined/usr/bin/start-cli # TODO: remove
|
||||
sudo chmod +x tmp/combined/usr/bin/start-cli
|
||||
sudo chown 0:0 tmp/combined/usr/bin/start-container
|
||||
|
||||
1226
core/Cargo.lock
generated
1226
core/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
||||
[workspace]
|
||||
|
||||
members = ["helpers", "models", "startos"]
|
||||
members = ["startos"]
|
||||
|
||||
@@ -5,6 +5,21 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
source ./builder-alias.sh
|
||||
|
||||
set -ea
|
||||
|
||||
INSTALL=false
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--install)
|
||||
INSTALL=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
>&2 echo "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
shopt -s expand_aliases
|
||||
|
||||
PROFILE=${PROFILE:-release}
|
||||
@@ -12,7 +27,7 @@ if [ "${PROFILE}" = "release" ]; then
|
||||
BUILD_FLAGS="--release"
|
||||
else
|
||||
if [ "$PROFILE" != "debug"]; then
|
||||
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
|
||||
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||
PROFILE=debug
|
||||
fi
|
||||
fi
|
||||
@@ -46,18 +61,7 @@ if [ -z "${TARGET:-}" ]; then
|
||||
fi
|
||||
|
||||
cd ..
|
||||
|
||||
# Ensure GIT_HASH.txt exists if not created by higher-level build steps
|
||||
if [ ! -f GIT_HASH.txt ] && command -v git >/dev/null 2>&1; then
|
||||
git rev-parse HEAD > GIT_HASH.txt || true
|
||||
fi
|
||||
|
||||
FEATURES="$(echo "${ENVIRONMENT:-}" | sed 's/-/,/g')"
|
||||
FEATURE_ARGS="cli"
|
||||
if [ -n "$FEATURES" ]; then
|
||||
FEATURE_ARGS="$FEATURE_ARGS,$FEATURES"
|
||||
fi
|
||||
|
||||
RUSTFLAGS=""
|
||||
if [[ "${ENVIRONMENT:-}" =~ (^|-)console($|-) ]]; then
|
||||
RUSTFLAGS="--cfg tokio_unstable"
|
||||
@@ -65,7 +69,11 @@ fi
|
||||
|
||||
echo "FEATURES=\"$FEATURES\""
|
||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features $FEATURE_ARGS --locked --bin start-cli --target=$TARGET
|
||||
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=$FEATURES --locked --bin start-cli --target=$TARGET
|
||||
if [ "$(ls -nd "core/target/$TARGET/$PROFILE/start-cli" | awk '{ print $3 }')" != "$UID" ]; then
|
||||
rust-zig-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
||||
rust-zig-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /usr/local/cargo"
|
||||
fi
|
||||
|
||||
if [ "$INSTALL" = "true" ]; then
|
||||
cp "core/target/$TARGET/$PROFILE/start-cli" ~/.cargo/bin/start-cli
|
||||
fi
|
||||
@@ -12,7 +12,7 @@ if [ "${PROFILE}" = "release" ]; then
|
||||
BUILD_FLAGS="--release"
|
||||
else
|
||||
if [ "$PROFILE" != "debug"]; then
|
||||
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
|
||||
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||
PROFILE=debug
|
||||
fi
|
||||
fi
|
||||
@@ -40,7 +40,7 @@ fi
|
||||
|
||||
echo "FEATURES=\"$FEATURES\""
|
||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-registry,registry,$FEATURES --locked --bin registrybox --target=$RUST_ARCH-unknown-linux-musl
|
||||
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=$FEATURES --locked --bin registrybox --target=$RUST_ARCH-unknown-linux-musl
|
||||
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/registrybox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /usr/local/cargo"
|
||||
fi
|
||||
@@ -12,7 +12,7 @@ if [ "${PROFILE}" = "release" ]; then
|
||||
BUILD_FLAGS="--release"
|
||||
else
|
||||
if [ "$PROFILE" != "debug"]; then
|
||||
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
|
||||
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||
PROFILE=debug
|
||||
fi
|
||||
fi
|
||||
@@ -40,7 +40,7 @@ fi
|
||||
|
||||
echo "FEATURES=\"$FEATURES\""
|
||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-container,$FEATURES --locked --bin containerbox --target=$RUST_ARCH-unknown-linux-musl
|
||||
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/containerbox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=$FEATURES --locked --bin start-container --target=$RUST_ARCH-unknown-linux-musl
|
||||
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/start-container" | awk '{ print $3 }')" != "$UID" ]; then
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /usr/local/cargo"
|
||||
fi
|
||||
@@ -12,7 +12,7 @@ if [ "${PROFILE}" = "release" ]; then
|
||||
BUILD_FLAGS="--release"
|
||||
else
|
||||
if [ "$PROFILE" != "debug"]; then
|
||||
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
|
||||
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||
PROFILE=debug
|
||||
fi
|
||||
fi
|
||||
@@ -40,7 +40,7 @@ fi
|
||||
|
||||
echo "FEATURES=\"$FEATURES\""
|
||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli,startd,$FEATURES --locked --bin startbox --target=$RUST_ARCH-unknown-linux-musl
|
||||
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=$FEATURES --locked --bin startbox --target=$RUST_ARCH-unknown-linux-musl
|
||||
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/startbox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /usr/local/cargo"
|
||||
fi
|
||||
@@ -12,7 +12,7 @@ if [ "${PROFILE}" = "release" ]; then
|
||||
BUILD_FLAGS="--release"
|
||||
else
|
||||
if [ "$PROFILE" != "debug"]; then
|
||||
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
|
||||
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||
PROFILE=debug
|
||||
fi
|
||||
fi
|
||||
@@ -38,7 +38,7 @@ if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||
fi
|
||||
echo "FEATURES=\"$FEATURES\""
|
||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||
rust-zig-builder cargo test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features test,$FEATURES --locked 'export_bindings_'
|
||||
rust-zig-builder cargo test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features test,$FEATURES --locked 'export_bindings_'
|
||||
if [ "$(ls -nd "core/startos/bindings" | awk '{ print $3 }')" != "$UID" ]; then
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID core/startos/bindings && chown -R $UID:$UID /root/.cargo"
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID core/startos/bindings && chown -R $UID:$UID /usr/local/cargo"
|
||||
fi
|
||||
@@ -12,7 +12,7 @@ if [ "${PROFILE}" = "release" ]; then
|
||||
BUILD_FLAGS="--release"
|
||||
else
|
||||
if [ "$PROFILE" != "debug"]; then
|
||||
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
|
||||
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||
PROFILE=debug
|
||||
fi
|
||||
fi
|
||||
@@ -40,7 +40,7 @@ fi
|
||||
|
||||
echo "FEATURES=\"$FEATURES\""
|
||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-tunnel,tunnel,$FEATURES --locked --bin tunnelbox --target=$RUST_ARCH-unknown-linux-musl
|
||||
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=$FEATURES --locked --bin tunnelbox --target=$RUST_ARCH-unknown-linux-musl
|
||||
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/tunnelbox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /usr/local/cargo"
|
||||
fi
|
||||
@@ -5,4 +5,4 @@ if tty -s; then
|
||||
USE_TTY="-it"
|
||||
fi
|
||||
|
||||
alias 'rust-zig-builder'='docker run '"$USE_TTY"' --rm -e "RUSTFLAGS=$RUSTFLAGS" -e "CFLAGS=-D_FORTIFY_SOURCE=2" -e "CXXFLAGS=-D_FORTIFY_SOURCE=2" -e SCCACHE_GHA_ENABLED -e SCCACHE_GHA_VERSION -e ACTIONS_RESULTS_URL -e ACTIONS_RUNTIME_TOKEN -v "$HOME/.cargo/registry":/usr/local/cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$HOME/.cache/sccache":/root/.cache/sccache -v "$(pwd)":/workdir -w /workdir -P start9/cargo-zigbuild'
|
||||
alias 'rust-zig-builder'='docker run '"$USE_TTY"' --rm -e "RUSTFLAGS=$RUSTFLAGS" -e "PKG_CONFIG_SYSROOT_DIR=/opt/sysroot/$ARCH" -e PKG_CONFIG_PATH="" -e PKG_CONFIG_LIBDIR="/opt/sysroot/$ARCH/usr/lib/pkgconfig" -e "AWS_LC_SYS_CMAKE_TOOLCHAIN_FILE_riscv64gc_unknown_linux_musl=/root/cmake-overrides/toolchain-riscv64-musl-clang.cmake" -e SCCACHE_GHA_ENABLED -e SCCACHE_GHA_VERSION -e ACTIONS_RESULTS_URL -e ACTIONS_RUNTIME_TOKEN -v "$HOME/.cargo/registry":/usr/local/cargo/registry -v "$HOME/.cargo/git":/usr/local/cargo/git -v "$HOME/.cache/sccache":/root/.cache/sccache -v "$HOME/.cache/cargo-zigbuild:/root/.cache/cargo-zigbuild" -v "$(pwd)":/workdir -w /workdir start9/cargo-zigbuild'
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "helpers"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
color-eyre = "0.6.2"
|
||||
futures = "0.3.28"
|
||||
lazy_async_pool = "0.3.3"
|
||||
models = { path = "../models" }
|
||||
pin-project = "1.1.3"
|
||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" }
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-stream = { version = "0.1.14", features = ["io-util", "sync"] }
|
||||
tracing = "0.1.39"
|
||||
@@ -1,31 +0,0 @@
|
||||
use std::task::Poll;
|
||||
|
||||
use tokio::io::{AsyncRead, ReadBuf};
|
||||
|
||||
#[pin_project::pin_project]
|
||||
pub struct ByteReplacementReader<R> {
|
||||
pub replace: u8,
|
||||
pub with: u8,
|
||||
#[pin]
|
||||
pub inner: R,
|
||||
}
|
||||
impl<R: AsyncRead> AsyncRead for ByteReplacementReader<R> {
|
||||
fn poll_read(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> std::task::Poll<std::io::Result<()>> {
|
||||
let this = self.project();
|
||||
match this.inner.poll_read(cx, buf) {
|
||||
Poll::Ready(Ok(())) => {
|
||||
for idx in 0..buf.filled().len() {
|
||||
if buf.filled()[idx] == *this.replace {
|
||||
buf.filled_mut()[idx] = *this.with;
|
||||
}
|
||||
}
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
a => a,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
use std::future::Future;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::{eyre, Context, Error};
|
||||
use futures::future::BoxFuture;
|
||||
use futures::FutureExt;
|
||||
use models::ResultExt;
|
||||
use tokio::fs::File;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::task::{JoinError, JoinHandle, LocalSet};
|
||||
|
||||
mod byte_replacement_reader;
|
||||
mod rsync;
|
||||
mod script_dir;
|
||||
pub use byte_replacement_reader::*;
|
||||
pub use rsync::*;
|
||||
pub use script_dir::*;
|
||||
|
||||
pub fn const_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn to_tmp_path(path: impl AsRef<Path>) -> Result<PathBuf, Error> {
|
||||
let path = path.as_ref();
|
||||
if let (Some(parent), Some(file_name)) =
|
||||
(path.parent(), path.file_name().and_then(|f| f.to_str()))
|
||||
{
|
||||
Ok(parent.join(format!(".{}.tmp", file_name)))
|
||||
} else {
|
||||
Err(eyre!("invalid path: {}", path.display()))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn canonicalize(
|
||||
path: impl AsRef<Path> + Send + Sync,
|
||||
create_parent: bool,
|
||||
) -> Result<PathBuf, Error> {
|
||||
fn create_canonical_folder<'a>(
|
||||
path: impl AsRef<Path> + Send + Sync + 'a,
|
||||
) -> BoxFuture<'a, Result<PathBuf, Error>> {
|
||||
async move {
|
||||
let path = canonicalize(path, true).await?;
|
||||
tokio::fs::create_dir(&path)
|
||||
.await
|
||||
.with_context(|| path.display().to_string())?;
|
||||
Ok(path)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
let path = path.as_ref();
|
||||
if tokio::fs::metadata(path).await.is_err() {
|
||||
let parent = path.parent().unwrap_or(Path::new("."));
|
||||
if let Some(file_name) = path.file_name() {
|
||||
if create_parent && tokio::fs::metadata(parent).await.is_err() {
|
||||
return Ok(create_canonical_folder(parent).await?.join(file_name));
|
||||
} else {
|
||||
return Ok(tokio::fs::canonicalize(parent)
|
||||
.await
|
||||
.with_context(|| parent.display().to_string())?
|
||||
.join(file_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
tokio::fs::canonicalize(&path)
|
||||
.await
|
||||
.with_context(|| path.display().to_string())
|
||||
}
|
||||
|
||||
#[pin_project::pin_project(PinnedDrop)]
|
||||
pub struct NonDetachingJoinHandle<T>(#[pin] JoinHandle<T>);
|
||||
impl<T> NonDetachingJoinHandle<T> {
|
||||
pub async fn wait_for_abort(self) -> Result<T, JoinError> {
|
||||
self.abort();
|
||||
self.await
|
||||
}
|
||||
}
|
||||
impl<T> From<JoinHandle<T>> for NonDetachingJoinHandle<T> {
|
||||
fn from(t: JoinHandle<T>) -> Self {
|
||||
NonDetachingJoinHandle(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for NonDetachingJoinHandle<T> {
|
||||
type Target = JoinHandle<T>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl<T> DerefMut for NonDetachingJoinHandle<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
#[pin_project::pinned_drop]
|
||||
impl<T> PinnedDrop for NonDetachingJoinHandle<T> {
|
||||
fn drop(self: std::pin::Pin<&mut Self>) {
|
||||
let this = self.project();
|
||||
this.0.into_ref().get_ref().abort()
|
||||
}
|
||||
}
|
||||
impl<T> Future for NonDetachingJoinHandle<T> {
|
||||
type Output = Result<T, JoinError>;
|
||||
fn poll(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
this.0.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AtomicFile {
|
||||
tmp_path: PathBuf,
|
||||
path: PathBuf,
|
||||
file: Option<File>,
|
||||
}
|
||||
impl AtomicFile {
|
||||
pub async fn new(
|
||||
path: impl AsRef<Path> + Send + Sync,
|
||||
tmp_path: Option<impl AsRef<Path> + Send + Sync>,
|
||||
) -> Result<Self, Error> {
|
||||
let path = canonicalize(&path, true).await?;
|
||||
let tmp_path = if let Some(tmp_path) = tmp_path {
|
||||
canonicalize(&tmp_path, true).await?
|
||||
} else {
|
||||
to_tmp_path(&path)?
|
||||
};
|
||||
let file = File::create(&tmp_path)
|
||||
.await
|
||||
.with_context(|| tmp_path.display().to_string())?;
|
||||
Ok(Self {
|
||||
tmp_path,
|
||||
path,
|
||||
file: Some(file),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn rollback(mut self) -> Result<(), Error> {
|
||||
drop(self.file.take());
|
||||
tokio::fs::remove_file(&self.tmp_path)
|
||||
.await
|
||||
.with_context(|| format!("rm {}", self.tmp_path.display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn save(mut self) -> Result<(), Error> {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
if let Some(file) = self.file.as_mut() {
|
||||
file.flush().await?;
|
||||
file.shutdown().await?;
|
||||
file.sync_all().await?;
|
||||
}
|
||||
drop(self.file.take());
|
||||
tokio::fs::rename(&self.tmp_path, &self.path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("mv {} -> {}", self.tmp_path.display(), self.path.display())
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl std::ops::Deref for AtomicFile {
|
||||
type Target = File;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.file.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
impl std::ops::DerefMut for AtomicFile {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.file.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
impl Drop for AtomicFile {
|
||||
fn drop(&mut self) {
|
||||
if let Some(file) = self.file.take() {
|
||||
drop(file);
|
||||
let path = std::mem::take(&mut self.tmp_path);
|
||||
tokio::spawn(async move { tokio::fs::remove_file(path).await.log_err() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TimedResource<T: 'static + Send> {
|
||||
handle: NonDetachingJoinHandle<Option<T>>,
|
||||
ready: oneshot::Sender<()>,
|
||||
}
|
||||
impl<T: 'static + Send> TimedResource<T> {
|
||||
pub fn new(resource: T, timer: Duration) -> Self {
|
||||
let (send, recv) = oneshot::channel();
|
||||
let handle = tokio::spawn(async move {
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(timer) => {
|
||||
drop(resource);
|
||||
None
|
||||
},
|
||||
_ = recv => Some(resource),
|
||||
}
|
||||
});
|
||||
Self {
|
||||
handle: handle.into(),
|
||||
ready: send,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_destructor<
|
||||
Fn: FnOnce(T) -> Fut + Send + 'static,
|
||||
Fut: Future<Output = ()> + Send,
|
||||
>(
|
||||
resource: T,
|
||||
timer: Duration,
|
||||
destructor: Fn,
|
||||
) -> Self {
|
||||
let (send, recv) = oneshot::channel();
|
||||
let handle = tokio::spawn(async move {
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(timer) => {
|
||||
destructor(resource).await;
|
||||
None
|
||||
},
|
||||
_ = recv => Some(resource),
|
||||
}
|
||||
});
|
||||
Self {
|
||||
handle: handle.into(),
|
||||
ready: send,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(self) -> Option<T> {
|
||||
let _ = self.ready.send(());
|
||||
self.handle.await.unwrap()
|
||||
}
|
||||
|
||||
pub fn is_timed_out(&self) -> bool {
|
||||
self.ready.is_closed()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn spawn_local<
|
||||
T: 'static + Send,
|
||||
F: FnOnce() -> Fut + Send + 'static,
|
||||
Fut: Future<Output = T> + 'static,
|
||||
>(
|
||||
fut: F,
|
||||
) -> NonDetachingJoinHandle<T> {
|
||||
let (send, recv) = tokio::sync::oneshot::channel();
|
||||
std::thread::spawn(move || {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async move {
|
||||
let set = LocalSet::new();
|
||||
send.send(set.spawn_local(fut()).into())
|
||||
.unwrap_or_else(|_| unreachable!());
|
||||
set.await
|
||||
})
|
||||
});
|
||||
recv.await.unwrap()
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use color_eyre::Report;
|
||||
use models::InterfaceId;
|
||||
use models::PackageId;
|
||||
use serde_json::Value;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub struct RuntimeDropped;
|
||||
|
||||
pub struct Callback {
|
||||
id: Arc<String>,
|
||||
sender: mpsc::UnboundedSender<(Arc<String>, Vec<Value>)>,
|
||||
}
|
||||
impl Callback {
|
||||
pub fn new(id: String, sender: mpsc::UnboundedSender<(Arc<String>, Vec<Value>)>) -> Self {
|
||||
Self {
|
||||
id: Arc::new(id),
|
||||
sender,
|
||||
}
|
||||
}
|
||||
pub fn is_listening(&self) -> bool {
|
||||
self.sender.is_closed()
|
||||
}
|
||||
pub fn call(&self, args: Vec<Value>) -> Result<(), RuntimeDropped> {
|
||||
self.sender
|
||||
.send((self.id.clone(), args))
|
||||
.map_err(|_| RuntimeDropped)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AddressSchemaOnion {
|
||||
pub id: InterfaceId,
|
||||
pub external_port: u16,
|
||||
}
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AddressSchemaLocal {
|
||||
pub id: InterfaceId,
|
||||
pub external_port: u16,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Address(pub String);
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Domain;
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Name;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
#[allow(unused_variables)]
|
||||
pub trait OsApi: Send + Sync + 'static {
|
||||
async fn get_service_config(
|
||||
&self,
|
||||
id: PackageId,
|
||||
path: &str,
|
||||
callback: Option<Callback>,
|
||||
) -> Result<Vec<Value>, Report>;
|
||||
|
||||
async fn bind_local(
|
||||
&self,
|
||||
internal_port: u16,
|
||||
address_schema: AddressSchemaLocal,
|
||||
) -> Result<Address, Report>;
|
||||
async fn bind_onion(
|
||||
&self,
|
||||
internal_port: u16,
|
||||
address_schema: AddressSchemaOnion,
|
||||
) -> Result<Address, Report>;
|
||||
|
||||
async fn unbind_local(&self, id: InterfaceId, external: u16) -> Result<(), Report>;
|
||||
async fn unbind_onion(&self, id: InterfaceId, external: u16) -> Result<(), Report>;
|
||||
fn set_started(&self) -> Result<(), Report>;
|
||||
async fn restart(&self) -> Result<(), Report>;
|
||||
async fn start(&self) -> Result<(), Report>;
|
||||
async fn stop(&self) -> Result<(), Report>;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use models::{PackageId, VersionString};
|
||||
|
||||
pub const PKG_SCRIPT_DIR: &str = "package-data/scripts";
|
||||
|
||||
pub fn script_dir<P: AsRef<Path>>(
|
||||
datadir: P,
|
||||
pkg_id: &PackageId,
|
||||
version: &VersionString,
|
||||
) -> PathBuf {
|
||||
datadir
|
||||
.as_ref()
|
||||
.join(&*PKG_SCRIPT_DIR)
|
||||
.join(pkg_id)
|
||||
.join(version.as_str())
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
set -ea
|
||||
shopt -s expand_aliases
|
||||
|
||||
web="../web/dist/static"
|
||||
[ -d "$web" ] || mkdir -p "$web"
|
||||
|
||||
if [ -z "$PLATFORM" ]; then
|
||||
PLATFORM=$(uname -m)
|
||||
fi
|
||||
|
||||
if [ "$PLATFORM" = "arm64" ]; then
|
||||
PLATFORM="aarch64"
|
||||
fi
|
||||
|
||||
cargo install --path=./startos --no-default-features --features=cli,docker --bin start-cli --locked
|
||||
@@ -1,46 +0,0 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "models"
|
||||
version = "0.1.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
arti = ["arti-client"]
|
||||
|
||||
[dependencies]
|
||||
arti-client = { version = "0.33", default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
axum = "0.8.4"
|
||||
base64 = "0.22.1"
|
||||
color-eyre = "0.6.2"
|
||||
ed25519-dalek = { version = "2.0.0", features = ["serde"] }
|
||||
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
||||
"serde",
|
||||
] }
|
||||
gpt = "4.1.0"
|
||||
ipnet = "2.8.0"
|
||||
lazy_static = "1.4"
|
||||
lettre = { version = "0.11", default-features = false }
|
||||
mbrman = "0.6.0"
|
||||
miette = "7.6.0"
|
||||
num_enum = "0.7.1"
|
||||
openssl = { version = "0.10.57", features = ["vendored"] }
|
||||
patch-db = { version = "*", path = "../../patch-db/patch-db", features = [
|
||||
"trace",
|
||||
] }
|
||||
rand = "0.9.1"
|
||||
regex = "1.10.2"
|
||||
reqwest = "0.12"
|
||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" }
|
||||
rustls = "0.23"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
ssh-key = "0.6.2"
|
||||
thiserror = "2.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
torut = "0.2.1"
|
||||
tracing = "0.1.39"
|
||||
ts-rs = "9"
|
||||
typeid = "1"
|
||||
yasi = { version = "0.1.6", features = ["serde", "ts-rs"] }
|
||||
zbus = "5"
|
||||
@@ -1,669 +0,0 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
use axum::http::uri::InvalidUri;
|
||||
use axum::http::StatusCode;
|
||||
use color_eyre::eyre::eyre;
|
||||
use num_enum::TryFromPrimitive;
|
||||
use patch_db::Revision;
|
||||
use rpc_toolkit::reqwest;
|
||||
use rpc_toolkit::yajrc::{
|
||||
RpcError, INVALID_PARAMS_ERROR, INVALID_REQUEST_ERROR, METHOD_NOT_FOUND_ERROR, PARSE_ERROR,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::InvalidId;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
|
||||
#[repr(i32)]
|
||||
pub enum ErrorKind {
|
||||
Unknown = 1,
|
||||
Filesystem = 2,
|
||||
Docker = 3,
|
||||
ConfigSpecViolation = 4,
|
||||
ConfigRulesViolation = 5,
|
||||
NotFound = 6,
|
||||
IncorrectPassword = 7,
|
||||
VersionIncompatible = 8,
|
||||
Network = 9,
|
||||
Registry = 10,
|
||||
Serialization = 11,
|
||||
Deserialization = 12,
|
||||
Utf8 = 13,
|
||||
ParseVersion = 14,
|
||||
IncorrectDisk = 15,
|
||||
// Nginx = 16,
|
||||
Dependency = 17,
|
||||
ParseS9pk = 18,
|
||||
ParseUrl = 19,
|
||||
DiskNotAvailable = 20,
|
||||
BlockDevice = 21,
|
||||
InvalidOnionAddress = 22,
|
||||
Pack = 23,
|
||||
ValidateS9pk = 24,
|
||||
DiskCorrupted = 25, // Remove
|
||||
Tor = 26,
|
||||
ConfigGen = 27,
|
||||
ParseNumber = 28,
|
||||
Database = 29,
|
||||
InvalidId = 30,
|
||||
InvalidSignature = 31,
|
||||
Backup = 32,
|
||||
Restore = 33,
|
||||
Authorization = 34,
|
||||
AutoConfigure = 35,
|
||||
Action = 36,
|
||||
RateLimited = 37,
|
||||
InvalidRequest = 38,
|
||||
MigrationFailed = 39,
|
||||
Uninitialized = 40,
|
||||
ParseNetAddress = 41,
|
||||
ParseSshKey = 42,
|
||||
SoundError = 43,
|
||||
ParseTimestamp = 44,
|
||||
ParseSysInfo = 45,
|
||||
Wifi = 46,
|
||||
Journald = 47,
|
||||
DiskManagement = 48,
|
||||
OpenSsl = 49,
|
||||
PasswordHashGeneration = 50,
|
||||
DiagnosticMode = 51,
|
||||
ParseDbField = 52,
|
||||
Duplicate = 53,
|
||||
MultipleErrors = 54,
|
||||
Incoherent = 55,
|
||||
InvalidBackupTargetId = 56,
|
||||
ProductKeyMismatch = 57,
|
||||
LanPortConflict = 58,
|
||||
Javascript = 59,
|
||||
Pem = 60,
|
||||
TLSInit = 61,
|
||||
Ascii = 62,
|
||||
MissingHeader = 63,
|
||||
Grub = 64,
|
||||
Systemd = 65,
|
||||
OpenSsh = 66,
|
||||
Zram = 67,
|
||||
Lshw = 68,
|
||||
CpuSettings = 69,
|
||||
Firmware = 70,
|
||||
Timeout = 71,
|
||||
Lxc = 72,
|
||||
Cancelled = 73,
|
||||
Git = 74,
|
||||
DBus = 75,
|
||||
InstallFailed = 76,
|
||||
UpdateFailed = 77,
|
||||
Smtp = 78,
|
||||
}
|
||||
impl ErrorKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
use ErrorKind::*;
|
||||
match self {
|
||||
Unknown => "Unknown Error",
|
||||
Filesystem => "Filesystem I/O Error",
|
||||
Docker => "Docker Error",
|
||||
ConfigSpecViolation => "Config Spec Violation",
|
||||
ConfigRulesViolation => "Config Rules Violation",
|
||||
NotFound => "Not Found",
|
||||
IncorrectPassword => "Incorrect Password",
|
||||
VersionIncompatible => "Version Incompatible",
|
||||
Network => "Network Error",
|
||||
Registry => "Registry Error",
|
||||
Serialization => "Serialization Error",
|
||||
Deserialization => "Deserialization Error",
|
||||
Utf8 => "UTF-8 Parse Error",
|
||||
ParseVersion => "Version Parsing Error",
|
||||
IncorrectDisk => "Incorrect Disk",
|
||||
// Nginx => "Nginx Error",
|
||||
Dependency => "Dependency Error",
|
||||
ParseS9pk => "S9PK Parsing Error",
|
||||
ParseUrl => "URL Parsing Error",
|
||||
DiskNotAvailable => "Disk Not Available",
|
||||
BlockDevice => "Block Device Error",
|
||||
InvalidOnionAddress => "Invalid Onion Address",
|
||||
Pack => "Pack Error",
|
||||
ValidateS9pk => "S9PK Validation Error",
|
||||
DiskCorrupted => "Disk Corrupted", // Remove
|
||||
Tor => "Tor Daemon Error",
|
||||
ConfigGen => "Config Generation Error",
|
||||
ParseNumber => "Number Parsing Error",
|
||||
Database => "Database Error",
|
||||
InvalidId => "Invalid ID",
|
||||
InvalidSignature => "Invalid Signature",
|
||||
Backup => "Backup Error",
|
||||
Restore => "Restore Error",
|
||||
Authorization => "Unauthorized",
|
||||
AutoConfigure => "Auto-Configure Error",
|
||||
Action => "Action Failed",
|
||||
RateLimited => "Rate Limited",
|
||||
InvalidRequest => "Invalid Request",
|
||||
MigrationFailed => "Migration Failed",
|
||||
Uninitialized => "Uninitialized",
|
||||
ParseNetAddress => "Net Address Parsing Error",
|
||||
ParseSshKey => "SSH Key Parsing Error",
|
||||
SoundError => "Sound Interface Error",
|
||||
ParseTimestamp => "Timestamp Parsing Error",
|
||||
ParseSysInfo => "System Info Parsing Error",
|
||||
Wifi => "WiFi Internal Error",
|
||||
Journald => "Journald Error",
|
||||
DiskManagement => "Disk Management Error",
|
||||
OpenSsl => "OpenSSL Internal Error",
|
||||
PasswordHashGeneration => "Password Hash Generation Error",
|
||||
DiagnosticMode => "Server is in Diagnostic Mode",
|
||||
ParseDbField => "Database Field Parse Error",
|
||||
Duplicate => "Duplication Error",
|
||||
MultipleErrors => "Multiple Errors",
|
||||
Incoherent => "Incoherent",
|
||||
InvalidBackupTargetId => "Invalid Backup Target ID",
|
||||
ProductKeyMismatch => "Incompatible Product Keys",
|
||||
LanPortConflict => "Incompatible LAN Port Configuration",
|
||||
Javascript => "Javascript Engine Error",
|
||||
Pem => "PEM Encoding Error",
|
||||
TLSInit => "TLS Backend Initialization Error",
|
||||
Ascii => "ASCII Parse Error",
|
||||
MissingHeader => "Missing Header",
|
||||
Grub => "Grub Error",
|
||||
Systemd => "Systemd Error",
|
||||
OpenSsh => "OpenSSH Error",
|
||||
Zram => "Zram Error",
|
||||
Lshw => "LSHW Error",
|
||||
CpuSettings => "CPU Settings Error",
|
||||
Firmware => "Firmware Error",
|
||||
Timeout => "Timeout Error",
|
||||
Lxc => "LXC Error",
|
||||
Cancelled => "Cancelled",
|
||||
Git => "Git Error",
|
||||
DBus => "DBus Error",
|
||||
InstallFailed => "Install Failed",
|
||||
UpdateFailed => "Update Failed",
|
||||
Smtp => "SMTP Error",
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for ErrorKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Error {
|
||||
pub source: color_eyre::eyre::Error,
|
||||
pub debug: Option<color_eyre::eyre::Error>,
|
||||
pub kind: ErrorKind,
|
||||
pub revision: Option<Revision>,
|
||||
pub task: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}: {:#}", self.kind.as_str(), self.source)
|
||||
}
|
||||
}
|
||||
impl Debug for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}: {:?}",
|
||||
self.kind.as_str(),
|
||||
self.debug.as_ref().unwrap_or(&self.source)
|
||||
)
|
||||
}
|
||||
}
|
||||
impl Error {
|
||||
pub fn new<E: Into<color_eyre::eyre::Error> + std::fmt::Debug + 'static>(
|
||||
source: E,
|
||||
kind: ErrorKind,
|
||||
) -> Self {
|
||||
let debug = (typeid::of::<E>() == typeid::of::<color_eyre::eyre::Error>())
|
||||
.then(|| eyre!("{source:?}"));
|
||||
Error {
|
||||
source: source.into(),
|
||||
debug,
|
||||
kind,
|
||||
revision: None,
|
||||
task: None,
|
||||
}
|
||||
}
|
||||
pub fn clone_output(&self) -> Self {
|
||||
Error {
|
||||
source: eyre!("{}", self.source),
|
||||
debug: self.debug.as_ref().map(|e| eyre!("{e}")),
|
||||
kind: self.kind,
|
||||
revision: self.revision.clone(),
|
||||
task: None,
|
||||
}
|
||||
}
|
||||
pub fn with_task(mut self, task: JoinHandle<()>) -> Self {
|
||||
self.task = Some(task);
|
||||
self
|
||||
}
|
||||
pub async fn wait(mut self) -> Self {
|
||||
if let Some(task) = &mut self.task {
|
||||
task.await.log_err();
|
||||
}
|
||||
self.task.take();
|
||||
self
|
||||
}
|
||||
}
|
||||
impl axum::response::IntoResponse for Error {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let mut res = axum::Json(RpcError::from(self)).into_response();
|
||||
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
res
|
||||
}
|
||||
}
|
||||
impl From<std::convert::Infallible> for Error {
|
||||
fn from(value: std::convert::Infallible) -> Self {
|
||||
match value {}
|
||||
}
|
||||
}
|
||||
impl From<InvalidId> for Error {
|
||||
fn from(err: InvalidId) -> Self {
|
||||
Error::new(err, ErrorKind::InvalidId)
|
||||
}
|
||||
}
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Filesystem)
|
||||
}
|
||||
}
|
||||
impl From<std::str::Utf8Error> for Error {
|
||||
fn from(e: std::str::Utf8Error) -> Self {
|
||||
Error::new(e, ErrorKind::Utf8)
|
||||
}
|
||||
}
|
||||
impl From<std::string::FromUtf8Error> for Error {
|
||||
fn from(e: std::string::FromUtf8Error) -> Self {
|
||||
Error::new(e, ErrorKind::Utf8)
|
||||
}
|
||||
}
|
||||
impl From<exver::ParseError> for Error {
|
||||
fn from(e: exver::ParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseVersion)
|
||||
}
|
||||
}
|
||||
impl From<rpc_toolkit::url::ParseError> for Error {
|
||||
fn from(e: rpc_toolkit::url::ParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseUrl)
|
||||
}
|
||||
}
|
||||
impl From<std::num::ParseIntError> for Error {
|
||||
fn from(e: std::num::ParseIntError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseNumber)
|
||||
}
|
||||
}
|
||||
impl From<std::num::ParseFloatError> for Error {
|
||||
fn from(e: std::num::ParseFloatError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseNumber)
|
||||
}
|
||||
}
|
||||
impl From<patch_db::Error> for Error {
|
||||
fn from(e: patch_db::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Database)
|
||||
}
|
||||
}
|
||||
impl From<ed25519_dalek::SignatureError> for Error {
|
||||
fn from(e: ed25519_dalek::SignatureError) -> Self {
|
||||
Error::new(e, ErrorKind::InvalidSignature)
|
||||
}
|
||||
}
|
||||
impl From<std::net::AddrParseError> for Error {
|
||||
fn from(e: std::net::AddrParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseNetAddress)
|
||||
}
|
||||
}
|
||||
impl From<ipnet::AddrParseError> for Error {
|
||||
fn from(e: ipnet::AddrParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseNetAddress)
|
||||
}
|
||||
}
|
||||
impl From<openssl::error::ErrorStack> for Error {
|
||||
fn from(e: openssl::error::ErrorStack) -> Self {
|
||||
Error::new(eyre!("{}", e), ErrorKind::OpenSsl)
|
||||
}
|
||||
}
|
||||
impl From<mbrman::Error> for Error {
|
||||
fn from(e: mbrman::Error) -> Self {
|
||||
Error::new(e, ErrorKind::DiskManagement)
|
||||
}
|
||||
}
|
||||
impl From<gpt::GptError> for Error {
|
||||
fn from(e: gpt::GptError) -> Self {
|
||||
Error::new(e, ErrorKind::DiskManagement)
|
||||
}
|
||||
}
|
||||
impl From<gpt::mbr::MBRError> for Error {
|
||||
fn from(e: gpt::mbr::MBRError) -> Self {
|
||||
Error::new(e, ErrorKind::DiskManagement)
|
||||
}
|
||||
}
|
||||
impl From<InvalidUri> for Error {
|
||||
fn from(e: InvalidUri) -> Self {
|
||||
Error::new(eyre!("{}", e), ErrorKind::ParseUrl)
|
||||
}
|
||||
}
|
||||
impl From<ssh_key::Error> for Error {
|
||||
fn from(e: ssh_key::Error) -> Self {
|
||||
Error::new(e, ErrorKind::OpenSsh)
|
||||
}
|
||||
}
|
||||
impl From<reqwest::Error> for Error {
|
||||
fn from(e: reqwest::Error) -> Self {
|
||||
let kind = match e {
|
||||
_ if e.is_builder() => ErrorKind::ParseUrl,
|
||||
_ if e.is_decode() => ErrorKind::Deserialization,
|
||||
_ => ErrorKind::Network,
|
||||
};
|
||||
Error::new(e, kind)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "arti")]
|
||||
impl From<arti_client::Error> for Error {
|
||||
fn from(e: arti_client::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Tor)
|
||||
}
|
||||
}
|
||||
impl From<torut::control::ConnError> for Error {
|
||||
fn from(e: torut::control::ConnError) -> Self {
|
||||
Error::new(e, ErrorKind::Tor)
|
||||
}
|
||||
}
|
||||
impl From<zbus::Error> for Error {
|
||||
fn from(e: zbus::Error) -> Self {
|
||||
Error::new(e, ErrorKind::DBus)
|
||||
}
|
||||
}
|
||||
impl From<rustls::Error> for Error {
|
||||
fn from(e: rustls::Error) -> Self {
|
||||
Error::new(e, ErrorKind::OpenSsl)
|
||||
}
|
||||
}
|
||||
impl From<lettre::error::Error> for Error {
|
||||
fn from(e: lettre::error::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Smtp)
|
||||
}
|
||||
}
|
||||
impl From<lettre::transport::smtp::Error> for Error {
|
||||
fn from(e: lettre::transport::smtp::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Smtp)
|
||||
}
|
||||
}
|
||||
impl From<lettre::address::AddressError> for Error {
|
||||
fn from(e: lettre::address::AddressError) -> Self {
|
||||
Error::new(e, ErrorKind::Smtp)
|
||||
}
|
||||
}
|
||||
impl From<patch_db::value::Error> for Error {
|
||||
fn from(value: patch_db::value::Error) -> Self {
|
||||
match value.kind {
|
||||
patch_db::value::ErrorKind::Serialization => {
|
||||
Error::new(value.source, ErrorKind::Serialization)
|
||||
}
|
||||
patch_db::value::ErrorKind::Deserialization => {
|
||||
Error::new(value.source, ErrorKind::Deserialization)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct ErrorData {
|
||||
pub details: String,
|
||||
pub debug: String,
|
||||
}
|
||||
impl Display for ErrorData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(&self.details, f)
|
||||
}
|
||||
}
|
||||
impl Debug for ErrorData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(&self.debug, f)
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ErrorData {}
|
||||
impl From<Error> for ErrorData {
|
||||
fn from(value: Error) -> Self {
|
||||
Self {
|
||||
details: value.to_string(),
|
||||
debug: format!("{:?}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<&RpcError> for ErrorData {
|
||||
fn from(value: &RpcError) -> Self {
|
||||
Self {
|
||||
details: value
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|d| {
|
||||
d.as_object()
|
||||
.and_then(|d| {
|
||||
d.get("details")
|
||||
.and_then(|d| d.as_str().map(|s| s.to_owned()))
|
||||
})
|
||||
.or_else(|| d.as_str().map(|s| s.to_owned()))
|
||||
})
|
||||
.unwrap_or_else(|| value.message.clone().into_owned()),
|
||||
debug: value
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|d| {
|
||||
d.as_object()
|
||||
.and_then(|d| {
|
||||
d.get("debug")
|
||||
.and_then(|d| d.as_str().map(|s| s.to_owned()))
|
||||
})
|
||||
.or_else(|| d.as_str().map(|s| s.to_owned()))
|
||||
})
|
||||
.unwrap_or_else(|| value.message.clone().into_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for RpcError {
|
||||
fn from(e: Error) -> Self {
|
||||
let mut data_object = serde_json::Map::with_capacity(3);
|
||||
data_object.insert("details".to_owned(), format!("{}", e.source).into());
|
||||
data_object.insert("debug".to_owned(), format!("{:?}", e.source).into());
|
||||
data_object.insert(
|
||||
"revision".to_owned(),
|
||||
match serde_json::to_value(&e.revision) {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
tracing::warn!("Error serializing revision for Error object: {}", e);
|
||||
serde_json::Value::Null
|
||||
}
|
||||
},
|
||||
);
|
||||
RpcError {
|
||||
code: e.kind as i32,
|
||||
message: e.kind.as_str().into(),
|
||||
data: Some(
|
||||
match serde_json::to_value(&ErrorData {
|
||||
details: format!("{}", e.source),
|
||||
debug: format!("{:?}", e.source),
|
||||
}) {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
tracing::warn!("Error serializing revision for Error object: {}", e);
|
||||
serde_json::Value::Null
|
||||
}
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<RpcError> for Error {
|
||||
fn from(e: RpcError) -> Self {
|
||||
Error::new(
|
||||
ErrorData::from(&e),
|
||||
if let Ok(kind) = e.code.try_into() {
|
||||
kind
|
||||
} else if e.code == METHOD_NOT_FOUND_ERROR.code {
|
||||
ErrorKind::NotFound
|
||||
} else if e.code == PARSE_ERROR.code
|
||||
|| e.code == INVALID_PARAMS_ERROR.code
|
||||
|| e.code == INVALID_REQUEST_ERROR.code
|
||||
{
|
||||
ErrorKind::Deserialization
|
||||
} else {
|
||||
ErrorKind::Unknown
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ErrorCollection(Vec<Error>);
|
||||
impl ErrorCollection {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn handle<T, E: Into<Error>>(&mut self, result: Result<T, E>) -> Option<T> {
|
||||
match result {
|
||||
Ok(a) => Some(a),
|
||||
Err(e) => {
|
||||
self.0.push(e.into());
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_result(self) -> Result<(), Error> {
|
||||
if self.0.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(eyre!("{}", self), ErrorKind::MultipleErrors))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<ErrorCollection> for Result<(), Error> {
|
||||
fn from(e: ErrorCollection) -> Self {
|
||||
e.into_result()
|
||||
}
|
||||
}
|
||||
impl<T, E: Into<Error>> Extend<Result<T, E>> for ErrorCollection {
|
||||
fn extend<I: IntoIterator<Item = Result<T, E>>>(&mut self, iter: I) {
|
||||
for item in iter {
|
||||
self.handle(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for ErrorCollection {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for (idx, e) in self.0.iter().enumerate() {
|
||||
if idx > 0 {
|
||||
write!(f, "; ")?;
|
||||
}
|
||||
write!(f, "{}", e)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ResultExt<T, E>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error>;
|
||||
fn with_ctx<F: FnOnce(&E) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error>;
|
||||
fn log_err(self) -> Option<T>;
|
||||
}
|
||||
impl<T, E> ResultExt<T, E> for Result<T, E>
|
||||
where
|
||||
color_eyre::eyre::Error: From<E>,
|
||||
E: std::fmt::Debug + 'static,
|
||||
{
|
||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
||||
self.map_err(|e| Error::new(e, kind))
|
||||
}
|
||||
|
||||
fn with_ctx<F: FnOnce(&E) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
||||
self.map_err(|e| {
|
||||
let (kind, ctx) = f(&e);
|
||||
let debug = (typeid::of::<E>() == typeid::of::<color_eyre::eyre::Error>())
|
||||
.then(|| eyre!("{ctx}: {e:?}"));
|
||||
let source = color_eyre::eyre::Error::from(e);
|
||||
let with_ctx = format!("{ctx}: {source}");
|
||||
let source = source.wrap_err(with_ctx);
|
||||
Error {
|
||||
kind,
|
||||
source,
|
||||
debug,
|
||||
revision: None,
|
||||
task: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn log_err(self) -> Option<T> {
|
||||
match self {
|
||||
Ok(a) => Some(a),
|
||||
Err(e) => {
|
||||
let e: color_eyre::eyre::Error = e.into();
|
||||
tracing::error!("{e}");
|
||||
tracing::debug!("{e:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> ResultExt<T, Error> for Result<T, Error> {
|
||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
||||
self.map_err(|e| Error { kind, ..e })
|
||||
}
|
||||
|
||||
fn with_ctx<F: FnOnce(&Error) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
||||
self.map_err(|e| {
|
||||
let (kind, ctx) = f(&e);
|
||||
let source = e.source;
|
||||
let with_ctx = format!("{ctx}: {source}");
|
||||
let source = source.wrap_err(with_ctx);
|
||||
let debug = e.debug.map(|e| {
|
||||
let with_ctx = format!("{ctx}: {e}");
|
||||
e.wrap_err(with_ctx)
|
||||
});
|
||||
Error {
|
||||
kind,
|
||||
source,
|
||||
debug,
|
||||
..e
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn log_err(self) -> Option<T> {
|
||||
match self {
|
||||
Ok(a) => Some(a),
|
||||
Err(e) => {
|
||||
tracing::error!("{e}");
|
||||
tracing::debug!("{e:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OptionExt<T>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn or_not_found(self, message: impl std::fmt::Display) -> Result<T, Error>;
|
||||
}
|
||||
impl<T> OptionExt<T> for Option<T> {
|
||||
fn or_not_found(self, message: impl std::fmt::Display) -> Result<T, Error> {
|
||||
self.ok_or_else(|| Error::new(eyre!("{}", message), ErrorKind::NotFound))
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ensure_code {
|
||||
($x:expr, $c:expr, $fmt:expr $(, $arg:expr)*) => {
|
||||
if !($x) {
|
||||
return Err(Error::new(color_eyre::eyre::eyre!($fmt, $($arg, )*), $c));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
mod clap;
|
||||
mod data_url;
|
||||
mod errors;
|
||||
mod id;
|
||||
mod mime;
|
||||
mod procedure_name;
|
||||
mod version;
|
||||
|
||||
pub use clap::*;
|
||||
pub use data_url::*;
|
||||
pub use errors::*;
|
||||
pub use id::*;
|
||||
pub use mime::*;
|
||||
pub use procedure_name::*;
|
||||
pub use version::*;
|
||||
@@ -12,7 +12,7 @@ if [ "${PROFILE}" = "release" ]; then
|
||||
BUILD_FLAGS="--release"
|
||||
else
|
||||
if [ "$PROFILE" != "debug"]; then
|
||||
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
|
||||
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||
PROFILE=debug
|
||||
fi
|
||||
fi
|
||||
@@ -41,5 +41,5 @@ fi
|
||||
|
||||
echo "FEATURES=\"$FEATURES\""
|
||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||
rust-zig-builder cargo test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=test,$FEATURES --workspace --locked -- --skip export_bindings_
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||
rust-zig-builder cargo test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=test,$FEATURES --workspace --locked --lib -- --skip export_bindings_
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /usr/local/cargo"
|
||||
|
||||
@@ -15,7 +15,7 @@ license = "MIT"
|
||||
name = "start-os"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/Start9Labs/start-os"
|
||||
version = "0.4.0-alpha.13" # VERSION_BUMP
|
||||
version = "0.4.0-alpha.16" # VERSION_BUMP
|
||||
|
||||
[lib]
|
||||
name = "startos"
|
||||
@@ -23,28 +23,27 @@ path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "startbox"
|
||||
path = "src/main.rs"
|
||||
path = "src/main/startbox.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "start-cli"
|
||||
path = "src/main.rs"
|
||||
path = "src/main/start-cli.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "containerbox"
|
||||
path = "src/main.rs"
|
||||
name = "start-container"
|
||||
path = "src/main/start-container.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "registrybox"
|
||||
path = "src/main.rs"
|
||||
path = "src/main/registrybox.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tunnelbox"
|
||||
path = "src/main.rs"
|
||||
path = "src/main/tunnelbox.rs"
|
||||
|
||||
[features]
|
||||
arti = [
|
||||
"arti-client",
|
||||
"models/arti",
|
||||
"safelog",
|
||||
"tor-cell",
|
||||
"tor-hscrypto",
|
||||
@@ -54,19 +53,10 @@ arti = [
|
||||
"tor-proto",
|
||||
"tor-rtcompat",
|
||||
]
|
||||
cli = ["cli-registry", "cli-startd", "cli-tunnel"]
|
||||
cli-container = ["procfs", "pty-process"]
|
||||
cli-registry = []
|
||||
cli-startd = []
|
||||
cli-tunnel = []
|
||||
console = ["console-subscriber", "tokio/tracing"]
|
||||
default = ["cli", "cli-container", "registry", "startd", "tunnel"]
|
||||
dev = ["backtrace-on-stack-overflow"]
|
||||
docker = []
|
||||
registry = []
|
||||
startd = []
|
||||
default = []
|
||||
dev = []
|
||||
test = []
|
||||
tunnel = []
|
||||
unstable = ["backtrace-on-stack-overflow"]
|
||||
|
||||
[dependencies]
|
||||
@@ -93,9 +83,8 @@ async-compression = { version = "0.4.32", features = [
|
||||
] }
|
||||
async-stream = "0.3.5"
|
||||
async-trait = "0.1.74"
|
||||
axum = { version = "0.8.4", features = ["ws"] }
|
||||
axum = { version = "0.8.4", features = ["ws", "http2"] }
|
||||
backtrace-on-stack-overflow = { version = "0.3.0", optional = true }
|
||||
barrage = "0.2.3"
|
||||
base32 = "0.5.0"
|
||||
base64 = "0.22.1"
|
||||
base64ct = "1.6.0"
|
||||
@@ -105,16 +94,16 @@ bytes = "1"
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
clap = { version = "4.4.12", features = ["string"] }
|
||||
color-eyre = "0.6.2"
|
||||
console = "0.15.7"
|
||||
console-subscriber = { version = "0.4.1", optional = true }
|
||||
console = "0.16.2"
|
||||
console-subscriber = { version = "0.5.0", optional = true }
|
||||
const_format = "0.2.34"
|
||||
cookie = "0.18.0"
|
||||
cookie_store = "0.21.0"
|
||||
cookie_store = "0.22.0"
|
||||
curve25519-dalek = "4.1.3"
|
||||
der = { version = "0.7.9", features = ["derive", "pem"] }
|
||||
digest = "0.10.7"
|
||||
divrem = "1.0.0"
|
||||
dns-lookup = "2.1.0"
|
||||
dns-lookup = "3.0.1"
|
||||
ed25519 = { version = "2.2.3", features = ["alloc", "pem", "pkcs8"] }
|
||||
ed25519-dalek = { version = "2.2.0", features = [
|
||||
"digest",
|
||||
@@ -132,7 +121,6 @@ fd-lock-rs = "0.1.4"
|
||||
form_urlencoded = "1.2.1"
|
||||
futures = "0.3.28"
|
||||
gpt = "4.1.0"
|
||||
helpers = { path = "../helpers" }
|
||||
hex = "0.4.3"
|
||||
hickory-client = "0.25.2"
|
||||
hickory-server = "0.25.2"
|
||||
@@ -158,7 +146,7 @@ imbl = { version = "6", features = ["serde", "small-chunks"] }
|
||||
imbl-value = { version = "0.4.3", features = ["ts-rs"] }
|
||||
include_dir = { version = "0.7.3", features = ["metadata"] }
|
||||
indexmap = { version = "2.0.2", features = ["serde"] }
|
||||
indicatif = { version = "0.17.7", features = ["tokio"] }
|
||||
indicatif = { version = "0.18.3", features = ["tokio"] }
|
||||
inotify = "0.11.0"
|
||||
integer-encoding = { version = "4.0.0", features = ["tokio_async"] }
|
||||
ipnet = { version = "2.8.0", features = ["serde"] }
|
||||
@@ -185,7 +173,6 @@ log = "0.4.20"
|
||||
mbrman = "0.6.0"
|
||||
miette = { version = "7.6.0", features = ["fancy"] }
|
||||
mio = "1"
|
||||
models = { version = "*", path = "../models" }
|
||||
new_mime_guess = "4"
|
||||
nix = { version = "0.30.1", features = [
|
||||
"fs",
|
||||
@@ -211,33 +198,32 @@ pbkdf2 = "0.12.2"
|
||||
pin-project = "1.1.3"
|
||||
pkcs8 = { version = "0.10.2", features = ["std"] }
|
||||
prettytable-rs = "0.10.0"
|
||||
procfs = { version = "0.17.0", optional = true }
|
||||
proptest = "1.3.1"
|
||||
proptest-derive = "0.5.0"
|
||||
pty-process = { version = "0.5.1", optional = true }
|
||||
proptest-derive = "0.7.0"
|
||||
qrcode = "0.14.1"
|
||||
r3bl_tui = "0.7.6"
|
||||
rand = "0.9.2"
|
||||
regex = "1.10.2"
|
||||
reqwest = { version = "0.12.4", features = ["json", "socks", "stream"] }
|
||||
reqwest_cookie_store = "0.8.0"
|
||||
reqwest = { version = "0.12.25", features = [
|
||||
"json",
|
||||
"socks",
|
||||
"stream",
|
||||
"http2",
|
||||
] }
|
||||
reqwest_cookie_store = "0.9.0"
|
||||
rpassword = "7.2.0"
|
||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", rev = "068db90" }
|
||||
rust-argon2 = "2.0.0"
|
||||
rust-argon2 = "3.0.0"
|
||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git" }
|
||||
safelog = { version = "0.4.8", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
semver = { version = "1.0.20", features = ["serde"] }
|
||||
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_urlencoded = "0.7"
|
||||
serde_with = { version = "3.4.0", features = ["json", "macros"] }
|
||||
serde_toml = { package = "toml", version = "0.9.9+spec-1.0.0" }
|
||||
serde_yaml = { package = "serde_yml", version = "0.0.12" }
|
||||
sha-crypt = "0.5.0"
|
||||
sha2 = "0.10.2"
|
||||
shell-words = "1"
|
||||
signal-hook = "0.3.17"
|
||||
simple-logging = "2.0.2"
|
||||
socket2 = { version = "0.6.0", features = ["all"] }
|
||||
socks5-impl = { version = "0.7.2", features = ["client", "server"] }
|
||||
sqlx = { version = "0.8.6", features = [
|
||||
@@ -276,19 +262,19 @@ torut = "0.2.1"
|
||||
tower-service = "0.3.3"
|
||||
tracing = "0.1.39"
|
||||
tracing-error = "0.2.0"
|
||||
tracing-futures = "0.2.5"
|
||||
tracing-journald = "0.3.0"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
ts-rs = "9.0.1"
|
||||
typed-builder = "0.21.0"
|
||||
unix-named-pipe = "0.2.0"
|
||||
typed-builder = "0.23.2"
|
||||
url = { version = "2.4.1", features = ["serde"] }
|
||||
urlencoding = "2.1.3"
|
||||
uuid = { version = "1.4.1", features = ["v4"] }
|
||||
visit-rs = "0.1.1"
|
||||
x25519-dalek = { version = "2.0.1", features = ["static_secrets"] }
|
||||
zbus = "5.1.1"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
procfs = "0.18.0"
|
||||
pty-process = "0.5.1"
|
||||
|
||||
[profile.test]
|
||||
opt-level = 3
|
||||
|
||||
@@ -7,7 +7,7 @@ use openssl::x509::X509;
|
||||
|
||||
use crate::db::model::DatabaseModel;
|
||||
use crate::hostname::{Hostname, generate_hostname, generate_id};
|
||||
use crate::net::ssl::{generate_key, make_root_cert};
|
||||
use crate::net::ssl::{gen_nistp256, make_root_cert};
|
||||
use crate::net::tor::TorSecretKey;
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::Pem;
|
||||
@@ -37,7 +37,7 @@ impl AccountInfo {
|
||||
let server_id = generate_id();
|
||||
let hostname = generate_hostname();
|
||||
let tor_key = vec![TorSecretKey::generate()];
|
||||
let root_ca_key = generate_key()?;
|
||||
let root_ca_key = gen_nistp256()?;
|
||||
let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?;
|
||||
let ssh_key = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::random(
|
||||
&mut ssh_key::rand_core::OsRng::default(),
|
||||
@@ -128,7 +128,7 @@ impl AccountInfo {
|
||||
cert_store
|
||||
.as_root_cert_mut()
|
||||
.ser(Pem::new_ref(&self.root_ca_cert))?;
|
||||
let int_key = crate::net::ssl::generate_key()?;
|
||||
let int_key = crate::net::ssl::gen_nistp256()?;
|
||||
let int_cert =
|
||||
crate::net::ssl::make_int_cert((&self.root_ca_key, &self.root_ca_cert), &int_key)?;
|
||||
cert_store.as_int_key_mut().ser(&Pem(int_key))?;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use std::fmt;
|
||||
|
||||
use clap::{CommandFactory, FromArgMatches, Parser};
|
||||
pub use models::ActionId;
|
||||
use models::{PackageId, ReplayId};
|
||||
use qrcode::QrCode;
|
||||
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
pub use crate::ActionId;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::model::package::TaskSeverity;
|
||||
use crate::prelude::*;
|
||||
@@ -16,6 +15,7 @@ use crate::rpc_continuations::Guid;
|
||||
use crate::util::serde::{
|
||||
HandlerExtSerde, StdinDeserializable, WithIoFormat, display_serializable,
|
||||
};
|
||||
use crate::{PackageId, ReplayId};
|
||||
|
||||
pub fn action_api<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
|
||||
@@ -14,8 +14,8 @@ use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::middleware::auth::{
|
||||
AsLogoutSessionId, AuthContext, HasLoggedOutSessions, HashSessionToken, LoginRes,
|
||||
use crate::middleware::auth::session::{
|
||||
AsLogoutSessionId, HasLoggedOutSessions, HashSessionToken, LoginRes, SessionAuthContext,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::util::crypto::EncryptedWire;
|
||||
@@ -110,7 +110,7 @@ impl std::str::FromStr for PasswordType {
|
||||
})
|
||||
}
|
||||
}
|
||||
pub fn auth<C: Context, AC: AuthContext>() -> ParentHandler<C>
|
||||
pub fn auth<C: Context, AC: SessionAuthContext>() -> ParentHandler<C>
|
||||
where
|
||||
CliContext: CallRemote<AC>,
|
||||
{
|
||||
@@ -173,7 +173,7 @@ fn gen_pwd() {
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn cli_login<C: AuthContext>(
|
||||
async fn cli_login<C: SessionAuthContext>(
|
||||
HandlerArgs {
|
||||
context: ctx,
|
||||
parent_method,
|
||||
@@ -227,7 +227,7 @@ pub struct LoginParams {
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn login_impl<C: AuthContext>(
|
||||
pub async fn login_impl<C: SessionAuthContext>(
|
||||
ctx: C,
|
||||
LoginParams {
|
||||
password,
|
||||
@@ -283,7 +283,7 @@ pub struct LogoutParams {
|
||||
session: InternedString,
|
||||
}
|
||||
|
||||
pub async fn logout<C: AuthContext>(
|
||||
pub async fn logout<C: SessionAuthContext>(
|
||||
ctx: C,
|
||||
LogoutParams { session }: LogoutParams,
|
||||
) -> Result<Option<HasLoggedOutSessions>, Error> {
|
||||
@@ -312,7 +312,7 @@ pub struct SessionList {
|
||||
sessions: Sessions,
|
||||
}
|
||||
|
||||
pub fn session<C: Context, AC: AuthContext>() -> ParentHandler<C>
|
||||
pub fn session<C: Context, AC: SessionAuthContext>() -> ParentHandler<C>
|
||||
where
|
||||
CliContext: CallRemote<AC>,
|
||||
{
|
||||
@@ -379,7 +379,7 @@ pub struct ListParams {
|
||||
|
||||
// #[command(display(display_sessions))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn list<C: AuthContext>(
|
||||
pub async fn list<C: SessionAuthContext>(
|
||||
ctx: C,
|
||||
ListParams { session, .. }: ListParams,
|
||||
) -> Result<SessionList, Error> {
|
||||
@@ -418,7 +418,10 @@ pub struct KillParams {
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn kill<C: AuthContext>(ctx: C, KillParams { ids }: KillParams) -> Result<(), Error> {
|
||||
pub async fn kill<C: SessionAuthContext>(
|
||||
ctx: C,
|
||||
KillParams { ids }: KillParams,
|
||||
) -> Result<(), Error> {
|
||||
HasLoggedOutSessions::new(ids.into_iter().map(KillSessionId::new), &ctx).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@ use std::sync::Arc;
|
||||
use chrono::Utc;
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::AtomicFile;
|
||||
use imbl::OrdSet;
|
||||
use models::PackageId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tracing::instrument;
|
||||
@@ -15,6 +13,7 @@ use ts_rs::TS;
|
||||
|
||||
use super::PackageBackupReport;
|
||||
use super::target::{BackupTargetId, PackageBackupInfo};
|
||||
use crate::PackageId;
|
||||
use crate::backup::os::OsBackup;
|
||||
use crate::backup::{BackupReport, ServerBackupReport};
|
||||
use crate::context::RpcContext;
|
||||
@@ -23,10 +22,10 @@ use crate::db::model::{Database, DatabaseModel};
|
||||
use crate::disk::mount::backup::BackupMountGuard;
|
||||
use crate::disk::mount::filesystem::ReadWrite;
|
||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||
use crate::middleware::auth::AuthContext;
|
||||
use crate::middleware::auth::session::SessionAuthContext;
|
||||
use crate::notifications::{NotificationLevel, notify};
|
||||
use crate::prelude::*;
|
||||
use crate::util::io::dir_copy;
|
||||
use crate::util::io::{AtomicFile, dir_copy};
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::version::VersionT;
|
||||
|
||||
@@ -312,19 +311,14 @@ async fn perform_backup(
|
||||
let ui = ctx.db.peek().await.into_public().into_ui().de()?;
|
||||
|
||||
let mut os_backup_file =
|
||||
AtomicFile::new(backup_guard.path().join("os-backup.json"), None::<PathBuf>)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
AtomicFile::new(backup_guard.path().join("os-backup.json"), None::<PathBuf>).await?;
|
||||
os_backup_file
|
||||
.write_all(&IoFormat::Json.to_vec(&OsBackup {
|
||||
account: ctx.account.peek(|a| a.clone()),
|
||||
ui,
|
||||
})?)
|
||||
.await?;
|
||||
os_backup_file
|
||||
.save()
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
os_backup_file.save().await?;
|
||||
|
||||
let luks_folder_old = backup_guard.path().join("luks.old");
|
||||
if tokio::fs::metadata(&luks_folder_old).await.is_ok() {
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use models::{HostId, PackageId};
|
||||
use reqwest::Url;
|
||||
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::PackageId;
|
||||
use crate::context::CliContext;
|
||||
#[allow(unused_imports)]
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::{Base32, Base64};
|
||||
|
||||
pub mod backup_bulk;
|
||||
pub mod os;
|
||||
@@ -58,13 +55,3 @@ pub fn package_backup<C: Context>() -> ParentHandler<C> {
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct BackupMetadata {
|
||||
pub timestamp: DateTime<Utc>,
|
||||
#[serde(default)]
|
||||
pub network_keys: BTreeMap<HostId, Base64<[u8; 32]>>,
|
||||
#[serde(default)]
|
||||
pub tor_keys: BTreeMap<HostId, Base32<[u8; 64]>>, // DEPRECATED
|
||||
pub registry: Option<Url>,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use futures::{StreamExt, stream};
|
||||
use models::PackageId;
|
||||
use patch_db::json_ptr::ROOT;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::Mutex;
|
||||
@@ -11,7 +10,6 @@ use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
use super::target::BackupTargetId;
|
||||
use crate::PLATFORM;
|
||||
use crate::backup::os::OsBackup;
|
||||
use crate::context::setup::SetupResult;
|
||||
use crate::context::{RpcContext, SetupContext};
|
||||
@@ -27,6 +25,7 @@ use crate::service::service_map::DownloadInstallFuture;
|
||||
use crate::setup::SetupExecuteProgress;
|
||||
use crate::system::sync_kiosk;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::{PLATFORM, PackageId};
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
@@ -9,7 +9,6 @@ use digest::OutputSizeUser;
|
||||
use digest::generic_array::GenericArray;
|
||||
use exver::Version;
|
||||
use imbl_value::InternedString;
|
||||
use models::{FromStrParser, PackageId};
|
||||
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
@@ -18,6 +17,7 @@ use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
use self::cifs::CifsBackupTarget;
|
||||
use crate::PackageId;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::model::DatabaseModel;
|
||||
use crate::disk::mount::backup::BackupMountGuard;
|
||||
@@ -27,10 +27,10 @@ use crate::disk::mount::filesystem::{FileSystem, MountType, ReadWrite};
|
||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||
use crate::disk::util::PartitionInfo;
|
||||
use crate::prelude::*;
|
||||
use crate::util::VersionString;
|
||||
use crate::util::serde::{
|
||||
HandlerExtSerde, WithIoFormat, deserialize_from_str, display_serializable, serialize_display,
|
||||
};
|
||||
use crate::util::{FromStrParser, VersionString};
|
||||
|
||||
pub mod cifs;
|
||||
|
||||
@@ -301,14 +301,14 @@ lazy_static::lazy_static! {
|
||||
Mutex::new(BTreeMap::new());
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct MountParams {
|
||||
target_id: BackupTargetId,
|
||||
#[arg(long)]
|
||||
server_id: Option<String>,
|
||||
password: String,
|
||||
password: String, // TODO: rpassword
|
||||
#[arg(long)]
|
||||
allow_partial: bool,
|
||||
}
|
||||
|
||||
@@ -1,91 +1,85 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::ffi::OsString;
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(feature = "cli-container")]
|
||||
pub mod container_cli;
|
||||
pub mod deprecated;
|
||||
#[cfg(any(feature = "registry", feature = "cli-registry"))]
|
||||
pub mod registry;
|
||||
#[cfg(feature = "cli")]
|
||||
pub mod start_cli;
|
||||
#[cfg(feature = "startd")]
|
||||
pub mod start_init;
|
||||
#[cfg(feature = "startd")]
|
||||
pub mod startd;
|
||||
#[cfg(any(feature = "tunnel", feature = "cli-tunnel"))]
|
||||
pub mod tunnel;
|
||||
|
||||
fn select_executable(name: &str) -> Option<fn(VecDeque<OsString>)> {
|
||||
match name {
|
||||
#[cfg(feature = "startd")]
|
||||
"startd" => Some(startd::main),
|
||||
#[cfg(feature = "startd")]
|
||||
"embassyd" => Some(|_| deprecated::renamed("embassyd", "startd")),
|
||||
#[cfg(feature = "startd")]
|
||||
"embassy-init" => Some(|_| deprecated::removed("embassy-init")),
|
||||
|
||||
#[cfg(feature = "cli-startd")]
|
||||
"start-cli" => Some(start_cli::main),
|
||||
#[cfg(feature = "cli-startd")]
|
||||
"embassy-cli" => Some(|_| deprecated::renamed("embassy-cli", "start-cli")),
|
||||
#[cfg(feature = "cli-startd")]
|
||||
"embassy-sdk" => Some(|_| deprecated::removed("embassy-sdk")),
|
||||
|
||||
#[cfg(feature = "cli-container")]
|
||||
"start-container" => Some(container_cli::main),
|
||||
|
||||
#[cfg(feature = "registry")]
|
||||
"start-registryd" => Some(registry::main),
|
||||
#[cfg(feature = "cli-registry")]
|
||||
"start-registry" => Some(registry::cli),
|
||||
|
||||
#[cfg(feature = "tunnel")]
|
||||
"start-tunneld" => Some(tunnel::main),
|
||||
#[cfg(feature = "cli-tunnel")]
|
||||
"start-tunnel" => Some(tunnel::cli),
|
||||
|
||||
"contents" => Some(|_| {
|
||||
#[cfg(feature = "startd")]
|
||||
println!("startd");
|
||||
#[cfg(feature = "cli-startd")]
|
||||
println!("start-cli");
|
||||
#[cfg(feature = "cli-container")]
|
||||
println!("start-container");
|
||||
#[cfg(feature = "registry")]
|
||||
println!("start-registryd");
|
||||
#[cfg(feature = "cli-registry")]
|
||||
println!("start-registry");
|
||||
#[cfg(feature = "tunnel")]
|
||||
println!("start-tunneld");
|
||||
#[cfg(feature = "cli-tunnel")]
|
||||
println!("start-tunnel");
|
||||
}),
|
||||
_ => None,
|
||||
#[derive(Default)]
|
||||
pub struct MultiExecutable(BTreeMap<&'static str, fn(VecDeque<OsString>)>);
|
||||
impl MultiExecutable {
|
||||
pub fn enable_startd(&mut self) -> &mut Self {
|
||||
self.0.insert("startd", startd::main);
|
||||
self.0
|
||||
.insert("embassyd", |_| deprecated::renamed("embassyd", "startd"));
|
||||
self.0
|
||||
.insert("embassy-init", |_| deprecated::removed("embassy-init"));
|
||||
self
|
||||
}
|
||||
pub fn enable_start_cli(&mut self) -> &mut Self {
|
||||
self.0.insert("start-cli", start_cli::main);
|
||||
self.0.insert("embassy-cli", |_| {
|
||||
deprecated::renamed("embassy-cli", "start-cli")
|
||||
});
|
||||
self.0
|
||||
.insert("embassy-sdk", |_| deprecated::removed("embassy-sdk"));
|
||||
self
|
||||
}
|
||||
pub fn enable_start_container(&mut self) -> &mut Self {
|
||||
self.0.insert("start-container", container_cli::main);
|
||||
self
|
||||
}
|
||||
pub fn enable_start_registryd(&mut self) -> &mut Self {
|
||||
self.0.insert("start-registryd", registry::main);
|
||||
self
|
||||
}
|
||||
pub fn enable_start_registry(&mut self) -> &mut Self {
|
||||
self.0.insert("start-registry", registry::cli);
|
||||
self
|
||||
}
|
||||
pub fn enable_start_tunneld(&mut self) -> &mut Self {
|
||||
self.0.insert("start-tunneld", tunnel::main);
|
||||
self
|
||||
}
|
||||
pub fn enable_start_tunnel(&mut self) -> &mut Self {
|
||||
self.0.insert("start-tunnel", tunnel::cli);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn startbox() {
|
||||
let mut args = std::env::args_os().collect::<VecDeque<_>>();
|
||||
for _ in 0..2 {
|
||||
if let Some(s) = args.pop_front() {
|
||||
if let Some(x) = Path::new(&*s)
|
||||
.file_name()
|
||||
.and_then(|s| s.to_str())
|
||||
.and_then(|s| select_executable(&s))
|
||||
{
|
||||
args.push_front(s);
|
||||
return x(args);
|
||||
fn select_executable(&self, name: &str) -> Option<fn(VecDeque<OsString>)> {
|
||||
self.0.get(&name).copied()
|
||||
}
|
||||
|
||||
pub fn execute(&self) {
|
||||
let mut args = std::env::args_os().collect::<VecDeque<_>>();
|
||||
for _ in 0..2 {
|
||||
if let Some(s) = args.pop_front() {
|
||||
if let Some(name) = Path::new(&*s).file_name().and_then(|s| s.to_str()) {
|
||||
if name == "--contents" {
|
||||
for name in self.0.keys() {
|
||||
println!("{name}");
|
||||
}
|
||||
}
|
||||
if let Some(x) = self.select_executable(&name) {
|
||||
args.push_front(s);
|
||||
return x(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let args = std::env::args().collect::<VecDeque<_>>();
|
||||
eprintln!(
|
||||
"unknown executable: {}",
|
||||
args.get(1)
|
||||
.or_else(|| args.get(0))
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("N/A")
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
let args = std::env::args().collect::<VecDeque<_>>();
|
||||
eprintln!(
|
||||
"unknown executable: {}",
|
||||
args.get(1)
|
||||
.or_else(|| args.get(0))
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("N/A")
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use futures::FutureExt;
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
use rpc_toolkit::CliApp;
|
||||
use tokio::signal::unix::signal;
|
||||
use tracing::instrument;
|
||||
@@ -20,6 +19,7 @@ use crate::prelude::*;
|
||||
use crate::tunnel::context::{TunnelConfig, TunnelContext};
|
||||
use crate::tunnel::tunnel_router;
|
||||
use crate::tunnel::web::TunnelCertHandler;
|
||||
use crate::util::future::NonDetachingJoinHandle;
|
||||
use crate::util::logger::LOGGER;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use helpers::Callback;
|
||||
use itertools::Itertools;
|
||||
use jsonpath_lib::Compiled;
|
||||
use models::PackageId;
|
||||
use crate::PackageId;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
|
||||
@@ -24,7 +24,7 @@ use super::setup::CURRENT_SECRET;
|
||||
use crate::context::config::{ClientConfig, local_config_path};
|
||||
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
||||
use crate::developer::{OS_DEVELOPER_KEY_PATH, default_developer_key_path};
|
||||
use crate::middleware::auth::AuthContext;
|
||||
use crate::middleware::auth::local::LocalAuthContext;
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::util::io::read_file_to_string;
|
||||
@@ -35,7 +35,7 @@ pub struct CliContextSeed {
|
||||
pub base_url: Url,
|
||||
pub rpc_url: Url,
|
||||
pub registry_url: Option<Url>,
|
||||
pub registry_hostname: Option<InternedString>,
|
||||
pub registry_hostname: Vec<InternedString>,
|
||||
pub registry_listen: Option<SocketAddr>,
|
||||
pub tunnel_addr: Option<SocketAddr>,
|
||||
pub tunnel_listen: Option<SocketAddr>,
|
||||
@@ -126,7 +126,7 @@ impl CliContext {
|
||||
Ok::<_, Error>(registry)
|
||||
})
|
||||
.transpose()?,
|
||||
registry_hostname: config.registry_hostname,
|
||||
registry_hostname: config.registry_hostname.unwrap_or_default(),
|
||||
registry_listen: config.registry_listen,
|
||||
tunnel_addr: config.tunnel,
|
||||
tunnel_listen: config.tunnel_listen,
|
||||
@@ -307,7 +307,7 @@ impl CallRemote<RpcContext> for CliContext {
|
||||
)
|
||||
.with_kind(crate::ErrorKind::Network)?;
|
||||
}
|
||||
crate::middleware::signature::call_remote(
|
||||
crate::middleware::auth::signature::call_remote(
|
||||
self,
|
||||
self.rpc_url.clone(),
|
||||
HeaderMap::new(),
|
||||
@@ -320,7 +320,7 @@ impl CallRemote<RpcContext> for CliContext {
|
||||
}
|
||||
impl CallRemote<DiagnosticContext> for CliContext {
|
||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||
crate::middleware::signature::call_remote(
|
||||
crate::middleware::auth::signature::call_remote(
|
||||
self,
|
||||
self.rpc_url.clone(),
|
||||
HeaderMap::new(),
|
||||
@@ -333,7 +333,7 @@ impl CallRemote<DiagnosticContext> for CliContext {
|
||||
}
|
||||
impl CallRemote<InitContext> for CliContext {
|
||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||
crate::middleware::signature::call_remote(
|
||||
crate::middleware::auth::signature::call_remote(
|
||||
self,
|
||||
self.rpc_url.clone(),
|
||||
HeaderMap::new(),
|
||||
@@ -346,7 +346,7 @@ impl CallRemote<InitContext> for CliContext {
|
||||
}
|
||||
impl CallRemote<SetupContext> for CliContext {
|
||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||
crate::middleware::signature::call_remote(
|
||||
crate::middleware::auth::signature::call_remote(
|
||||
self,
|
||||
self.rpc_url.clone(),
|
||||
HeaderMap::new(),
|
||||
@@ -359,7 +359,7 @@ impl CallRemote<SetupContext> for CliContext {
|
||||
}
|
||||
impl CallRemote<InstallContext> for CliContext {
|
||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||
crate::middleware::signature::call_remote(
|
||||
crate::middleware::auth::signature::call_remote(
|
||||
self,
|
||||
self.rpc_url.clone(),
|
||||
HeaderMap::new(),
|
||||
|
||||
@@ -65,7 +65,7 @@ pub struct ClientConfig {
|
||||
#[arg(short = 'r', long)]
|
||||
pub registry: Option<Url>,
|
||||
#[arg(long)]
|
||||
pub registry_hostname: Option<InternedString>,
|
||||
pub registry_hostname: Option<Vec<InternedString>>,
|
||||
#[arg(skip)]
|
||||
pub registry_listen: Option<SocketAddr>,
|
||||
#[arg(short = 't', long)]
|
||||
|
||||
@@ -8,12 +8,10 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::{TimeDelta, Utc};
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
use imbl::OrdMap;
|
||||
use imbl_value::InternedString;
|
||||
use itertools::Itertools;
|
||||
use josekit::jwk::Jwk;
|
||||
use models::{ActionId, PackageId};
|
||||
use reqwest::{Client, Proxy};
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use rpc_toolkit::{CallRemote, Context, Empty};
|
||||
@@ -22,7 +20,6 @@ use tokio::time::Instant;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::setup::CURRENT_SECRET;
|
||||
use crate::DATA_DIR;
|
||||
use crate::account::AccountInfo;
|
||||
use crate::auth::Sessions;
|
||||
use crate::context::config::ServerConfig;
|
||||
@@ -45,9 +42,11 @@ use crate::service::ServiceMap;
|
||||
use crate::service::action::update_tasks;
|
||||
use crate::service::effects::callbacks::ServiceCallbacks;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::util::future::NonDetachingJoinHandle;
|
||||
use crate::util::io::delete_file;
|
||||
use crate::util::lshw::LshwDevice;
|
||||
use crate::util::sync::{SyncMutex, SyncRwLock, Watch};
|
||||
use crate::{ActionId, DATA_DIR, PackageId};
|
||||
|
||||
pub struct RpcContextSeed {
|
||||
is_closed: AtomicBool,
|
||||
@@ -389,12 +388,19 @@ impl RpcContext {
|
||||
.as_entries()?
|
||||
.into_iter()
|
||||
.map(|(_, pde)| {
|
||||
Ok(pde.as_tasks().as_entries()?.into_iter().map(|(_, r)| {
|
||||
Ok::<_, Error>((
|
||||
r.as_task().as_package_id().de()?,
|
||||
r.as_task().as_action_id().de()?,
|
||||
))
|
||||
}))
|
||||
Ok(pde
|
||||
.as_tasks()
|
||||
.as_entries()?
|
||||
.into_iter()
|
||||
.map(|(_, r)| {
|
||||
let t = r.as_task();
|
||||
Ok::<_, Error>(if t.as_input().transpose_ref().is_some() {
|
||||
Some((t.as_package_id().de()?, t.as_action_id().de()?))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
})
|
||||
.filter_map_ok(|a| a))
|
||||
})
|
||||
.flatten_ok()
|
||||
.map(|a| a.and_then(|a| a))
|
||||
@@ -416,46 +422,32 @@ impl RpcContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
for id in
|
||||
self.db
|
||||
.mutate::<Vec<PackageId>>(|db| {
|
||||
for (package_id, action_input) in &action_input {
|
||||
for (action_id, input) in action_input {
|
||||
for (_, pde) in
|
||||
db.as_public_mut().as_package_data_mut().as_entries_mut()?
|
||||
{
|
||||
pde.as_tasks_mut().mutate(|tasks| {
|
||||
Ok(update_tasks(tasks, package_id, action_id, input, false))
|
||||
})?;
|
||||
}
|
||||
|
||||
self.db
|
||||
.mutate(|db| {
|
||||
for (package_id, action_input) in &action_input {
|
||||
for (action_id, input) in action_input {
|
||||
for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? {
|
||||
pde.as_tasks_mut().mutate(|tasks| {
|
||||
Ok(update_tasks(tasks, package_id, action_id, input, false))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
db.as_public()
|
||||
.as_package_data()
|
||||
.as_entries()?
|
||||
}
|
||||
for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? {
|
||||
if pde
|
||||
.as_tasks()
|
||||
.de()?
|
||||
.into_iter()
|
||||
.filter_map(|(id, pkg)| {
|
||||
(|| {
|
||||
if pkg.as_tasks().de()?.into_iter().any(|(_, t)| {
|
||||
t.active && t.task.severity == TaskSeverity::Critical
|
||||
}) {
|
||||
Ok(Some(id))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})()
|
||||
.transpose()
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.await
|
||||
.result?
|
||||
{
|
||||
let svc = self.services.get(&id).await;
|
||||
if let Some(svc) = &*svc {
|
||||
svc.stop(procedure_id.clone(), false).await?;
|
||||
}
|
||||
}
|
||||
.any(|(_, t)| t.active && t.task.severity == TaskSeverity::Critical)
|
||||
{
|
||||
pde.as_status_info_mut().stop()?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
check_tasks.complete();
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::{Future, StreamExt};
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
use imbl_value::InternedString;
|
||||
use josekit::jwk::Jwk;
|
||||
use patch_db::PatchDb;
|
||||
@@ -28,7 +27,7 @@ use crate::progress::FullProgressTracker;
|
||||
use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations};
|
||||
use crate::setup::SetupProgress;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::util::net::WebSocketExt;
|
||||
use crate::util::future::NonDetachingJoinHandle;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref CURRENT_SECRET: Jwk = Jwk::generate_ec_key(josekit::jwk::alg::ec::EcCurve::P256).unwrap_or_else(|e| {
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use models::PackageId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::Error;
|
||||
use crate::context::RpcContext;
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::{Error, PackageId};
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -19,37 +16,51 @@ pub struct ControlParams {
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn start(ctx: RpcContext, ControlParams { id }: ControlParams) -> Result<(), Error> {
|
||||
ctx.services
|
||||
.get(&id)
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(&id)
|
||||
.or_not_found(&id)?
|
||||
.as_status_info_mut()
|
||||
.as_desired_mut()
|
||||
.map_mutate(|s| Ok(s.start()))
|
||||
})
|
||||
.await
|
||||
.as_ref()
|
||||
.or_not_found(lazy_format!("Manager for {id}"))?
|
||||
.start(Guid::new())
|
||||
.await?;
|
||||
.result?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn stop(ctx: RpcContext, ControlParams { id }: ControlParams) -> Result<(), Error> {
|
||||
ctx.services
|
||||
.get(&id)
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(&id)
|
||||
.or_not_found(&id)?
|
||||
.as_status_info_mut()
|
||||
.stop()
|
||||
})
|
||||
.await
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest))?
|
||||
.stop(Guid::new(), true)
|
||||
.await?;
|
||||
.result?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn restart(ctx: RpcContext, ControlParams { id }: ControlParams) -> Result<(), Error> {
|
||||
ctx.services
|
||||
.get(&id)
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(&id)
|
||||
.or_not_found(&id)?
|
||||
.as_status_info_mut()
|
||||
.as_desired_mut()
|
||||
.map_mutate(|s| Ok(s.restart()))
|
||||
})
|
||||
.await
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest))?
|
||||
.restart(Guid::new(), false)
|
||||
.await?;
|
||||
.result?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pub mod model;
|
||||
pub mod prelude;
|
||||
|
||||
use std::panic::UnwindSafe;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -23,7 +22,6 @@ use ts_rs::TS;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||
use crate::util::net::WebSocketExt;
|
||||
use crate::util::serde::{HandlerExtSerde, apply_expr};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::path::PathBuf;
|
||||
use chrono::{DateTime, Utc};
|
||||
use exver::VersionRange;
|
||||
use imbl_value::InternedString;
|
||||
use models::{ActionId, DataUrl, HealthCheckId, HostId, PackageId, ReplayId, ServiceInterfaceId};
|
||||
use patch_db::HasModel;
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use reqwest::Url;
|
||||
@@ -16,8 +15,10 @@ use crate::net::service_interface::ServiceInterface;
|
||||
use crate::prelude::*;
|
||||
use crate::progress::FullProgress;
|
||||
use crate::s9pk::manifest::Manifest;
|
||||
use crate::status::MainStatus;
|
||||
use crate::status::StatusInfo;
|
||||
use crate::util::DataUrl;
|
||||
use crate::util::serde::{Pem, is_partial_of};
|
||||
use crate::{ActionId, HealthCheckId, HostId, PackageId, ReplayId, ServiceInterfaceId};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
@@ -365,7 +366,7 @@ impl Default for ActionVisibility {
|
||||
pub struct PackageDataEntry {
|
||||
pub state_info: PackageState,
|
||||
pub s9pk: PathBuf,
|
||||
pub status: MainStatus,
|
||||
pub status_info: StatusInfo,
|
||||
#[ts(type = "string | null")]
|
||||
pub registry: Option<Url>,
|
||||
#[ts(type = "string")]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
use models::PackageId;
|
||||
use patch_db::{HasModel, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::PackageId;
|
||||
use crate::auth::Sessions;
|
||||
use crate::backup::target::cifs::CifsTargets;
|
||||
use crate::net::forward::AvailablePorts;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use exver::{Version, VersionRange};
|
||||
@@ -9,7 +9,6 @@ use imbl_value::InternedString;
|
||||
use ipnet::IpNet;
|
||||
use isocountry::CountryCode;
|
||||
use itertools::Itertools;
|
||||
use models::{GatewayId, PackageId};
|
||||
use openssl::hash::MessageDigest;
|
||||
use patch_db::{HasModel, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -31,7 +30,9 @@ use crate::util::cpupower::Governor;
|
||||
use crate::util::lshw::LshwDevice;
|
||||
use crate::util::serde::MaybeUtf8String;
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::{ARCH, PLATFORM};
|
||||
use crate::{ARCH, GatewayId, PLATFORM, PackageId};
|
||||
|
||||
pub static DB_UI_SEED_CELL: OnceLock<&'static str> = OnceLock::new();
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -65,9 +66,10 @@ impl Public {
|
||||
preferred_external_port: 80,
|
||||
add_ssl: Some(AddSslOptions {
|
||||
preferred_external_port: 443,
|
||||
add_x_forwarded_headers: false,
|
||||
alpn: Some(AlpnInfo::Specified(vec![
|
||||
MaybeUtf8String("http/1.1".into()),
|
||||
MaybeUtf8String("h2".into()),
|
||||
MaybeUtf8String("http/1.1".into()),
|
||||
])),
|
||||
}),
|
||||
secure: None,
|
||||
@@ -123,20 +125,8 @@ impl Public {
|
||||
kiosk,
|
||||
},
|
||||
package_data: AllPackageData::default(),
|
||||
ui: {
|
||||
#[cfg(feature = "startd")]
|
||||
{
|
||||
serde_json::from_str(include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../../web/patchdb-ui-seed.json"
|
||||
)))
|
||||
.with_kind(ErrorKind::Deserialization)?
|
||||
}
|
||||
#[cfg(not(feature = "startd"))]
|
||||
{
|
||||
Value::Null
|
||||
}
|
||||
},
|
||||
ui: serde_json::from_str(*DB_UI_SEED_CELL.get().unwrap_or(&"null"))
|
||||
.with_kind(ErrorKind::Deserialization)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -260,11 +250,7 @@ impl NetworkInterfaceInfo {
|
||||
}
|
||||
|
||||
pub fn secure(&self) -> bool {
|
||||
self.secure.unwrap_or_else(|| {
|
||||
self.ip_info.as_ref().map_or(false, |ip_info| {
|
||||
ip_info.device_type == Some(NetworkInterfaceType::Wireguard)
|
||||
}) && !self.public()
|
||||
})
|
||||
self.secure.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@ use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
use imbl_value::InternedString;
|
||||
use models::PackageId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::Error;
|
||||
use crate::prelude::*;
|
||||
use crate::util::PathOrUrl;
|
||||
use crate::{Error, PackageId};
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||
#[model = "Model<Self>"]
|
||||
|
||||
@@ -287,6 +287,7 @@ pub async fn mount_fs<P: AsRef<Path>>(
|
||||
Command::new("cryptsetup")
|
||||
.arg("-q")
|
||||
.arg("luksOpen")
|
||||
.arg("--allow-discards")
|
||||
.arg(format!("--key-file={}", PASSWORD_PATH))
|
||||
.arg(format!("--keyfile-size={}", password.len()))
|
||||
.arg(&blockdev_path)
|
||||
|
||||
@@ -2,21 +2,21 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::AtomicFile;
|
||||
use models::PackageId;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::guard::{GenericMountGuard, TmpMountGuard};
|
||||
use crate::PackageId;
|
||||
use crate::auth::check_password;
|
||||
use crate::backup::target::BackupInfo;
|
||||
use crate::disk::mount::filesystem::ReadWrite;
|
||||
use crate::disk::mount::filesystem::backupfs::BackupFS;
|
||||
use crate::disk::mount::guard::SubPath;
|
||||
use crate::disk::util::StartOsRecoveryInfo;
|
||||
use crate::prelude::*;
|
||||
use crate::util::crypto::{decrypt_slice, encrypt_slice};
|
||||
use crate::util::io::AtomicFile;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BackupMountGuard<G: GenericMountGuard> {
|
||||
@@ -184,18 +184,14 @@ impl<G: GenericMountGuard> BackupMountGuard<G> {
|
||||
#[instrument(skip_all)]
|
||||
pub async fn save(&self) -> Result<(), Error> {
|
||||
let metadata_path = self.path().join("metadata.json");
|
||||
let mut file = AtomicFile::new(&metadata_path, None::<PathBuf>)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
let mut file = AtomicFile::new(&metadata_path, None::<PathBuf>).await?;
|
||||
file.write_all(&IoFormat::Json.to_vec(&self.metadata)?)
|
||||
.await?;
|
||||
file.save().await.with_kind(ErrorKind::Filesystem)?;
|
||||
let mut file = AtomicFile::new(&self.unencrypted_metadata_path, None::<PathBuf>)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
file.save().await?;
|
||||
let mut file = AtomicFile::new(&self.unencrypted_metadata_path, None::<PathBuf>).await?;
|
||||
file.write_all(&IoFormat::Json.to_vec(&self.unencrypted_metadata)?)
|
||||
.await?;
|
||||
file.save().await.with_kind(ErrorKind::Filesystem)?;
|
||||
file.save().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ pub enum FileType {
|
||||
Directory,
|
||||
Infer,
|
||||
}
|
||||
impl Default for FileType {
|
||||
fn default() -> Self {
|
||||
FileType::Directory
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Bind<Src: AsRef<Path>> {
|
||||
src: Src,
|
||||
|
||||
@@ -2,34 +2,85 @@ use std::ffi::OsStr;
|
||||
use std::fmt::Display;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
use clap::builder::ValueParserFactory;
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
use tokio::process::Command;
|
||||
use ts_rs::TS;
|
||||
|
||||
use super::{FileSystem, MountType};
|
||||
use crate::disk::mount::filesystem::default_mount_command;
|
||||
use super::FileSystem;
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
use crate::util::{FromStrParser, Invoke};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IdMap {
|
||||
pub from_id: u32,
|
||||
pub to_id: u32,
|
||||
pub range: u32,
|
||||
}
|
||||
impl IdMap {
|
||||
pub fn stack(a: Vec<IdMap>, b: Vec<IdMap>) -> Vec<IdMap> {
|
||||
let mut res = Vec::with_capacity(a.len() + b.len());
|
||||
res.extend_from_slice(&a);
|
||||
|
||||
for mut b in b {
|
||||
for a in &a {
|
||||
if a.from_id <= b.to_id && a.from_id + a.range > b.to_id {
|
||||
b.to_id += a.to_id;
|
||||
}
|
||||
}
|
||||
res.push(b);
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
impl FromStr for IdMap {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let split = s.splitn(3, ":").collect::<Vec<_>>();
|
||||
if let Some([u, k, r]) = split.get(0..3) {
|
||||
Ok(Self {
|
||||
from_id: u.parse()?,
|
||||
to_id: k.parse()?,
|
||||
range: r.parse()?,
|
||||
})
|
||||
} else if let Some([u, k]) = split.get(0..2) {
|
||||
Ok(Self {
|
||||
from_id: u.parse()?,
|
||||
to_id: k.parse()?,
|
||||
range: 1,
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("{s} is not a valid idmap"),
|
||||
ErrorKind::ParseNumber,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ValueParserFactory for IdMap {
|
||||
type Parser = FromStrParser<IdMap>;
|
||||
fn value_parser() -> Self::Parser {
|
||||
<Self::Parser>::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IdMapped<Fs: FileSystem> {
|
||||
filesystem: Fs,
|
||||
from_id: u32,
|
||||
to_id: u32,
|
||||
range: u32,
|
||||
idmap: Vec<IdMap>,
|
||||
}
|
||||
impl<Fs: FileSystem> IdMapped<Fs> {
|
||||
pub fn new(filesystem: Fs, from_id: u32, to_id: u32, range: u32) -> Self {
|
||||
Self {
|
||||
filesystem,
|
||||
from_id,
|
||||
to_id,
|
||||
range,
|
||||
}
|
||||
pub fn new(filesystem: Fs, idmap: Vec<IdMap>) -> Self {
|
||||
Self { filesystem, idmap }
|
||||
}
|
||||
}
|
||||
impl<Fs: FileSystem> FileSystem for IdMapped<Fs> {
|
||||
@@ -44,12 +95,17 @@ impl<Fs: FileSystem> FileSystem for IdMapped<Fs> {
|
||||
.mount_options()
|
||||
.into_iter()
|
||||
.map(|a| Box::new(a) as Box<dyn Display>)
|
||||
.chain(std::iter::once(Box::new(lazy_format!(
|
||||
"X-mount.idmap=b:{}:{}:{}",
|
||||
self.from_id,
|
||||
self.to_id,
|
||||
self.range,
|
||||
)) as Box<dyn Display>))
|
||||
.chain(if self.idmap.is_empty() {
|
||||
None
|
||||
} else {
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut option = "X-mount.idmap=".to_owned();
|
||||
for i in &self.idmap {
|
||||
write!(&mut option, "b:{}:{}:{} ", i.from_id, i.to_id, i.range).unwrap();
|
||||
}
|
||||
Some(Box::new(option) as Box<dyn Display>)
|
||||
})
|
||||
}
|
||||
async fn source(&self) -> Result<Option<impl AsRef<Path>>, Error> {
|
||||
self.filesystem.source().await
|
||||
@@ -57,26 +113,28 @@ impl<Fs: FileSystem> FileSystem for IdMapped<Fs> {
|
||||
async fn pre_mount(&self, mountpoint: &Path) -> Result<(), Error> {
|
||||
self.filesystem.pre_mount(mountpoint).await?;
|
||||
let info = tokio::fs::metadata(mountpoint).await?;
|
||||
let uid_in_range = self.from_id <= info.uid() && self.from_id + self.range > info.uid();
|
||||
let gid_in_range = self.from_id <= info.gid() && self.from_id + self.range > info.gid();
|
||||
if uid_in_range || gid_in_range {
|
||||
Command::new("chown")
|
||||
.arg(format!(
|
||||
"{uid}:{gid}",
|
||||
uid = if uid_in_range {
|
||||
self.to_id + info.uid() - self.from_id
|
||||
} else {
|
||||
info.uid()
|
||||
},
|
||||
gid = if gid_in_range {
|
||||
self.to_id + info.gid() - self.from_id
|
||||
} else {
|
||||
info.gid()
|
||||
},
|
||||
))
|
||||
.arg(&mountpoint)
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
for i in &self.idmap {
|
||||
let uid_in_range = i.from_id <= info.uid() && i.from_id + i.range > info.uid();
|
||||
let gid_in_range = i.from_id <= info.gid() && i.from_id + i.range > info.gid();
|
||||
if uid_in_range || gid_in_range {
|
||||
Command::new("chown")
|
||||
.arg(format!(
|
||||
"{uid}:{gid}",
|
||||
uid = if uid_in_range {
|
||||
i.to_id + info.uid() - i.from_id
|
||||
} else {
|
||||
info.uid()
|
||||
},
|
||||
gid = if gid_in_range {
|
||||
i.to_id + info.gid() - i.from_id
|
||||
} else {
|
||||
info.gid()
|
||||
},
|
||||
))
|
||||
.arg(&mountpoint)
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -86,9 +144,12 @@ impl<Fs: FileSystem> FileSystem for IdMapped<Fs> {
|
||||
let mut sha = Sha256::new();
|
||||
sha.update("IdMapped");
|
||||
sha.update(self.filesystem.source_hash().await?);
|
||||
sha.update(u32::to_be_bytes(self.from_id));
|
||||
sha.update(u32::to_be_bytes(self.to_id));
|
||||
sha.update(u32::to_be_bytes(self.range));
|
||||
sha.update(usize::to_be_bytes(self.idmap.len()));
|
||||
for i in &self.idmap {
|
||||
sha.update(u32::to_be_bytes(i.from_id));
|
||||
sha.update(u32::to_be_bytes(i.to_id));
|
||||
sha.update(u32::to_be_bytes(i.range));
|
||||
}
|
||||
Ok(sha.finalize())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,13 @@ use std::sync::{Arc, Weak};
|
||||
|
||||
use futures::Future;
|
||||
use lazy_static::lazy_static;
|
||||
use models::ResultExt;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::filesystem::{FileSystem, MountType, ReadOnly, ReadWrite};
|
||||
use super::util::unmount;
|
||||
use crate::Error;
|
||||
use crate::util::{Invoke, Never};
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
pub const TMP_MOUNTPOINT: &'static str = "/media/startos/tmp";
|
||||
|
||||
|
||||
@@ -1,4 +1,527 @@
|
||||
pub use models::{Error, ErrorKind, OptionExt, ResultExt};
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
use axum::http::StatusCode;
|
||||
use axum::http::uri::InvalidUri;
|
||||
use color_eyre::eyre::eyre;
|
||||
use num_enum::TryFromPrimitive;
|
||||
use patch_db::Revision;
|
||||
use rpc_toolkit::reqwest;
|
||||
use rpc_toolkit::yajrc::{
|
||||
INVALID_PARAMS_ERROR, INVALID_REQUEST_ERROR, METHOD_NOT_FOUND_ERROR, PARSE_ERROR, RpcError,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_rustls::rustls;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::InvalidId;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
|
||||
#[repr(i32)]
|
||||
pub enum ErrorKind {
|
||||
Unknown = 1,
|
||||
Filesystem = 2,
|
||||
Docker = 3,
|
||||
ConfigSpecViolation = 4,
|
||||
ConfigRulesViolation = 5,
|
||||
NotFound = 6,
|
||||
IncorrectPassword = 7,
|
||||
VersionIncompatible = 8,
|
||||
Network = 9,
|
||||
Registry = 10,
|
||||
Serialization = 11,
|
||||
Deserialization = 12,
|
||||
Utf8 = 13,
|
||||
ParseVersion = 14,
|
||||
IncorrectDisk = 15,
|
||||
// Nginx = 16,
|
||||
Dependency = 17,
|
||||
ParseS9pk = 18,
|
||||
ParseUrl = 19,
|
||||
DiskNotAvailable = 20,
|
||||
BlockDevice = 21,
|
||||
InvalidOnionAddress = 22,
|
||||
Pack = 23,
|
||||
ValidateS9pk = 24,
|
||||
DiskCorrupted = 25, // Remove
|
||||
Tor = 26,
|
||||
ConfigGen = 27,
|
||||
ParseNumber = 28,
|
||||
Database = 29,
|
||||
InvalidId = 30,
|
||||
InvalidSignature = 31,
|
||||
Backup = 32,
|
||||
Restore = 33,
|
||||
Authorization = 34,
|
||||
AutoConfigure = 35,
|
||||
Action = 36,
|
||||
RateLimited = 37,
|
||||
InvalidRequest = 38,
|
||||
MigrationFailed = 39,
|
||||
Uninitialized = 40,
|
||||
ParseNetAddress = 41,
|
||||
ParseSshKey = 42,
|
||||
SoundError = 43,
|
||||
ParseTimestamp = 44,
|
||||
ParseSysInfo = 45,
|
||||
Wifi = 46,
|
||||
Journald = 47,
|
||||
DiskManagement = 48,
|
||||
OpenSsl = 49,
|
||||
PasswordHashGeneration = 50,
|
||||
DiagnosticMode = 51,
|
||||
ParseDbField = 52,
|
||||
Duplicate = 53,
|
||||
MultipleErrors = 54,
|
||||
Incoherent = 55,
|
||||
InvalidBackupTargetId = 56,
|
||||
ProductKeyMismatch = 57,
|
||||
LanPortConflict = 58,
|
||||
Javascript = 59,
|
||||
Pem = 60,
|
||||
TLSInit = 61,
|
||||
Ascii = 62,
|
||||
MissingHeader = 63,
|
||||
Grub = 64,
|
||||
Systemd = 65,
|
||||
OpenSsh = 66,
|
||||
Zram = 67,
|
||||
Lshw = 68,
|
||||
CpuSettings = 69,
|
||||
Firmware = 70,
|
||||
Timeout = 71,
|
||||
Lxc = 72,
|
||||
Cancelled = 73,
|
||||
Git = 74,
|
||||
DBus = 75,
|
||||
InstallFailed = 76,
|
||||
UpdateFailed = 77,
|
||||
Smtp = 78,
|
||||
}
|
||||
impl ErrorKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
use ErrorKind::*;
|
||||
match self {
|
||||
Unknown => "Unknown Error",
|
||||
Filesystem => "Filesystem I/O Error",
|
||||
Docker => "Docker Error",
|
||||
ConfigSpecViolation => "Config Spec Violation",
|
||||
ConfigRulesViolation => "Config Rules Violation",
|
||||
NotFound => "Not Found",
|
||||
IncorrectPassword => "Incorrect Password",
|
||||
VersionIncompatible => "Version Incompatible",
|
||||
Network => "Network Error",
|
||||
Registry => "Registry Error",
|
||||
Serialization => "Serialization Error",
|
||||
Deserialization => "Deserialization Error",
|
||||
Utf8 => "UTF-8 Parse Error",
|
||||
ParseVersion => "Version Parsing Error",
|
||||
IncorrectDisk => "Incorrect Disk",
|
||||
// Nginx => "Nginx Error",
|
||||
Dependency => "Dependency Error",
|
||||
ParseS9pk => "S9PK Parsing Error",
|
||||
ParseUrl => "URL Parsing Error",
|
||||
DiskNotAvailable => "Disk Not Available",
|
||||
BlockDevice => "Block Device Error",
|
||||
InvalidOnionAddress => "Invalid Onion Address",
|
||||
Pack => "Pack Error",
|
||||
ValidateS9pk => "S9PK Validation Error",
|
||||
DiskCorrupted => "Disk Corrupted", // Remove
|
||||
Tor => "Tor Daemon Error",
|
||||
ConfigGen => "Config Generation Error",
|
||||
ParseNumber => "Number Parsing Error",
|
||||
Database => "Database Error",
|
||||
InvalidId => "Invalid ID",
|
||||
InvalidSignature => "Invalid Signature",
|
||||
Backup => "Backup Error",
|
||||
Restore => "Restore Error",
|
||||
Authorization => "Unauthorized",
|
||||
AutoConfigure => "Auto-Configure Error",
|
||||
Action => "Action Failed",
|
||||
RateLimited => "Rate Limited",
|
||||
InvalidRequest => "Invalid Request",
|
||||
MigrationFailed => "Migration Failed",
|
||||
Uninitialized => "Uninitialized",
|
||||
ParseNetAddress => "Net Address Parsing Error",
|
||||
ParseSshKey => "SSH Key Parsing Error",
|
||||
SoundError => "Sound Interface Error",
|
||||
ParseTimestamp => "Timestamp Parsing Error",
|
||||
ParseSysInfo => "System Info Parsing Error",
|
||||
Wifi => "WiFi Internal Error",
|
||||
Journald => "Journald Error",
|
||||
DiskManagement => "Disk Management Error",
|
||||
OpenSsl => "OpenSSL Internal Error",
|
||||
PasswordHashGeneration => "Password Hash Generation Error",
|
||||
DiagnosticMode => "Server is in Diagnostic Mode",
|
||||
ParseDbField => "Database Field Parse Error",
|
||||
Duplicate => "Duplication Error",
|
||||
MultipleErrors => "Multiple Errors",
|
||||
Incoherent => "Incoherent",
|
||||
InvalidBackupTargetId => "Invalid Backup Target ID",
|
||||
ProductKeyMismatch => "Incompatible Product Keys",
|
||||
LanPortConflict => "Incompatible LAN Port Configuration",
|
||||
Javascript => "Javascript Engine Error",
|
||||
Pem => "PEM Encoding Error",
|
||||
TLSInit => "TLS Backend Initialization Error",
|
||||
Ascii => "ASCII Parse Error",
|
||||
MissingHeader => "Missing Header",
|
||||
Grub => "Grub Error",
|
||||
Systemd => "Systemd Error",
|
||||
OpenSsh => "OpenSSH Error",
|
||||
Zram => "Zram Error",
|
||||
Lshw => "LSHW Error",
|
||||
CpuSettings => "CPU Settings Error",
|
||||
Firmware => "Firmware Error",
|
||||
Timeout => "Timeout Error",
|
||||
Lxc => "LXC Error",
|
||||
Cancelled => "Cancelled",
|
||||
Git => "Git Error",
|
||||
DBus => "DBus Error",
|
||||
InstallFailed => "Install Failed",
|
||||
UpdateFailed => "Update Failed",
|
||||
Smtp => "SMTP Error",
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for ErrorKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Error {
|
||||
pub source: color_eyre::eyre::Error,
|
||||
pub debug: Option<color_eyre::eyre::Error>,
|
||||
pub kind: ErrorKind,
|
||||
pub revision: Option<Revision>,
|
||||
pub task: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}: {:#}", self.kind.as_str(), self.source)
|
||||
}
|
||||
}
|
||||
impl Debug for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}: {:?}",
|
||||
self.kind.as_str(),
|
||||
self.debug.as_ref().unwrap_or(&self.source)
|
||||
)
|
||||
}
|
||||
}
|
||||
impl Error {
|
||||
pub fn new<E: Into<color_eyre::eyre::Error> + std::fmt::Debug + 'static>(
|
||||
source: E,
|
||||
kind: ErrorKind,
|
||||
) -> Self {
|
||||
let debug = (std::any::TypeId::of::<E>()
|
||||
== std::any::TypeId::of::<color_eyre::eyre::Error>())
|
||||
.then(|| eyre!("{source:?}"));
|
||||
Error {
|
||||
source: source.into(),
|
||||
debug,
|
||||
kind,
|
||||
revision: None,
|
||||
task: None,
|
||||
}
|
||||
}
|
||||
pub fn clone_output(&self) -> Self {
|
||||
Error {
|
||||
source: eyre!("{}", self.source),
|
||||
debug: self.debug.as_ref().map(|e| eyre!("{e}")),
|
||||
kind: self.kind,
|
||||
revision: self.revision.clone(),
|
||||
task: None,
|
||||
}
|
||||
}
|
||||
pub fn with_task(mut self, task: JoinHandle<()>) -> Self {
|
||||
self.task = Some(task);
|
||||
self
|
||||
}
|
||||
pub async fn wait(mut self) -> Self {
|
||||
if let Some(task) = &mut self.task {
|
||||
task.await.log_err();
|
||||
}
|
||||
self.task.take();
|
||||
self
|
||||
}
|
||||
}
|
||||
impl axum::response::IntoResponse for Error {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let mut res = axum::Json(RpcError::from(self)).into_response();
|
||||
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
res
|
||||
}
|
||||
}
|
||||
impl From<std::convert::Infallible> for Error {
|
||||
fn from(value: std::convert::Infallible) -> Self {
|
||||
match value {}
|
||||
}
|
||||
}
|
||||
impl From<InvalidId> for Error {
|
||||
fn from(err: InvalidId) -> Self {
|
||||
Error::new(err, ErrorKind::InvalidId)
|
||||
}
|
||||
}
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Filesystem)
|
||||
}
|
||||
}
|
||||
impl From<std::str::Utf8Error> for Error {
|
||||
fn from(e: std::str::Utf8Error) -> Self {
|
||||
Error::new(e, ErrorKind::Utf8)
|
||||
}
|
||||
}
|
||||
impl From<std::string::FromUtf8Error> for Error {
|
||||
fn from(e: std::string::FromUtf8Error) -> Self {
|
||||
Error::new(e, ErrorKind::Utf8)
|
||||
}
|
||||
}
|
||||
impl From<exver::ParseError> for Error {
|
||||
fn from(e: exver::ParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseVersion)
|
||||
}
|
||||
}
|
||||
impl From<rpc_toolkit::url::ParseError> for Error {
|
||||
fn from(e: rpc_toolkit::url::ParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseUrl)
|
||||
}
|
||||
}
|
||||
impl From<std::num::ParseIntError> for Error {
|
||||
fn from(e: std::num::ParseIntError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseNumber)
|
||||
}
|
||||
}
|
||||
impl From<std::num::ParseFloatError> for Error {
|
||||
fn from(e: std::num::ParseFloatError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseNumber)
|
||||
}
|
||||
}
|
||||
impl From<patch_db::Error> for Error {
|
||||
fn from(e: patch_db::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Database)
|
||||
}
|
||||
}
|
||||
impl From<ed25519_dalek::SignatureError> for Error {
|
||||
fn from(e: ed25519_dalek::SignatureError) -> Self {
|
||||
Error::new(e, ErrorKind::InvalidSignature)
|
||||
}
|
||||
}
|
||||
impl From<std::net::AddrParseError> for Error {
|
||||
fn from(e: std::net::AddrParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseNetAddress)
|
||||
}
|
||||
}
|
||||
impl From<ipnet::AddrParseError> for Error {
|
||||
fn from(e: ipnet::AddrParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseNetAddress)
|
||||
}
|
||||
}
|
||||
impl From<openssl::error::ErrorStack> for Error {
|
||||
fn from(e: openssl::error::ErrorStack) -> Self {
|
||||
Error::new(eyre!("{}", e), ErrorKind::OpenSsl)
|
||||
}
|
||||
}
|
||||
impl From<mbrman::Error> for Error {
|
||||
fn from(e: mbrman::Error) -> Self {
|
||||
Error::new(e, ErrorKind::DiskManagement)
|
||||
}
|
||||
}
|
||||
impl From<gpt::GptError> for Error {
|
||||
fn from(e: gpt::GptError) -> Self {
|
||||
Error::new(e, ErrorKind::DiskManagement)
|
||||
}
|
||||
}
|
||||
impl From<gpt::mbr::MBRError> for Error {
|
||||
fn from(e: gpt::mbr::MBRError) -> Self {
|
||||
Error::new(e, ErrorKind::DiskManagement)
|
||||
}
|
||||
}
|
||||
impl From<InvalidUri> for Error {
|
||||
fn from(e: InvalidUri) -> Self {
|
||||
Error::new(eyre!("{}", e), ErrorKind::ParseUrl)
|
||||
}
|
||||
}
|
||||
impl From<ssh_key::Error> for Error {
|
||||
fn from(e: ssh_key::Error) -> Self {
|
||||
Error::new(e, ErrorKind::OpenSsh)
|
||||
}
|
||||
}
|
||||
impl From<reqwest::Error> for Error {
|
||||
fn from(e: reqwest::Error) -> Self {
|
||||
let kind = match e {
|
||||
_ if e.is_builder() => ErrorKind::ParseUrl,
|
||||
_ if e.is_decode() => ErrorKind::Deserialization,
|
||||
_ => ErrorKind::Network,
|
||||
};
|
||||
Error::new(e, kind)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "arti")]
|
||||
impl From<arti_client::Error> for Error {
|
||||
fn from(e: arti_client::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Tor)
|
||||
}
|
||||
}
|
||||
impl From<torut::control::ConnError> for Error {
|
||||
fn from(e: torut::control::ConnError) -> Self {
|
||||
Error::new(e, ErrorKind::Tor)
|
||||
}
|
||||
}
|
||||
impl From<zbus::Error> for Error {
|
||||
fn from(e: zbus::Error) -> Self {
|
||||
Error::new(e, ErrorKind::DBus)
|
||||
}
|
||||
}
|
||||
impl From<rustls::Error> for Error {
|
||||
fn from(e: rustls::Error) -> Self {
|
||||
Error::new(e, ErrorKind::OpenSsl)
|
||||
}
|
||||
}
|
||||
impl From<lettre::error::Error> for Error {
|
||||
fn from(e: lettre::error::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Smtp)
|
||||
}
|
||||
}
|
||||
impl From<lettre::transport::smtp::Error> for Error {
|
||||
fn from(e: lettre::transport::smtp::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Smtp)
|
||||
}
|
||||
}
|
||||
impl From<lettre::address::AddressError> for Error {
|
||||
fn from(e: lettre::address::AddressError) -> Self {
|
||||
Error::new(e, ErrorKind::Smtp)
|
||||
}
|
||||
}
|
||||
impl From<hyper::Error> for Error {
|
||||
fn from(e: hyper::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Network)
|
||||
}
|
||||
}
|
||||
impl From<patch_db::value::Error> for Error {
|
||||
fn from(value: patch_db::value::Error) -> Self {
|
||||
match value.kind {
|
||||
patch_db::value::ErrorKind::Serialization => {
|
||||
Error::new(value.source, ErrorKind::Serialization)
|
||||
}
|
||||
patch_db::value::ErrorKind::Deserialization => {
|
||||
Error::new(value.source, ErrorKind::Deserialization)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, TS)]
|
||||
pub struct ErrorData {
|
||||
pub details: String,
|
||||
pub debug: String,
|
||||
}
|
||||
impl Display for ErrorData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(&self.details, f)
|
||||
}
|
||||
}
|
||||
impl Debug for ErrorData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(&self.debug, f)
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ErrorData {}
|
||||
impl From<Error> for ErrorData {
|
||||
fn from(value: Error) -> Self {
|
||||
Self {
|
||||
details: value.to_string(),
|
||||
debug: format!("{:?}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<&RpcError> for ErrorData {
|
||||
fn from(value: &RpcError) -> Self {
|
||||
Self {
|
||||
details: value
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|d| {
|
||||
d.as_object()
|
||||
.and_then(|d| {
|
||||
d.get("details")
|
||||
.and_then(|d| d.as_str().map(|s| s.to_owned()))
|
||||
})
|
||||
.or_else(|| d.as_str().map(|s| s.to_owned()))
|
||||
})
|
||||
.unwrap_or_else(|| value.message.clone().into_owned()),
|
||||
debug: value
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|d| {
|
||||
d.as_object()
|
||||
.and_then(|d| {
|
||||
d.get("debug")
|
||||
.and_then(|d| d.as_str().map(|s| s.to_owned()))
|
||||
})
|
||||
.or_else(|| d.as_str().map(|s| s.to_owned()))
|
||||
})
|
||||
.unwrap_or_else(|| value.message.clone().into_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for RpcError {
|
||||
fn from(e: Error) -> Self {
|
||||
let mut data_object = serde_json::Map::with_capacity(3);
|
||||
data_object.insert("details".to_owned(), format!("{}", e.source).into());
|
||||
data_object.insert("debug".to_owned(), format!("{:?}", e.source).into());
|
||||
data_object.insert(
|
||||
"revision".to_owned(),
|
||||
match serde_json::to_value(&e.revision) {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
tracing::warn!("Error serializing revision for Error object: {}", e);
|
||||
serde_json::Value::Null
|
||||
}
|
||||
},
|
||||
);
|
||||
RpcError {
|
||||
code: e.kind as i32,
|
||||
message: e.kind.as_str().into(),
|
||||
data: Some(
|
||||
match serde_json::to_value(&ErrorData {
|
||||
details: format!("{}", e.source),
|
||||
debug: format!("{:?}", e.source),
|
||||
}) {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
tracing::warn!("Error serializing revision for Error object: {}", e);
|
||||
serde_json::Value::Null
|
||||
}
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<RpcError> for Error {
|
||||
fn from(e: RpcError) -> Self {
|
||||
Error::new(
|
||||
ErrorData::from(&e),
|
||||
if let Ok(kind) = e.code.try_into() {
|
||||
kind
|
||||
} else if e.code == METHOD_NOT_FOUND_ERROR.code {
|
||||
ErrorKind::NotFound
|
||||
} else if e.code == PARSE_ERROR.code
|
||||
|| e.code == INVALID_PARAMS_ERROR.code
|
||||
|| e.code == INVALID_REQUEST_ERROR.code
|
||||
{
|
||||
ErrorKind::Deserialization
|
||||
} else {
|
||||
ErrorKind::Unknown
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ErrorCollection(Vec<Error>);
|
||||
@@ -17,15 +540,11 @@ impl ErrorCollection {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_result(mut self) -> Result<(), Error> {
|
||||
if self.0.len() <= 1 {
|
||||
if let Some(err) = self.0.pop() {
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
pub fn into_result(self) -> Result<(), Error> {
|
||||
if self.0.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(self, ErrorKind::MultipleErrors))
|
||||
Err(Error::new(eyre!("{}", self), ErrorKind::MultipleErrors))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,13 +571,108 @@ impl std::fmt::Display for ErrorCollection {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ErrorCollection {}
|
||||
|
||||
pub trait ResultExt<T, E>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error>;
|
||||
fn with_ctx<F: FnOnce(&E) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error>;
|
||||
fn log_err(self) -> Option<T>;
|
||||
}
|
||||
impl<T, E> ResultExt<T, E> for Result<T, E>
|
||||
where
|
||||
color_eyre::eyre::Error: From<E>,
|
||||
E: std::fmt::Debug + 'static,
|
||||
{
|
||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
||||
self.map_err(|e| Error::new(e, kind))
|
||||
}
|
||||
|
||||
fn with_ctx<F: FnOnce(&E) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
||||
self.map_err(|e| {
|
||||
let (kind, ctx) = f(&e);
|
||||
let debug = (std::any::TypeId::of::<E>()
|
||||
== std::any::TypeId::of::<color_eyre::eyre::Error>())
|
||||
.then(|| eyre!("{ctx}: {e:?}"));
|
||||
let source = color_eyre::eyre::Error::from(e);
|
||||
let with_ctx = format!("{ctx}: {source}");
|
||||
let source = source.wrap_err(with_ctx);
|
||||
Error {
|
||||
kind,
|
||||
source,
|
||||
debug,
|
||||
revision: None,
|
||||
task: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn log_err(self) -> Option<T> {
|
||||
match self {
|
||||
Ok(a) => Some(a),
|
||||
Err(e) => {
|
||||
let e: color_eyre::eyre::Error = e.into();
|
||||
tracing::error!("{e}");
|
||||
tracing::debug!("{e:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> ResultExt<T, Error> for Result<T, Error> {
|
||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
||||
self.map_err(|e| Error { kind, ..e })
|
||||
}
|
||||
|
||||
fn with_ctx<F: FnOnce(&Error) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
||||
self.map_err(|e| {
|
||||
let (kind, ctx) = f(&e);
|
||||
let source = e.source;
|
||||
let with_ctx = format!("{ctx}: {source}");
|
||||
let source = source.wrap_err(with_ctx);
|
||||
let debug = e.debug.map(|e| {
|
||||
let with_ctx = format!("{ctx}: {e}");
|
||||
e.wrap_err(with_ctx)
|
||||
});
|
||||
Error {
|
||||
kind,
|
||||
source,
|
||||
debug,
|
||||
..e
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn log_err(self) -> Option<T> {
|
||||
match self {
|
||||
Ok(a) => Some(a),
|
||||
Err(e) => {
|
||||
tracing::error!("{e}");
|
||||
tracing::debug!("{e:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OptionExt<T>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn or_not_found(self, message: impl std::fmt::Display) -> Result<T, Error>;
|
||||
}
|
||||
impl<T> OptionExt<T> for Option<T> {
|
||||
fn or_not_found(self, message: impl std::fmt::Display) -> Result<T, Error> {
|
||||
self.ok_or_else(|| Error::new(eyre!("{}", message), ErrorKind::NotFound))
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ensure_code {
|
||||
($x:expr, $c:expr, $fmt:expr $(, $arg:expr)*) => {
|
||||
if !($x) {
|
||||
Err::<(), _>(crate::error::Error::new(color_eyre::eyre::eyre!($fmt, $($arg, )*), $c))?;
|
||||
return Err(Error::new(color_eyre::eyre::eyre!($fmt, $($arg, )*), $c));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ use std::convert::Infallible;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use imbl_value::InternedString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
use yasi::InternedString;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)]
|
||||
#[ts(type = "string")]
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use imbl_value::InternedString;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use ts_rs::TS;
|
||||
use yasi::InternedString;
|
||||
|
||||
use crate::{Id, InvalidId};
|
||||
|
||||
@@ -5,7 +5,8 @@ use std::str::FromStr;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::{Id, InvalidId, PackageId, VersionString};
|
||||
use crate::util::VersionString;
|
||||
use crate::{Id, InvalidId, PackageId};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)]
|
||||
#[ts(type = "string")]
|
||||
@@ -1,4 +1,4 @@
|
||||
use yasi::InternedString;
|
||||
use imbl_value::InternedString;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("Invalid ID: {0}")]
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::str::FromStr;
|
||||
|
||||
use imbl_value::InternedString;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use yasi::InternedString;
|
||||
|
||||
mod action;
|
||||
mod gateway;
|
||||
@@ -2,9 +2,9 @@ use std::borrow::Borrow;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use imbl_value::InternedString;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use ts_rs::TS;
|
||||
use yasi::InternedString;
|
||||
|
||||
use crate::{Id, InvalidId, SYSTEM_ID};
|
||||
|
||||
@@ -2,9 +2,9 @@ use std::convert::Infallible;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use imbl_value::InternedString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
use yasi::InternedString;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)]
|
||||
#[ts(type = "string")]
|
||||
@@ -5,7 +5,8 @@ use rpc_toolkit::clap::builder::ValueParserFactory;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::{FromStrParser, Id};
|
||||
use crate::Id;
|
||||
use crate::util::FromStrParser;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)]
|
||||
#[ts(export, type = "string")]
|
||||
@@ -7,7 +7,6 @@ use axum::extract::ws;
|
||||
use const_format::formatcp;
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use itertools::Itertools;
|
||||
use models::ResultExt;
|
||||
use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::process::Command;
|
||||
@@ -21,7 +20,7 @@ use crate::db::model::Database;
|
||||
use crate::db::model::public::ServerStatus;
|
||||
use crate::developer::OS_DEVELOPER_KEY_PATH;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::middleware::auth::AuthContext;
|
||||
use crate::middleware::auth::local::LocalAuthContext;
|
||||
use crate::net::gateway::UpgradableListener;
|
||||
use crate::net::net_controller::{NetController, NetService};
|
||||
use crate::net::socks::DEFAULT_SOCKS_LISTEN;
|
||||
@@ -37,9 +36,8 @@ use crate::ssh::SSH_DIR;
|
||||
use crate::system::{get_mem_info, sync_kiosk};
|
||||
use crate::util::io::{IOHook, open_file};
|
||||
use crate::util::lshw::lshw;
|
||||
use crate::util::net::WebSocketExt;
|
||||
use crate::util::{Invoke, cpupower};
|
||||
use crate::{Error, MAIN_DATA, PACKAGE_DATA};
|
||||
use crate::{Error, MAIN_DATA, PACKAGE_DATA, ResultExt};
|
||||
|
||||
pub const SYSTEM_REBUILD_PATH: &str = "/media/startos/config/system-rebuild";
|
||||
pub const STANDBY_MODE_PATH: &str = "/media/startos/config/standby";
|
||||
@@ -303,8 +301,8 @@ pub async fn init(
|
||||
if tokio::fs::metadata(&downloading).await.is_ok() {
|
||||
tokio::fs::remove_dir_all(&downloading).await?;
|
||||
}
|
||||
let tmp_docker = Path::new(PACKAGE_DATA).join(formatcp!("tmp/{CONTAINER_TOOL}"));
|
||||
crate::disk::mount::util::bind(&tmp_docker, CONTAINER_DATADIR, false).await?;
|
||||
let tmp_docker = Path::new(PACKAGE_DATA).join("tmp").join(*CONTAINER_TOOL);
|
||||
crate::disk::mount::util::bind(&tmp_docker, *CONTAINER_DATADIR, false).await?;
|
||||
init_tmp.complete();
|
||||
|
||||
let server_info = db.peek().await.into_public().into_server_info();
|
||||
|
||||
@@ -10,7 +10,6 @@ use exver::VersionRange;
|
||||
use futures::StreamExt;
|
||||
use imbl_value::{InternedString, json};
|
||||
use itertools::Itertools;
|
||||
use models::{FromStrParser, VersionString};
|
||||
use reqwest::Url;
|
||||
use reqwest::header::{CONTENT_LENGTH, HeaderMap};
|
||||
use rpc_toolkit::HandlerArgs;
|
||||
@@ -29,11 +28,11 @@ use crate::registry::context::{RegistryContext, RegistryUrlParams};
|
||||
use crate::registry::package::get::GetPackageResponse;
|
||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::s9pk::v2::SIG_CONTEXT;
|
||||
use crate::upload::upload;
|
||||
use crate::util::Never;
|
||||
use crate::util::io::open_file;
|
||||
use crate::util::net::WebSocketExt;
|
||||
use crate::util::tui::choose;
|
||||
use crate::util::{FromStrParser, Never, VersionString};
|
||||
|
||||
pub const PKG_ARCHIVE_DIR: &str = "package-data/archive";
|
||||
pub const PKG_PUBLIC_DIR: &str = "package-data/public";
|
||||
@@ -154,6 +153,8 @@ pub async fn install(
|
||||
})?
|
||||
.s9pk;
|
||||
|
||||
asset.validate(SIG_CONTEXT, asset.all_signers())?;
|
||||
|
||||
let progress_tracker = FullProgressTracker::new();
|
||||
let download_progress = progress_tracker.add_phase("Downloading".into(), Some(100));
|
||||
let download = ctx
|
||||
|
||||
@@ -44,6 +44,7 @@ pub mod disk;
|
||||
pub mod error;
|
||||
pub mod firmware;
|
||||
pub mod hostname;
|
||||
pub mod id;
|
||||
pub mod init;
|
||||
pub mod install;
|
||||
pub mod logs;
|
||||
@@ -75,7 +76,8 @@ pub mod volume;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use clap::Parser;
|
||||
pub use error::{Error, ErrorKind, ResultExt};
|
||||
pub use error::{Error, ErrorKind, OptionExt, ResultExt};
|
||||
pub use id::*;
|
||||
use imbl_value::Value;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use rpc_toolkit::{
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
use std::convert::Infallible;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::path::Path;
|
||||
use std::process::Stdio;
|
||||
use std::str::FromStr;
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
|
||||
use axum::extract::ws::{self, WebSocket};
|
||||
use axum::extract::ws;
|
||||
use crate::util::net::WebSocket;
|
||||
use chrono::{DateTime, Utc};
|
||||
use clap::builder::ValueParserFactory;
|
||||
use clap::{Args, FromArgMatches, Parser};
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::stream::BoxStream;
|
||||
use futures::{Future, FutureExt, Stream, StreamExt, TryStreamExt};
|
||||
use futures::{Future, Stream, StreamExt, TryStreamExt};
|
||||
use itertools::Itertools;
|
||||
use models::{FromStrParser, PackageId};
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use rpc_toolkit::{
|
||||
CallRemote, Context, Empty, HandlerArgs, HandlerExt, HandlerFor, ParentHandler, from_fn_async,
|
||||
@@ -25,14 +26,13 @@ use tokio_stream::wrappers::LinesStream;
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::PackageId;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::error::ResultExt;
|
||||
use crate::lxc::ContainerId;
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations};
|
||||
use crate::util::Invoke;
|
||||
use crate::util::net::WebSocketExt;
|
||||
use crate::util::serde::Reversible;
|
||||
use crate::util::{FromStrParser, Invoke};
|
||||
|
||||
#[pin_project::pin_project]
|
||||
pub struct LogStream {
|
||||
@@ -100,8 +100,8 @@ async fn ws_handler(
|
||||
return stream.normal_close("complete").await;
|
||||
}
|
||||
},
|
||||
msg = stream.try_next() => {
|
||||
if msg.with_kind(crate::ErrorKind::Network)?.is_none() {
|
||||
msg = stream.recv() => {
|
||||
if msg.transpose().with_kind(crate::ErrorKind::Network)?.is_none() {
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
@@ -223,7 +223,7 @@ fn deserialize_log_message<'de, D: serde::de::Deserializer<'de>>(
|
||||
pub enum LogSource {
|
||||
Kernel,
|
||||
Unit(&'static str),
|
||||
Container(ContainerId),
|
||||
Package(PackageId),
|
||||
}
|
||||
|
||||
pub const SYSTEM_UNIT: &str = "startd";
|
||||
@@ -499,22 +499,10 @@ fn logs_follow<
|
||||
}
|
||||
|
||||
async fn get_package_id(
|
||||
ctx: &RpcContext,
|
||||
_: &RpcContext,
|
||||
PackageIdParams { id }: PackageIdParams,
|
||||
) -> Result<LogSource, Error> {
|
||||
let container_id = ctx
|
||||
.services
|
||||
.get(&id)
|
||||
.await
|
||||
.as_ref()
|
||||
.map(|x| x.container_id())
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("No service found with id: {}", id),
|
||||
ErrorKind::NotFound,
|
||||
)
|
||||
})??;
|
||||
Ok(LogSource::Container(container_id))
|
||||
Ok(LogSource::Package(id))
|
||||
}
|
||||
|
||||
pub fn package_logs() -> ParentHandler<RpcContext, LogsParams<PackageIdParams>> {
|
||||
@@ -596,16 +584,8 @@ pub async fn journalctl(
|
||||
}
|
||||
|
||||
fn gen_journalctl_command(id: &LogSource) -> Command {
|
||||
let mut cmd = match id {
|
||||
LogSource::Container(container_id) => {
|
||||
let mut cmd = Command::new("lxc-attach");
|
||||
cmd.arg(format!("{}", container_id))
|
||||
.arg("--")
|
||||
.arg("journalctl");
|
||||
cmd
|
||||
}
|
||||
_ => Command::new("journalctl"),
|
||||
};
|
||||
let mut cmd = Command::new("journalctl");
|
||||
|
||||
cmd.kill_on_drop(true);
|
||||
|
||||
cmd.arg("--output=json");
|
||||
@@ -618,8 +598,11 @@ fn gen_journalctl_command(id: &LogSource) -> Command {
|
||||
cmd.arg("-u");
|
||||
cmd.arg(id);
|
||||
}
|
||||
LogSource::Container(_container_id) => {
|
||||
cmd.arg("-u").arg("container-runtime.service");
|
||||
LogSource::Package(id) => {
|
||||
cmd.arg("-u")
|
||||
.arg("container-runtime.service")
|
||||
.arg("-D")
|
||||
.arg(Path::new("/media/startos/data/package-data/logs").join(id));
|
||||
}
|
||||
};
|
||||
cmd
|
||||
@@ -715,16 +698,11 @@ pub async fn follow_logs<Context: AsRef<RpcContinuations>>(
|
||||
.add(
|
||||
guid.clone(),
|
||||
RpcContinuation::ws(
|
||||
Box::new(move |socket| {
|
||||
ws_handler(first_entry, stream, socket)
|
||||
.map(|x| match x {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
tracing::error!("Error in log stream: {}", e);
|
||||
}
|
||||
})
|
||||
.boxed()
|
||||
}),
|
||||
move |socket| async move {
|
||||
if let Err(e) = ws_handler(first_entry, stream, socket).await {
|
||||
tracing::error!("Error in log stream: {}", e);
|
||||
}
|
||||
},
|
||||
Duration::from_secs(30),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -5,11 +5,10 @@ use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::builder::ValueParserFactory;
|
||||
use futures::{AsyncWriteExt, StreamExt};
|
||||
use imbl_value::{InOMap, InternedString};
|
||||
use models::{FromStrParser, InvalidId, PackageId};
|
||||
use futures::StreamExt;
|
||||
use imbl_value::InternedString;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use rpc_toolkit::{GenericRpcMethod, RpcRequest, RpcResponse};
|
||||
use rpc_toolkit::{RpcRequest, RpcResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use tokio::process::Command;
|
||||
@@ -17,10 +16,10 @@ use tokio::sync::Mutex;
|
||||
use tokio::time::Instant;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::context::RpcContext;
|
||||
use crate::disk::mount::filesystem::bind::Bind;
|
||||
use crate::disk::mount::filesystem::block_dev::BlockDev;
|
||||
use crate::disk::mount::filesystem::idmapped::IdMapped;
|
||||
use crate::disk::mount::filesystem::idmapped::{IdMap, IdMapped};
|
||||
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
|
||||
use crate::disk::mount::filesystem::{MountType, ReadOnly, ReadWrite};
|
||||
use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard};
|
||||
@@ -30,7 +29,8 @@ use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||
use crate::service::ServiceStats;
|
||||
use crate::util::io::open_file;
|
||||
use crate::util::rpc_client::UnixRpcClient;
|
||||
use crate::util::{Invoke, new_guid};
|
||||
use crate::util::{FromStrParser, Invoke, new_guid};
|
||||
use crate::{InvalidId, PackageId};
|
||||
|
||||
const LXC_CONTAINER_DIR: &str = "/var/lib/lxc";
|
||||
const RPC_DIR: &str = "media/startos/rpc"; // must not be absolute path
|
||||
@@ -185,9 +185,11 @@ impl LxcContainer {
|
||||
TmpMountGuard::mount(
|
||||
&IdMapped::new(
|
||||
BlockDev::new("/usr/lib/startos/container-runtime/rootfs.squashfs"),
|
||||
0,
|
||||
100000,
|
||||
65536,
|
||||
vec![IdMap {
|
||||
from_id: 0,
|
||||
to_id: 100000,
|
||||
range: 65536,
|
||||
}],
|
||||
),
|
||||
ReadOnly,
|
||||
)
|
||||
@@ -366,6 +368,7 @@ impl LxcContainer {
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
tracing::info!("Connected to socket in {:?}", started.elapsed());
|
||||
Ok(UnixRpcClient::new(sock_path))
|
||||
}
|
||||
}
|
||||
@@ -424,7 +427,7 @@ pub async fn connect(ctx: &RpcContext, container: &LxcContainer) -> Result<Guid,
|
||||
|mut ws| async move {
|
||||
if let Err(e) = async {
|
||||
loop {
|
||||
match ws.next().await {
|
||||
match ws.recv().await {
|
||||
None => break,
|
||||
Some(Ok(Message::Text(txt))) => {
|
||||
let mut id = None;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
fn main() {
|
||||
#[cfg(feature = "backtrace-on-stack-overflow")]
|
||||
unsafe {
|
||||
backtrace_on_stack_overflow::enable()
|
||||
};
|
||||
startos::bins::startbox()
|
||||
}
|
||||
8
core/startos/src/main/registrybox.rs
Normal file
8
core/startos/src/main/registrybox.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use startos::bins::MultiExecutable;
|
||||
|
||||
fn main() {
|
||||
MultiExecutable::default()
|
||||
.enable_start_registry()
|
||||
.enable_start_registryd()
|
||||
.execute()
|
||||
}
|
||||
19
core/startos/src/main/start-cli.rs
Normal file
19
core/startos/src/main/start-cli.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use startos::bins::MultiExecutable;
|
||||
use startos::s9pk::v2::pack::PREFER_DOCKER;
|
||||
|
||||
fn main() {
|
||||
if !std::env::var("STARTOS_USE_PODMAN").map_or(false, |v| {
|
||||
let v = v.trim();
|
||||
if ["1", "true", "y", "yes"].into_iter().any(|x| v == x) {
|
||||
true
|
||||
} else if ["0", "false", "n", "no"].into_iter().any(|x| v == x) {
|
||||
false
|
||||
} else {
|
||||
tracing::warn!("Unknown value for STARTOS_USE_PODMAN: {v}");
|
||||
false
|
||||
}
|
||||
}) {
|
||||
PREFER_DOCKER.set(true).ok();
|
||||
}
|
||||
MultiExecutable::default().enable_start_cli().execute()
|
||||
}
|
||||
7
core/startos/src/main/start-container.rs
Normal file
7
core/startos/src/main/start-container.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use startos::bins::MultiExecutable;
|
||||
|
||||
fn main() {
|
||||
MultiExecutable::default()
|
||||
.enable_start_container()
|
||||
.execute()
|
||||
}
|
||||
29
core/startos/src/main/startbox.rs
Normal file
29
core/startos/src/main/startbox.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use startos::bins::MultiExecutable;
|
||||
|
||||
fn main() {
|
||||
startos::net::static_server::UI_CELL
|
||||
.set(include_dir::include_dir!(
|
||||
"$CARGO_MANIFEST_DIR/../../web/dist/static/ui"
|
||||
))
|
||||
.ok();
|
||||
startos::net::static_server::SETUP_WIZARD_CELL
|
||||
.set(include_dir::include_dir!(
|
||||
"$CARGO_MANIFEST_DIR/../../web/dist/static/setup-wizard"
|
||||
))
|
||||
.ok();
|
||||
startos::net::static_server::INSTALL_WIZARD_CELL
|
||||
.set(include_dir::include_dir!(
|
||||
"$CARGO_MANIFEST_DIR/../../web/dist/static/install-wizard"
|
||||
))
|
||||
.ok();
|
||||
startos::db::model::public::DB_UI_SEED_CELL
|
||||
.set(include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../../web/patchdb-ui-seed.json"
|
||||
)))
|
||||
.ok();
|
||||
MultiExecutable::default()
|
||||
.enable_startd()
|
||||
.enable_start_cli()
|
||||
.execute()
|
||||
}
|
||||
13
core/startos/src/main/tunnelbox.rs
Normal file
13
core/startos/src/main/tunnelbox.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use startos::bins::MultiExecutable;
|
||||
|
||||
fn main() {
|
||||
startos::tunnel::context::TUNNEL_UI_CELL
|
||||
.set(include_dir::include_dir!(
|
||||
"$CARGO_MANIFEST_DIR/../../web/dist/static/start-tunnel"
|
||||
))
|
||||
.ok();
|
||||
MultiExecutable::default()
|
||||
.enable_start_tunnel()
|
||||
.enable_start_tunneld()
|
||||
.execute()
|
||||
}
|
||||
101
core/startos/src/middleware/auth/local.rs
Normal file
101
core/startos/src/middleware/auth/local.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use base64::Engine;
|
||||
use basic_cookies::Cookie;
|
||||
use http::HeaderValue;
|
||||
use http::header::COOKIE;
|
||||
use rand::random;
|
||||
use rpc_toolkit::yajrc::{RpcError, RpcResponse};
|
||||
use rpc_toolkit::{Context, Empty, Middleware};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
use crate::util::io::{create_file_mod, read_file_to_string};
|
||||
use crate::util::serde::BASE64;
|
||||
|
||||
pub trait LocalAuthContext: Context {
|
||||
const LOCAL_AUTH_COOKIE_PATH: &str;
|
||||
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str;
|
||||
fn init_auth_cookie() -> impl Future<Output = Result<(), Error>> + Send {
|
||||
async {
|
||||
let mut file = create_file_mod(Self::LOCAL_AUTH_COOKIE_PATH, 0o640).await?;
|
||||
file.write_all(BASE64.encode(random::<[u8; 32]>()).as_bytes())
|
||||
.await?;
|
||||
file.sync_all().await?;
|
||||
drop(file);
|
||||
Command::new("chown")
|
||||
.arg(Self::LOCAL_AUTH_COOKIE_OWNERSHIP)
|
||||
.arg(Self::LOCAL_AUTH_COOKIE_PATH)
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalAuthContext for RpcContext {
|
||||
const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/rpc.authcookie";
|
||||
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str = "root:startos";
|
||||
}
|
||||
|
||||
fn unauthorized() -> Error {
|
||||
Error::new(eyre!("UNAUTHORIZED"), crate::ErrorKind::Authorization)
|
||||
}
|
||||
|
||||
async fn check_from_header<C: LocalAuthContext>(header: Option<&HeaderValue>) -> Result<(), Error> {
|
||||
if let Some(cookie_header) = header {
|
||||
let cookies = Cookie::parse(
|
||||
cookie_header
|
||||
.to_str()
|
||||
.with_kind(crate::ErrorKind::Authorization)?,
|
||||
)
|
||||
.with_kind(crate::ErrorKind::Authorization)?;
|
||||
if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "local") {
|
||||
return check_cookie::<C>(cookie).await;
|
||||
}
|
||||
}
|
||||
Err(unauthorized())
|
||||
}
|
||||
|
||||
async fn check_cookie<C: LocalAuthContext>(local: &Cookie<'_>) -> Result<(), Error> {
|
||||
if let Ok(token) = read_file_to_string(C::LOCAL_AUTH_COOKIE_PATH).await {
|
||||
if local.get_value() == &*token {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err(unauthorized())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LocalAuth {
|
||||
cookie: Option<HeaderValue>,
|
||||
}
|
||||
impl LocalAuth {
|
||||
pub fn new() -> Self {
|
||||
Self { cookie: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: LocalAuthContext> Middleware<C> for LocalAuth {
|
||||
type Metadata = Empty;
|
||||
async fn process_http_request(
|
||||
&mut self,
|
||||
_: &C,
|
||||
request: &mut axum::extract::Request,
|
||||
) -> Result<(), axum::response::Response> {
|
||||
self.cookie = request.headers().get(COOKIE).cloned();
|
||||
Ok(())
|
||||
}
|
||||
async fn process_rpc_request(
|
||||
&mut self,
|
||||
_: &C,
|
||||
_: Self::Metadata,
|
||||
_: &mut rpc_toolkit::RpcRequest,
|
||||
) -> Result<(), rpc_toolkit::RpcResponse> {
|
||||
check_from_header::<C>(self.cookie.as_ref())
|
||||
.await
|
||||
.map_err(|e| RpcResponse::from(RpcError::from(e)))
|
||||
}
|
||||
}
|
||||
113
core/startos/src/middleware/auth/mod.rs
Normal file
113
core/startos/src/middleware/auth/mod.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use axum::extract::Request;
|
||||
use axum::response::Response;
|
||||
use rpc_toolkit::{Context, DynMiddleware, Middleware, RpcRequest, RpcResponse};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::Database;
|
||||
use crate::middleware::auth::local::{LocalAuth, LocalAuthContext};
|
||||
use crate::middleware::auth::session::{SessionAuth, SessionAuthContext};
|
||||
use crate::middleware::auth::signature::{SignatureAuth, SignatureAuthContext};
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::const_true;
|
||||
|
||||
pub mod local;
|
||||
pub mod session;
|
||||
pub mod signature;
|
||||
|
||||
pub trait DbContext: Context {
|
||||
type Database: HasModel<Model = Model<Self::Database>> + Send + Sync;
|
||||
fn db(&self) -> &TypedPatchDb<Self::Database>;
|
||||
}
|
||||
impl DbContext for RpcContext {
|
||||
type Database = Database;
|
||||
fn db(&self) -> &TypedPatchDb<Self::Database> {
|
||||
&self.db
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Metadata {
|
||||
#[serde(default = "const_true")]
|
||||
authenticated: bool,
|
||||
}
|
||||
|
||||
pub struct Auth<C: Context>(Vec<DynMiddleware<C>>);
|
||||
impl<C: Context> Clone for Auth<C> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
impl<C: Context> Auth<C> {
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
}
|
||||
impl<C: LocalAuthContext> Auth<C> {
|
||||
pub fn with_local_auth(mut self) -> Self {
|
||||
self.0.push(DynMiddleware::new(LocalAuth::new()));
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<C: SignatureAuthContext> Auth<C> {
|
||||
pub fn with_signature_auth(mut self) -> Self {
|
||||
self.0.push(DynMiddleware::new(SignatureAuth::new()));
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<C: SessionAuthContext> Auth<C> {
|
||||
pub fn with_session_auth(mut self) -> Self {
|
||||
self.0.push(DynMiddleware::new(SessionAuth::new()));
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<C: Context> Middleware<C> for Auth<C> {
|
||||
type Metadata = Value;
|
||||
async fn process_http_request(
|
||||
&mut self,
|
||||
context: &C,
|
||||
request: &mut Request,
|
||||
) -> Result<(), Response> {
|
||||
for middleware in self.0.iter_mut() {
|
||||
middleware.process_http_request(context, request).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn process_rpc_request(
|
||||
&mut self,
|
||||
context: &C,
|
||||
metadata: Self::Metadata,
|
||||
request: &mut RpcRequest,
|
||||
) -> Result<(), RpcResponse> {
|
||||
let m: Metadata =
|
||||
from_value(metadata.clone()).map_err(|e| RpcResponse::from_result(Err(e)))?;
|
||||
let mut err = None;
|
||||
for middleware in self.0.iter_mut() {
|
||||
if let Err(e) = middleware
|
||||
.process_rpc_request(context, metadata.clone(), request)
|
||||
.await
|
||||
{
|
||||
if m.authenticated {
|
||||
err = Some(e);
|
||||
}
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if let Some(e) = err {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
async fn process_rpc_response(&mut self, context: &C, response: &mut RpcResponse) {
|
||||
for middleware in self.0.iter_mut() {
|
||||
middleware.process_rpc_response(context, response).await;
|
||||
}
|
||||
}
|
||||
async fn process_http_response(&mut self, context: &C, response: &mut Response) {
|
||||
for middleware in self.0.iter_mut() {
|
||||
middleware.process_http_response(context, response).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,31 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::BTreeSet;
|
||||
use std::future::Future;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use axum::extract::Request;
|
||||
use axum::response::Response;
|
||||
use base64::Engine;
|
||||
use basic_cookies::Cookie;
|
||||
use chrono::Utc;
|
||||
use color_eyre::eyre::eyre;
|
||||
use digest::Digest;
|
||||
use helpers::const_true;
|
||||
use http::HeaderValue;
|
||||
use http::header::{COOKIE, USER_AGENT};
|
||||
use imbl_value::{InternedString, json};
|
||||
use rand::random;
|
||||
use rpc_toolkit::yajrc::INTERNAL_ERROR;
|
||||
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::process::Command;
|
||||
use tokio::sync::Mutex;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::auth::{Sessions, check_password, write_shadow};
|
||||
use crate::context::RpcContext;
|
||||
use crate::middleware::signature::{SignatureAuth, SignatureAuthContext};
|
||||
use crate::middleware::auth::DbContext;
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::OpenAuthedContinuations;
|
||||
use crate::util::Invoke;
|
||||
use crate::util::io::{create_file_mod, read_file_to_string};
|
||||
use crate::util::serde::BASE64;
|
||||
use crate::util::serde::{BASE64, const_true};
|
||||
use crate::util::sync::SyncMutex;
|
||||
|
||||
pub trait AuthContext: SignatureAuthContext {
|
||||
const LOCAL_AUTH_COOKIE_PATH: &str;
|
||||
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str;
|
||||
fn init_auth_cookie() -> impl Future<Output = Result<(), Error>> + Send {
|
||||
async {
|
||||
let mut file = create_file_mod(Self::LOCAL_AUTH_COOKIE_PATH, 0o640).await?;
|
||||
file.write_all(BASE64.encode(random::<[u8; 32]>()).as_bytes())
|
||||
.await?;
|
||||
file.sync_all().await?;
|
||||
drop(file);
|
||||
Command::new("chown")
|
||||
.arg(Self::LOCAL_AUTH_COOKIE_OWNERSHIP)
|
||||
.arg(Self::LOCAL_AUTH_COOKIE_PATH)
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
pub trait SessionAuthContext: DbContext {
|
||||
fn ephemeral_sessions(&self) -> &SyncMutex<Sessions>;
|
||||
fn open_authed_continuations(&self) -> &OpenAuthedContinuations<Option<InternedString>>;
|
||||
fn access_sessions(db: &mut Model<Self::Database>) -> &mut Model<Sessions>;
|
||||
@@ -63,9 +36,7 @@ pub trait AuthContext: SignatureAuthContext {
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthContext for RpcContext {
|
||||
const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/rpc.authcookie";
|
||||
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str = "root:startos";
|
||||
impl SessionAuthContext for RpcContext {
|
||||
fn ephemeral_sessions(&self) -> &SyncMutex<Sessions> {
|
||||
&self.ephemeral_sessions
|
||||
}
|
||||
@@ -104,7 +75,7 @@ pub trait AsLogoutSessionId {
|
||||
pub struct HasLoggedOutSessions(());
|
||||
|
||||
impl HasLoggedOutSessions {
|
||||
pub async fn new<C: AuthContext>(
|
||||
pub async fn new<C: SessionAuthContext>(
|
||||
sessions: impl IntoIterator<Item = impl AsLogoutSessionId>,
|
||||
ctx: &C,
|
||||
) -> Result<Self, Error> {
|
||||
@@ -135,90 +106,6 @@ impl HasLoggedOutSessions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Used when we need to know that we have logged in with a valid user
|
||||
#[derive(Clone)]
|
||||
pub struct HasValidSession(SessionType);
|
||||
|
||||
#[derive(Clone)]
|
||||
enum SessionType {
|
||||
Local,
|
||||
Session(HashSessionToken),
|
||||
}
|
||||
|
||||
impl HasValidSession {
|
||||
pub async fn from_header<C: AuthContext>(
|
||||
header: Option<&HeaderValue>,
|
||||
ctx: &C,
|
||||
) -> Result<Self, Error> {
|
||||
if let Some(cookie_header) = header {
|
||||
let cookies = Cookie::parse(
|
||||
cookie_header
|
||||
.to_str()
|
||||
.with_kind(crate::ErrorKind::Authorization)?,
|
||||
)
|
||||
.with_kind(crate::ErrorKind::Authorization)?;
|
||||
if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "local") {
|
||||
if let Ok(s) = Self::from_local::<C>(cookie).await {
|
||||
return Ok(s);
|
||||
}
|
||||
}
|
||||
if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "session") {
|
||||
if let Ok(s) = Self::from_session(HashSessionToken::from_cookie(cookie), ctx).await
|
||||
{
|
||||
return Ok(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Error::new(
|
||||
eyre!("UNAUTHORIZED"),
|
||||
crate::ErrorKind::Authorization,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn from_session<C: AuthContext>(
|
||||
session_token: HashSessionToken,
|
||||
ctx: &C,
|
||||
) -> Result<Self, Error> {
|
||||
let session_hash = session_token.hashed();
|
||||
if !ctx.ephemeral_sessions().mutate(|s| {
|
||||
if let Some(session) = s.0.get_mut(session_hash) {
|
||||
session.last_active = Utc::now();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
ctx.db()
|
||||
.mutate(|db| {
|
||||
C::access_sessions(db)
|
||||
.as_idx_mut(session_hash)
|
||||
.ok_or_else(|| {
|
||||
Error::new(eyre!("UNAUTHORIZED"), crate::ErrorKind::Authorization)
|
||||
})?
|
||||
.mutate(|s| {
|
||||
s.last_active = Utc::now();
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
}
|
||||
Ok(Self(SessionType::Session(session_token)))
|
||||
}
|
||||
|
||||
pub async fn from_local<C: AuthContext>(local: &Cookie<'_>) -> Result<Self, Error> {
|
||||
let token = read_file_to_string(C::LOCAL_AUTH_COOKIE_PATH).await?;
|
||||
if local.get_value() == &*token {
|
||||
Ok(Self(SessionType::Local))
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("UNAUTHORIZED"),
|
||||
crate::ErrorKind::Authorization,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// When we have a need to create a new session,
|
||||
/// Or when we are using internal valid authenticated service.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -313,51 +200,97 @@ impl Borrow<str> for HashSessionToken {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ValidSessionToken(pub HashSessionToken);
|
||||
impl ValidSessionToken {
|
||||
pub async fn from_header<C: SessionAuthContext>(
|
||||
header: Option<&HeaderValue>,
|
||||
ctx: &C,
|
||||
) -> Result<Self, Error> {
|
||||
if let Some(cookie_header) = header {
|
||||
let cookies = Cookie::parse(
|
||||
cookie_header
|
||||
.to_str()
|
||||
.with_kind(crate::ErrorKind::Authorization)?,
|
||||
)
|
||||
.with_kind(crate::ErrorKind::Authorization)?;
|
||||
if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "session") {
|
||||
if let Ok(s) = Self::from_session(HashSessionToken::from_cookie(cookie), ctx).await
|
||||
{
|
||||
return Ok(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Error::new(
|
||||
eyre!("UNAUTHORIZED"),
|
||||
crate::ErrorKind::Authorization,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn from_session<C: SessionAuthContext>(
|
||||
session_token: HashSessionToken,
|
||||
ctx: &C,
|
||||
) -> Result<Self, Error> {
|
||||
let session_hash = session_token.hashed();
|
||||
if !ctx.ephemeral_sessions().mutate(|s| {
|
||||
if let Some(session) = s.0.get_mut(session_hash) {
|
||||
session.last_active = Utc::now();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
ctx.db()
|
||||
.mutate(|db| {
|
||||
C::access_sessions(db)
|
||||
.as_idx_mut(session_hash)
|
||||
.ok_or_else(|| {
|
||||
Error::new(eyre!("UNAUTHORIZED"), crate::ErrorKind::Authorization)
|
||||
})?
|
||||
.mutate(|s| {
|
||||
s.last_active = Utc::now();
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
}
|
||||
Ok(Self(session_token))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Metadata {
|
||||
#[serde(default = "const_true")]
|
||||
authenticated: bool,
|
||||
#[serde(default)]
|
||||
login: bool,
|
||||
#[serde(default)]
|
||||
get_session: bool,
|
||||
#[serde(default)]
|
||||
get_signer: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Auth {
|
||||
rate_limiter: Arc<Mutex<(usize, Instant)>>,
|
||||
cookie: Option<HeaderValue>,
|
||||
pub struct SessionAuth {
|
||||
rate_limiter: Arc<SyncMutex<(usize, Instant)>>,
|
||||
is_login: bool,
|
||||
cookie: Option<HeaderValue>,
|
||||
set_cookie: Option<HeaderValue>,
|
||||
user_agent: Option<HeaderValue>,
|
||||
signature_auth: SignatureAuth,
|
||||
}
|
||||
impl Auth {
|
||||
impl SessionAuth {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
rate_limiter: Arc::new(Mutex::new((0, Instant::now()))),
|
||||
cookie: None,
|
||||
rate_limiter: Arc::new(SyncMutex::new((0, Instant::now()))),
|
||||
is_login: false,
|
||||
cookie: None,
|
||||
set_cookie: None,
|
||||
user_agent: None,
|
||||
signature_auth: SignatureAuth::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<C: AuthContext> Middleware<C> for Auth {
|
||||
|
||||
impl<C: SessionAuthContext> Middleware<C> for SessionAuth {
|
||||
type Metadata = Metadata;
|
||||
async fn process_http_request(
|
||||
&mut self,
|
||||
context: &C,
|
||||
request: &mut Request,
|
||||
) -> Result<(), Response> {
|
||||
self.cookie = request.headers_mut().remove(COOKIE);
|
||||
self.user_agent = request.headers_mut().remove(USER_AGENT);
|
||||
self.signature_auth
|
||||
.process_http_request(context, request)
|
||||
.await?;
|
||||
async fn process_http_request(&mut self, _: &C, request: &mut Request) -> Result<(), Response> {
|
||||
self.cookie = request.headers().get(COOKIE).cloned();
|
||||
self.user_agent = request.headers().get(USER_AGENT).cloned();
|
||||
Ok(())
|
||||
}
|
||||
async fn process_rpc_request(
|
||||
@@ -369,56 +302,37 @@ impl<C: AuthContext> Middleware<C> for Auth {
|
||||
async {
|
||||
if metadata.login {
|
||||
self.is_login = true;
|
||||
let guard = self.rate_limiter.lock().await;
|
||||
if guard.1.elapsed() < Duration::from_secs(20) && guard.0 >= 3 {
|
||||
return Err(Error::new(
|
||||
eyre!("Please limit login attempts to 3 per 20 seconds."),
|
||||
crate::ErrorKind::RateLimited,
|
||||
));
|
||||
}
|
||||
self.rate_limiter.mutate(|(count, time)| {
|
||||
if time.elapsed() < Duration::from_secs(20) && *count >= 3 {
|
||||
Err(Error::new(
|
||||
eyre!("Please limit login attempts to 3 per 20 seconds."),
|
||||
crate::ErrorKind::RateLimited,
|
||||
))
|
||||
} else {
|
||||
*count += 1;
|
||||
*time = Instant::now();
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
if let Some(user_agent) = self.user_agent.as_ref().and_then(|h| h.to_str().ok()) {
|
||||
request.params["__Auth_userAgent"] =
|
||||
Value::String(Arc::new(user_agent.to_owned()))
|
||||
// TODO: will this panic?
|
||||
}
|
||||
} else if metadata.authenticated {
|
||||
if self
|
||||
.signature_auth
|
||||
.process_rpc_request(
|
||||
context,
|
||||
from_value(json!({
|
||||
"get_signer": metadata.get_signer
|
||||
}))?,
|
||||
request,
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
match HasValidSession::from_header(self.cookie.as_ref(), context).await? {
|
||||
HasValidSession(SessionType::Session(s)) if metadata.get_session => {
|
||||
request.params["__Auth_session"] =
|
||||
Value::String(Arc::new(s.hashed().deref().to_owned()));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
} else {
|
||||
let ValidSessionToken(s) =
|
||||
ValidSessionToken::from_header(self.cookie.as_ref(), context).await?;
|
||||
if metadata.get_session {
|
||||
request.params["__Auth_session"] =
|
||||
Value::String(Arc::new(s.hashed().deref().to_owned()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
.map_err(|e| RpcResponse::from_result(Err(e)))
|
||||
}
|
||||
async fn process_rpc_response(&mut self, _: &C, response: &mut RpcResponse) {
|
||||
if self.is_login {
|
||||
let mut guard = self.rate_limiter.lock().await;
|
||||
if guard.1.elapsed() < Duration::from_secs(20) {
|
||||
if response.result.is_err() {
|
||||
guard.0 += 1;
|
||||
}
|
||||
} else {
|
||||
guard.0 = 0;
|
||||
}
|
||||
guard.1 = Instant::now();
|
||||
if response.result.is_ok() {
|
||||
let res = std::mem::replace(&mut response.result, Err(INTERNAL_ERROR));
|
||||
response.result = async {
|
||||
@@ -8,14 +8,14 @@ use axum::extract::Request;
|
||||
use http::{HeaderMap, HeaderValue};
|
||||
use reqwest::Client;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use rpc_toolkit::{Context, Middleware, RpcRequest, RpcResponse};
|
||||
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
||||
use serde::Deserialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use tokio::sync::Mutex;
|
||||
use url::Url;
|
||||
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::model::Database;
|
||||
use crate::middleware::auth::DbContext;
|
||||
use crate::prelude::*;
|
||||
use crate::sign::commitment::Commitment;
|
||||
use crate::sign::commitment::request::RequestCommitment;
|
||||
@@ -25,11 +25,9 @@ use crate::util::serde::Base64;
|
||||
|
||||
pub const AUTH_SIG_HEADER: &str = "X-StartOS-Auth-Sig";
|
||||
|
||||
pub trait SignatureAuthContext: Context {
|
||||
type Database: HasModel<Model = Model<Self::Database>> + Send + Sync;
|
||||
pub trait SignatureAuthContext: DbContext {
|
||||
type AdditionalMetadata: DeserializeOwned + Send;
|
||||
type CheckPubkeyRes: Send;
|
||||
fn db(&self) -> &TypedPatchDb<Self::Database>;
|
||||
fn sig_context(
|
||||
&self,
|
||||
) -> impl Future<Output = impl IntoIterator<Item = Result<impl AsRef<str> + Send, Error>> + Send>
|
||||
@@ -47,12 +45,8 @@ pub trait SignatureAuthContext: Context {
|
||||
}
|
||||
|
||||
impl SignatureAuthContext for RpcContext {
|
||||
type Database = Database;
|
||||
type AdditionalMetadata = ();
|
||||
type CheckPubkeyRes = ();
|
||||
fn db(&self) -> &TypedPatchDb<Self::Database> {
|
||||
&self.db
|
||||
}
|
||||
async fn sig_context(
|
||||
&self,
|
||||
) -> impl IntoIterator<Item = Result<impl AsRef<str> + Send, Error>> + Send {
|
||||
@@ -96,7 +90,7 @@ impl SignatureAuthContext for RpcContext {
|
||||
}
|
||||
|
||||
Err(Error::new(
|
||||
eyre!("Developer Key is not authorized"),
|
||||
eyre!("Key is not authorized"),
|
||||
ErrorKind::IncorrectPassword,
|
||||
))
|
||||
}
|
||||
@@ -2,4 +2,3 @@ pub mod auth;
|
||||
pub mod connect_info;
|
||||
pub mod cors;
|
||||
pub mod db;
|
||||
pub mod signature;
|
||||
|
||||
@@ -9,7 +9,6 @@ use clap::builder::ValueParserFactory;
|
||||
use futures::StreamExt;
|
||||
use imbl_value::InternedString;
|
||||
use itertools::Itertools;
|
||||
use models::{ErrorData, FromStrParser};
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::x509::X509;
|
||||
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||
@@ -26,10 +25,12 @@ use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::model::Database;
|
||||
use crate::db::model::public::AcmeSettings;
|
||||
use crate::db::{DbAccess, DbAccessByKey, DbAccessMut};
|
||||
use crate::error::ErrorData;
|
||||
use crate::net::ssl::should_use_cert;
|
||||
use crate::net::tls::{SingleCertResolver, TlsHandler};
|
||||
use crate::net::web_server::Accept;
|
||||
use crate::prelude::*;
|
||||
use crate::util::FromStrParser;
|
||||
use crate::util::serde::{Pem, Pkcs8Doc};
|
||||
use crate::util::sync::{SyncMutex, Watch};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user