mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-27 02:41:53 +00:00
Compare commits
2 Commits
fix/servic
...
feat/rando
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04196df976 | ||
|
|
30b1654666 |
81
.github/actions/setup-build/action.yml
vendored
81
.github/actions/setup-build/action.yml
vendored
@@ -1,81 +0,0 @@
|
||||
name: Setup Build Environment
|
||||
description: Common build environment setup steps
|
||||
|
||||
inputs:
|
||||
nodejs-version:
|
||||
description: Node.js version
|
||||
required: true
|
||||
setup-python:
|
||||
description: Set up Python
|
||||
required: false
|
||||
default: "false"
|
||||
setup-docker:
|
||||
description: Set up Docker QEMU and Buildx
|
||||
required: false
|
||||
default: "true"
|
||||
setup-sccache:
|
||||
description: Configure sccache for GitHub Actions
|
||||
required: false
|
||||
default: "true"
|
||||
free-space:
|
||||
description: Remove unnecessary packages to free disk space
|
||||
required: false
|
||||
default: "true"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Free disk space
|
||||
if: inputs.free-space == 'true'
|
||||
shell: bash
|
||||
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
|
||||
sudo rm -rf /usr/local/.ghcup
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /usr/share/swift
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
|
||||
# BuildJet runners lack /opt/hostedtoolcache, which setup-python and setup-qemu expect
|
||||
- name: Ensure hostedtoolcache exists
|
||||
shell: bash
|
||||
run: sudo mkdir -p /opt/hostedtoolcache && sudo chown $USER:$USER /opt/hostedtoolcache
|
||||
|
||||
- name: Set up Python
|
||||
if: inputs.setup-python == 'true'
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ inputs.nodejs-version }}
|
||||
cache: npm
|
||||
cache-dependency-path: "**/package-lock.json"
|
||||
|
||||
- name: Set up Docker QEMU
|
||||
if: inputs.setup-docker == 'true'
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: inputs.setup-docker == 'true'
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Configure sccache
|
||||
if: inputs.setup-sccache == 'true'
|
||||
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 || '');
|
||||
48
.github/workflows/start-cli.yaml
vendored
48
.github/workflows/start-cli.yaml
vendored
@@ -37,10 +37,6 @@ on:
|
||||
- master
|
||||
- next/*
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODEJS_VERSION: "24.11.0"
|
||||
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||
@@ -48,7 +44,6 @@ env:
|
||||
jobs:
|
||||
compile:
|
||||
name: Build Debian Package
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
@@ -65,15 +60,50 @@ jobs:
|
||||
}}
|
||||
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||
steps:
|
||||
- name: Mount tmpfs
|
||||
- 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' }}
|
||||
run: sudo mount -t tmpfs tmpfs .
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: ./.github/actions/setup-build
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
nodejs-version: ${{ env.NODEJS_VERSION }}
|
||||
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
|
||||
|
||||
50
.github/workflows/start-registry.yaml
vendored
50
.github/workflows/start-registry.yaml
vendored
@@ -1,4 +1,4 @@
|
||||
name: start-registry
|
||||
name: Start-Registry
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
@@ -35,10 +35,6 @@ on:
|
||||
- master
|
||||
- next/*
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODEJS_VERSION: "24.11.0"
|
||||
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||
@@ -46,7 +42,6 @@ env:
|
||||
jobs:
|
||||
compile:
|
||||
name: Build Debian Package
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
@@ -61,15 +56,50 @@ jobs:
|
||||
}}
|
||||
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||
steps:
|
||||
- name: Mount tmpfs
|
||||
- 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' }}
|
||||
run: sudo mount -t tmpfs tmpfs .
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: ./.github/actions/setup-build
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
nodejs-version: ${{ env.NODEJS_VERSION }}
|
||||
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
|
||||
|
||||
50
.github/workflows/start-tunnel.yaml
vendored
50
.github/workflows/start-tunnel.yaml
vendored
@@ -1,4 +1,4 @@
|
||||
name: start-tunnel
|
||||
name: Start-Tunnel
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
@@ -35,10 +35,6 @@ on:
|
||||
- master
|
||||
- next/*
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODEJS_VERSION: "24.11.0"
|
||||
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||
@@ -46,7 +42,6 @@ env:
|
||||
jobs:
|
||||
compile:
|
||||
name: Build Debian Package
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
@@ -61,15 +56,50 @@ jobs:
|
||||
}}
|
||||
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||
steps:
|
||||
- name: Mount tmpfs
|
||||
- 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' }}
|
||||
run: sudo mount -t tmpfs tmpfs .
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: ./.github/actions/setup-build
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
nodejs-version: ${{ env.NODEJS_VERSION }}
|
||||
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
|
||||
|
||||
132
.github/workflows/startos-iso.yaml
vendored
132
.github/workflows/startos-iso.yaml
vendored
@@ -27,7 +27,7 @@ on:
|
||||
- x86_64-nonfree
|
||||
- aarch64
|
||||
- aarch64-nonfree
|
||||
# - raspberrypi
|
||||
- raspberrypi
|
||||
- riscv64
|
||||
deploy:
|
||||
type: choice
|
||||
@@ -45,10 +45,6 @@ on:
|
||||
- master
|
||||
- next/*
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODEJS_VERSION: "24.11.0"
|
||||
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||
@@ -56,7 +52,6 @@ env:
|
||||
jobs:
|
||||
compile:
|
||||
name: Compile Base Binaries
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
@@ -91,16 +86,57 @@ jobs:
|
||||
)[github.event.inputs.runner == 'fast']
|
||||
}}
|
||||
steps:
|
||||
- name: Mount tmpfs
|
||||
- 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' }}
|
||||
run: sudo mount -t tmpfs tmpfs .
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: ./.github/actions/setup-build
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
nodejs-version: ${{ env.NODEJS_VERSION }}
|
||||
setup-python: "true"
|
||||
python-version: "3.x"
|
||||
|
||||
- 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 system dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y qemu-user-static systemd-container squashfuse
|
||||
|
||||
- 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 ARCH=${{ matrix.arch }} compiled-${{ matrix.arch }}.tar
|
||||
@@ -118,14 +154,13 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# TODO: re-add "raspberrypi" to the platform list below
|
||||
platform: >-
|
||||
${{
|
||||
fromJson(
|
||||
format(
|
||||
'[
|
||||
["{0}"],
|
||||
["x86_64", "x86_64-nonfree", "aarch64", "aarch64-nonfree", "riscv64"]
|
||||
["x86_64", "x86_64-nonfree", "aarch64", "aarch64-nonfree", "riscv64", "raspberrypi"]
|
||||
]',
|
||||
github.event.inputs.platform || 'ALL'
|
||||
)
|
||||
@@ -189,17 +224,32 @@ jobs:
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY" # Pre-cached tool cache (Go, Node, etc.)
|
||||
if: ${{ github.event.inputs.runner != 'fast' }}
|
||||
|
||||
# BuildJet runners lack /opt/hostedtoolcache, which setup-qemu expects
|
||||
- name: Ensure hostedtoolcache exists
|
||||
run: sudo mkdir -p /opt/hostedtoolcache && sudo chown $USER:$USER /opt/hostedtoolcache
|
||||
|
||||
- name: Set up docker QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y qemu-user-static
|
||||
wget https://deb.debian.org/debian/pool/main/d/debspawn/debspawn_0.6.2-1_all.deb
|
||||
sha256sum ./debspawn_0.6.2-1_all.deb | grep 37ef27458cb1e35e8bce4d4f639b06b4b3866fc0b9191ec6b9bd157afd06a817
|
||||
sudo apt-get install -y ./debspawn_0.6.2-1_all.deb
|
||||
|
||||
- name: Configure debspawn
|
||||
run: |
|
||||
sudo mkdir -p /etc/debspawn/
|
||||
echo "AllowUnsafePermissions=true" | sudo tee /etc/debspawn/global.toml
|
||||
sudo mkdir -p /var/tmp/debspawn
|
||||
|
||||
- run: sudo mount -t tmpfs tmpfs /var/tmp/debspawn
|
||||
if: ${{ github.event.inputs.runner == 'fast' && (matrix.platform == 'x86_64' || matrix.platform == 'x86_64-nonfree') }}
|
||||
|
||||
- name: Download compiled artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -212,19 +262,22 @@ jobs:
|
||||
run: |
|
||||
mkdir -p web/node_modules
|
||||
mkdir -p web/dist/raw
|
||||
mkdir -p core/bindings
|
||||
mkdir -p core/startos/bindings
|
||||
mkdir -p sdk/base/lib/osBindings
|
||||
mkdir -p container-runtime/node_modules
|
||||
mkdir -p container-runtime/dist
|
||||
mkdir -p container-runtime/dist/node_modules
|
||||
mkdir -p core/startos/bindings
|
||||
mkdir -p sdk/dist
|
||||
mkdir -p sdk/baseDist
|
||||
mkdir -p patch-db/client/node_modules
|
||||
mkdir -p patch-db/client/dist
|
||||
mkdir -p web/.angular
|
||||
mkdir -p web/dist/raw/ui
|
||||
mkdir -p web/dist/raw/install-wizard
|
||||
mkdir -p web/dist/raw/setup-wizard
|
||||
mkdir -p web/dist/static/ui
|
||||
mkdir -p web/dist/static/install-wizard
|
||||
mkdir -p web/dist/static/setup-wizard
|
||||
PLATFORM=${{ matrix.platform }} make -t compiled-${{ env.ARCH }}.tar
|
||||
|
||||
@@ -254,3 +307,40 @@ jobs:
|
||||
name: ${{ matrix.platform }}.img
|
||||
path: results/*.img
|
||||
if: ${{ matrix.platform == 'raspberrypi' }}
|
||||
|
||||
- name: Upload OTA to registry
|
||||
run: >-
|
||||
PLATFORM=${{ matrix.platform }} make upload-ota TARGET="${{
|
||||
fromJson('{
|
||||
"alpha": "alpha-registry-x.start9.com",
|
||||
"beta": "beta-registry.start9.com",
|
||||
}')[github.event.inputs.deploy]
|
||||
}}" KEY="${{
|
||||
fromJson(
|
||||
format('{{
|
||||
"alpha": "{0}",
|
||||
"beta": "{1}",
|
||||
}}', secrets.ALPHA_INDEX_KEY, secrets.BETA_INDEX_KEY)
|
||||
)[github.event.inputs.deploy]
|
||||
}}"
|
||||
if: ${{ github.event.inputs.deploy != '' && github.event.inputs.deploy != 'NONE' }}
|
||||
|
||||
index:
|
||||
if: ${{ github.event.inputs.deploy != '' && github.event.inputs.deploy != 'NONE' }}
|
||||
needs: [image]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: >-
|
||||
curl "https://${{
|
||||
fromJson('{
|
||||
"alpha": "alpha-registry-x.start9.com",
|
||||
"beta": "beta-registry.start9.com",
|
||||
}')[github.event.inputs.deploy]
|
||||
}}:8443/resync.cgi?key=${{
|
||||
fromJson(
|
||||
format('{{
|
||||
"alpha": "{0}",
|
||||
"beta": "{1}",
|
||||
}}', secrets.ALPHA_INDEX_KEY, secrets.BETA_INDEX_KEY)
|
||||
)[github.event.inputs.deploy]
|
||||
}}"
|
||||
|
||||
13
.github/workflows/test.yaml
vendored
13
.github/workflows/test.yaml
vendored
@@ -10,10 +10,6 @@ on:
|
||||
- master
|
||||
- next/*
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODEJS_VERSION: "24.11.0"
|
||||
ENVIRONMENT: dev-unstable
|
||||
@@ -21,18 +17,15 @@ env:
|
||||
jobs:
|
||||
test:
|
||||
name: Run Automated Tests
|
||||
if: github.event.pull_request.draft != true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: ./.github/actions/setup-build
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
nodejs-version: ${{ env.NODEJS_VERSION }}
|
||||
free-space: "false"
|
||||
setup-docker: "false"
|
||||
setup-sccache: "false"
|
||||
node-version: ${{ env.NODEJS_VERSION }}
|
||||
|
||||
- name: Build And Run Tests
|
||||
run: make test
|
||||
|
||||
24
.gitignore
vendored
24
.gitignore
vendored
@@ -1,22 +1,28 @@
|
||||
.DS_Store
|
||||
.idea
|
||||
*.img
|
||||
*.img.gz
|
||||
*.img.xz
|
||||
*.zip
|
||||
/*.img
|
||||
/*.img.gz
|
||||
/*.img.xz
|
||||
/*-raspios-bullseye-arm64-lite.img
|
||||
/*-raspios-bullseye-arm64-lite.zip
|
||||
/product_key.txt
|
||||
/*_product_key.txt
|
||||
.vscode/settings.json
|
||||
deploy_web.sh
|
||||
deploy_web.sh
|
||||
secrets.db
|
||||
.vscode/
|
||||
/build/env/*.txt
|
||||
*.deb
|
||||
/cargo-deps/**/*
|
||||
/PLATFORM.txt
|
||||
/ENVIRONMENT.txt
|
||||
/GIT_HASH.txt
|
||||
/VERSION.txt
|
||||
/*.deb
|
||||
/target
|
||||
*.squashfs
|
||||
/*.squashfs
|
||||
/results
|
||||
/dpkg-workdir
|
||||
/compiled.tar
|
||||
/compiled-*.tar
|
||||
/build/lib/firmware
|
||||
tmp
|
||||
/firmware
|
||||
/tmp
|
||||
169
Makefile
169
Makefile
@@ -1,44 +1,45 @@
|
||||
ls-files = $(shell git ls-files --cached --others --exclude-standard $1)
|
||||
PROFILE = release
|
||||
|
||||
PLATFORM_FILE := $(shell ./build/env/check-platform.sh)
|
||||
ENVIRONMENT_FILE := $(shell ./build/env/check-environment.sh)
|
||||
GIT_HASH_FILE := $(shell ./build/env/check-git-hash.sh)
|
||||
VERSION_FILE := $(shell ./build/env/check-version.sh)
|
||||
BASENAME := $(shell PROJECT=startos ./build/env/basename.sh)
|
||||
PLATFORM := $(shell if [ -f $(PLATFORM_FILE) ]; then cat $(PLATFORM_FILE); else echo unknown; fi)
|
||||
PLATFORM_FILE := $(shell ./check-platform.sh)
|
||||
ENVIRONMENT_FILE := $(shell ./check-environment.sh)
|
||||
GIT_HASH_FILE := $(shell ./check-git-hash.sh)
|
||||
VERSION_FILE := $(shell ./check-version.sh)
|
||||
BASENAME := $(shell PROJECT=startos ./basename.sh)
|
||||
PLATFORM := $(shell if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi)
|
||||
ARCH := $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo aarch64; else echo $(PLATFORM) | sed 's/-nonfree$$//g'; fi)
|
||||
RUST_ARCH := $(shell if [ "$(ARCH)" = "riscv64" ]; then echo riscv64gc; else echo $(ARCH); fi)
|
||||
REGISTRY_BASENAME := $(shell PROJECT=start-registry PLATFORM=$(ARCH) ./build/env/basename.sh)
|
||||
TUNNEL_BASENAME := $(shell PROJECT=start-tunnel PLATFORM=$(ARCH) ./build/env/basename.sh)
|
||||
REGISTRY_BASENAME := $(shell PROJECT=start-registry PLATFORM=$(ARCH) ./basename.sh)
|
||||
TUNNEL_BASENAME := $(shell PROJECT=start-tunnel PLATFORM=$(ARCH) ./basename.sh)
|
||||
IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi)
|
||||
WEB_UIS := web/dist/raw/ui/index.html web/dist/raw/setup-wizard/index.html
|
||||
COMPRESSED_WEB_UIS := web/dist/static/ui/index.html web/dist/static/setup-wizard/index.html
|
||||
FIRMWARE_ROMS := build/lib/firmware/$(PLATFORM) $(shell jq --raw-output '.[] | select(.platform[] | contains("$(PLATFORM)")) | "./build/lib/firmware/$(PLATFORM)/" + .id + ".rom.gz"' build/lib/firmware.json)
|
||||
BUILD_SRC := $(call ls-files, build/lib) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS)
|
||||
IMAGE_RECIPE_SRC := $(call ls-files, build/image-recipe/)
|
||||
STARTD_SRC := core/startd.service $(BUILD_SRC)
|
||||
WEB_UIS := web/dist/raw/ui/index.html web/dist/raw/setup-wizard/index.html web/dist/raw/install-wizard/index.html
|
||||
COMPRESSED_WEB_UIS := web/dist/static/ui/index.html web/dist/static/setup-wizard/index.html web/dist/static/install-wizard/index.html
|
||||
FIRMWARE_ROMS := ./firmware/$(PLATFORM) $(shell jq --raw-output '.[] | select(.platform[] | contains("$(PLATFORM)")) | "./firmware/$(PLATFORM)/" + .id + ".rom.gz"' build/lib/firmware.json)
|
||||
BUILD_SRC := $(call ls-files, build) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS)
|
||||
IMAGE_RECIPE_SRC := $(call ls-files, image-recipe/)
|
||||
STARTD_SRC := core/startos/startd.service $(BUILD_SRC)
|
||||
CORE_SRC := $(call ls-files, core) $(shell git ls-files --recurse-submodules patch-db) $(GIT_HASH_FILE)
|
||||
WEB_SHARED_SRC := $(call ls-files, web/projects/shared) $(call ls-files, web/projects/marketplace) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist/index.js sdk/baseDist/package.json web/patchdb-ui-seed.json sdk/dist/package.json
|
||||
WEB_UI_SRC := $(call ls-files, web/projects/ui)
|
||||
WEB_SETUP_WIZARD_SRC := $(call ls-files, web/projects/setup-wizard)
|
||||
WEB_INSTALL_WIZARD_SRC := $(call ls-files, web/projects/install-wizard)
|
||||
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/start-container container-runtime/rootfs.$(ARCH).squashfs
|
||||
STARTOS_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) $(COMPILED_TARGETS) target/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs $(PLATFORM_FILE) \
|
||||
STARTOS_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) $(COMPILED_TARGETS) cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs $(PLATFORM_FILE) \
|
||||
$(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then \
|
||||
echo target/aarch64-unknown-linux-musl/release/pi-beep; \
|
||||
echo cargo-deps/aarch64-unknown-linux-musl/release/pi-beep; \
|
||||
fi) \
|
||||
$(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then \
|
||||
echo target/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph; \
|
||||
echo cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph; \
|
||||
fi') \
|
||||
$(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)console($$|-) ]]; then \
|
||||
echo target/$(RUST_ARCH)-unknown-linux-musl/release/tokio-console; \
|
||||
echo cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/tokio-console; \
|
||||
fi')
|
||||
REGISTRY_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox core/start-registryd.service
|
||||
TUNNEL_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox core/start-tunneld.service
|
||||
REGISTRY_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox core/startos/start-registryd.service
|
||||
TUNNEL_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox core/startos/start-tunneld.service
|
||||
|
||||
ifeq ($(REMOTE),)
|
||||
mkdir = mkdir -p $1
|
||||
@@ -72,7 +73,7 @@ metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
||||
|
||||
clean:
|
||||
rm -rf core/target
|
||||
rm -rf core/bindings
|
||||
rm -rf core/startos/bindings
|
||||
rm -rf web/.angular
|
||||
rm -f web/config.json
|
||||
rm -rf web/node_modules
|
||||
@@ -80,7 +81,7 @@ clean:
|
||||
rm -rf patch-db/client/node_modules
|
||||
rm -rf patch-db/client/dist
|
||||
rm -rf patch-db/target
|
||||
rm -rf target
|
||||
rm -rf cargo-deps
|
||||
rm -rf dpkg-workdir
|
||||
rm -rf image-recipe/deb
|
||||
rm -rf results
|
||||
@@ -88,8 +89,14 @@ clean:
|
||||
rm -rf container-runtime/dist
|
||||
rm -rf container-runtime/node_modules
|
||||
rm -f container-runtime/*.squashfs
|
||||
if [ -d container-runtime/tmp/combined ] && mountpoint container-runtime/tmp/combined; then sudo umount container-runtime/tmp/combined; fi
|
||||
if [ -d container-runtime/tmp/lower ] && mountpoint container-runtime/tmp/lower; then sudo umount container-runtime/tmp/lower; fi
|
||||
rm -rf container-runtime/tmp
|
||||
(cd sdk && make clean)
|
||||
rm -f env/*.txt
|
||||
rm -f ENVIRONMENT.txt
|
||||
rm -f PLATFORM.txt
|
||||
rm -f GIT_HASH.txt
|
||||
rm -f VERSION.txt
|
||||
|
||||
format:
|
||||
cd core && cargo +nightly fmt
|
||||
@@ -106,10 +113,10 @@ test-container-runtime: container-runtime/node_modules/.package-lock.json $(call
|
||||
cd container-runtime && npm test
|
||||
|
||||
install-cli: $(GIT_HASH_FILE)
|
||||
./core/build/build-cli.sh --install
|
||||
./core/build-cli.sh --install
|
||||
|
||||
cli: $(GIT_HASH_FILE)
|
||||
./core/build/build-cli.sh
|
||||
./core/build-cli.sh
|
||||
|
||||
registry: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox
|
||||
|
||||
@@ -120,49 +127,49 @@ install-registry: $(REGISTRY_TARGETS)
|
||||
$(call ln,/usr/bin/start-registrybox,$(DESTDIR)/usr/bin/start-registry)
|
||||
|
||||
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
||||
$(call cp,core/start-registryd.service,$(DESTDIR)/lib/systemd/system/start-registryd.service)
|
||||
$(call cp,core/startos/start-registryd.service,$(DESTDIR)/lib/systemd/system/start-registryd.service)
|
||||
|
||||
core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build/build-registrybox.sh
|
||||
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-registrybox.sh
|
||||
|
||||
tunnel: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox
|
||||
|
||||
install-tunnel: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox core/start-tunneld.service
|
||||
install-tunnel: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox core/startos/start-tunneld.service
|
||||
$(call mkdir,$(DESTDIR)/usr/bin)
|
||||
$(call cp,core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox,$(DESTDIR)/usr/bin/start-tunnelbox)
|
||||
$(call ln,/usr/bin/start-tunnelbox,$(DESTDIR)/usr/bin/start-tunneld)
|
||||
$(call ln,/usr/bin/start-tunnelbox,$(DESTDIR)/usr/bin/start-tunnel)
|
||||
|
||||
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
||||
$(call cp,core/start-tunneld.service,$(DESTDIR)/lib/systemd/system/start-tunneld.service)
|
||||
$(call cp,core/startos/start-tunneld.service,$(DESTDIR)/lib/systemd/system/start-tunneld.service)
|
||||
|
||||
$(call mkdir,$(DESTDIR)/usr/lib/startos/scripts)
|
||||
$(call cp,build/lib/scripts/forward-port,$(DESTDIR)/usr/lib/startos/scripts/forward-port)
|
||||
|
||||
core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox: $(CORE_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) web/dist/static/start-tunnel/index.html
|
||||
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build/build-tunnelbox.sh
|
||||
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-tunnelbox.sh
|
||||
|
||||
deb: results/$(BASENAME).deb
|
||||
|
||||
results/$(BASENAME).deb: debian/dpkg-build.sh $(call ls-files,debian/startos) $(STARTOS_TARGETS)
|
||||
PLATFORM=$(PLATFORM) REQUIRES=debian ./build/os-compat/run-compat.sh ./debian/dpkg-build.sh
|
||||
results/$(BASENAME).deb: dpkg-build.sh $(call ls-files,debian/startos) $(STARTOS_TARGETS)
|
||||
PLATFORM=$(PLATFORM) REQUIRES=debian ./build/os-compat/run-compat.sh ./dpkg-build.sh
|
||||
|
||||
registry-deb: results/$(REGISTRY_BASENAME).deb
|
||||
|
||||
results/$(REGISTRY_BASENAME).deb: debian/dpkg-build.sh $(call ls-files,debian/start-registry) $(REGISTRY_TARGETS)
|
||||
PROJECT=start-registry PLATFORM=$(ARCH) REQUIRES=debian ./build/os-compat/run-compat.sh ./debian/dpkg-build.sh
|
||||
results/$(REGISTRY_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-registry) $(REGISTRY_TARGETS)
|
||||
PROJECT=start-registry PLATFORM=$(ARCH) REQUIRES=debian ./build/os-compat/run-compat.sh ./dpkg-build.sh
|
||||
|
||||
tunnel-deb: results/$(TUNNEL_BASENAME).deb
|
||||
|
||||
results/$(TUNNEL_BASENAME).deb: debian/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 ./debian/dpkg-build.sh
|
||||
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)
|
||||
|
||||
squashfs: results/$(BASENAME).squashfs
|
||||
|
||||
results/$(BASENAME).$(IMAGE_TYPE) results/$(BASENAME).squashfs: $(IMAGE_RECIPE_SRC) results/$(BASENAME).deb
|
||||
ARCH=$(ARCH) ./build/image-recipe/run-local-build.sh "results/$(BASENAME).deb"
|
||||
./image-recipe/run-local-build.sh "results/$(BASENAME).deb"
|
||||
|
||||
# For creating os images. DO NOT USE
|
||||
install: $(STARTOS_TARGETS)
|
||||
@@ -171,18 +178,18 @@ install: $(STARTOS_TARGETS)
|
||||
$(call cp,core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox,$(DESTDIR)/usr/bin/startbox)
|
||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/startd)
|
||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-cli)
|
||||
if [ "$(PLATFORM)" = "raspberrypi" ]; then $(call cp,target/aarch64-unknown-linux-musl/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi
|
||||
if [ "$(PLATFORM)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-musl/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi
|
||||
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then \
|
||||
$(call cp,target/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph,$(DESTDIR)/usr/bin/flamegraph); \
|
||||
$(call cp,cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph,$(DESTDIR)/usr/bin/flamegraph); \
|
||||
fi
|
||||
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)console($$|-) ]]'; then \
|
||||
$(call cp,target/$(RUST_ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); \
|
||||
$(call cp,cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); \
|
||||
fi
|
||||
$(call cp,target/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs,$(DESTDIR)/usr/bin/startos-backup-fs)
|
||||
$(call cp,cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs,$(DESTDIR)/usr/bin/startos-backup-fs)
|
||||
$(call ln,/usr/bin/startos-backup-fs,$(DESTDIR)/usr/sbin/mount.backup-fs)
|
||||
|
||||
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
||||
$(call cp,core/startd.service,$(DESTDIR)/lib/systemd/system/startd.service)
|
||||
$(call cp,core/startos/startd.service,$(DESTDIR)/lib/systemd/system/startd.service)
|
||||
|
||||
$(call mkdir,$(DESTDIR)/usr/lib)
|
||||
$(call rm,$(DESTDIR)/usr/lib/startos)
|
||||
@@ -190,16 +197,18 @@ install: $(STARTOS_TARGETS)
|
||||
$(call mkdir,$(DESTDIR)/usr/lib/startos/container-runtime)
|
||||
$(call cp,container-runtime/rootfs.$(ARCH).squashfs,$(DESTDIR)/usr/lib/startos/container-runtime/rootfs.squashfs)
|
||||
|
||||
$(call cp,build/env/PLATFORM.txt,$(DESTDIR)/usr/lib/startos/PLATFORM.txt)
|
||||
$(call cp,build/env/ENVIRONMENT.txt,$(DESTDIR)/usr/lib/startos/ENVIRONMENT.txt)
|
||||
$(call cp,build/env/GIT_HASH.txt,$(DESTDIR)/usr/lib/startos/GIT_HASH.txt)
|
||||
$(call cp,build/env/VERSION.txt,$(DESTDIR)/usr/lib/startos/VERSION.txt)
|
||||
$(call cp,PLATFORM.txt,$(DESTDIR)/usr/lib/startos/PLATFORM.txt)
|
||||
$(call cp,ENVIRONMENT.txt,$(DESTDIR)/usr/lib/startos/ENVIRONMENT.txt)
|
||||
$(call cp,GIT_HASH.txt,$(DESTDIR)/usr/lib/startos/GIT_HASH.txt)
|
||||
$(call cp,VERSION.txt,$(DESTDIR)/usr/lib/startos/VERSION.txt)
|
||||
|
||||
$(call cp,firmware/$(PLATFORM),$(DESTDIR)/usr/lib/startos/firmware)
|
||||
|
||||
update-overlay: $(STARTOS_TARGETS)
|
||||
@echo "\033[33m!!! THIS WILL ONLY REFLASH YOUR DEVICE IN MEMORY !!!\033[0m"
|
||||
@echo "\033[33mALL CHANGES WILL BE REVERTED IF YOU RESTART THE DEVICE\033[0m"
|
||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||
@if [ "`ssh $(REMOTE) 'cat /usr/lib/startos/VERSION.txt'`" != "`cat $(VERSION_FILE)`" ]; then >&2 echo "StartOS requires migrations: update-overlay is unavailable." && false; fi
|
||||
@if [ "`ssh $(REMOTE) 'cat /usr/lib/startos/VERSION.txt'`" != "`cat ./VERSION.txt`" ]; then >&2 echo "StartOS requires migrations: update-overlay is unavailable." && false; fi
|
||||
$(call ssh,"sudo systemctl stop startd")
|
||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) PLATFORM=$(PLATFORM)
|
||||
$(call ssh,"sudo systemctl start startd")
|
||||
@@ -257,7 +266,7 @@ emulate-reflash: $(STARTOS_TARGETS)
|
||||
$(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync "apt-get install -y $(shell cat ./build/lib/depends)"')
|
||||
|
||||
upload-ota: results/$(BASENAME).squashfs
|
||||
TARGET=$(TARGET) KEY=$(KEY) ./build/upload-ota.sh
|
||||
TARGET=$(TARGET) KEY=$(KEY) ./upload-ota.sh
|
||||
|
||||
container-runtime/debian.$(ARCH).squashfs: ./container-runtime/download-base-image.sh
|
||||
ARCH=$(ARCH) ./container-runtime/download-base-image.sh
|
||||
@@ -270,16 +279,16 @@ container-runtime/node_modules/.package-lock.json: container-runtime/package-loc
|
||||
npm --prefix container-runtime ci
|
||||
touch container-runtime/node_modules/.package-lock.json
|
||||
|
||||
ts-bindings: core/bindings/index.ts
|
||||
ts-bindings: core/startos/bindings/index.ts
|
||||
mkdir -p sdk/base/lib/osBindings
|
||||
rsync -ac --delete core/bindings/ sdk/base/lib/osBindings/
|
||||
rsync -ac --delete core/startos/bindings/ sdk/base/lib/osBindings/
|
||||
|
||||
core/bindings/index.ts: $(call ls-files, core) $(ENVIRONMENT_FILE)
|
||||
rm -rf core/bindings
|
||||
./core/build/build-ts.sh
|
||||
ls core/bindings/*.ts | sed 's/core\/bindings\/\([^.]*\)\.ts/export { \1 } from ".\/\1";/g' | grep -v '"./index"' | tee core/bindings/index.ts
|
||||
npm --prefix sdk exec -- prettier --config ./sdk/base/package.json -w ./core/bindings/*.ts
|
||||
touch core/bindings/index.ts
|
||||
core/startos/bindings/index.ts: $(call ls-files, core) $(ENVIRONMENT_FILE)
|
||||
rm -rf core/startos/bindings
|
||||
./core/build-ts.sh
|
||||
ls core/startos/bindings/*.ts | sed 's/core\/startos\/bindings\/\([^.]*\)\.ts/export { \1 } from ".\/\1";/g' | grep -v '"./index"' | tee core/startos/bindings/index.ts
|
||||
npm --prefix sdk exec -- prettier --config ./sdk/base/package.json -w ./core/startos/bindings/*.ts
|
||||
touch core/startos/bindings/index.ts
|
||||
|
||||
sdk/dist/package.json sdk/baseDist/package.json: $(call ls-files, sdk) sdk/base/lib/osBindings/index.ts
|
||||
(cd sdk && make bundle)
|
||||
@@ -294,21 +303,21 @@ 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/update-image-local.sh container-runtime/deb-install.sh container-runtime/dist/index.js container-runtime/dist/node_modules/.package-lock.json core/target/$(RUST_ARCH)-unknown-linux-musl/release/start-container
|
||||
ARCH=$(ARCH) ./container-runtime/update-image-local.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
|
||||
|
||||
$(FIRMWARE_ROMS): build/lib/firmware.json ./build/download-firmware.sh $(PLATFORM_FILE)
|
||||
./build/download-firmware.sh $(PLATFORM)
|
||||
$(FIRMWARE_ROMS): build/lib/firmware.json download-firmware.sh $(PLATFORM_FILE)
|
||||
./download-firmware.sh $(PLATFORM)
|
||||
|
||||
core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox: $(CORE_SRC) $(COMPRESSED_WEB_UIS) web/patchdb-ui-seed.json $(ENVIRONMENT_FILE)
|
||||
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build/build-startbox.sh
|
||||
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/start-container: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||
ARCH=$(ARCH) ./core/build/build-start-container.sh
|
||||
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
|
||||
@@ -324,27 +333,27 @@ web/.angular/.updated: patch-db/client/dist/index.js sdk/baseDist/package.json w
|
||||
mkdir -p web/.angular
|
||||
touch web/.angular/.updated
|
||||
|
||||
web/.i18n-checked: $(WEB_SHARED_SRC) $(WEB_UI_SRC) $(WEB_SETUP_WIZARD_SRC) $(WEB_START_TUNNEL_SRC)
|
||||
npm --prefix web run check:i18n
|
||||
touch web/.i18n-checked
|
||||
|
||||
web/dist/raw/ui/index.html: $(WEB_UI_SRC) $(WEB_SHARED_SRC) web/.angular/.updated web/.i18n-checked
|
||||
web/dist/raw/ui/index.html: $(WEB_UI_SRC) $(WEB_SHARED_SRC) web/.angular/.updated
|
||||
npm --prefix web run build:ui
|
||||
touch web/dist/raw/ui/index.html
|
||||
|
||||
web/dist/raw/setup-wizard/index.html: $(WEB_SETUP_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular/.updated web/.i18n-checked
|
||||
web/dist/raw/setup-wizard/index.html: $(WEB_SETUP_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular/.updated
|
||||
npm --prefix web run build:setup
|
||||
touch web/dist/raw/setup-wizard/index.html
|
||||
|
||||
web/dist/raw/start-tunnel/index.html: $(WEB_START_TUNNEL_SRC) $(WEB_SHARED_SRC) web/.angular/.updated web/.i18n-checked
|
||||
web/dist/raw/install-wizard/index.html: $(WEB_INSTALL_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular/.updated
|
||||
npm --prefix web run build:install
|
||||
touch web/dist/raw/install-wizard/index.html
|
||||
|
||||
web/dist/raw/start-tunnel/index.html: $(WEB_START_TUNNEL_SRC) $(WEB_SHARED_SRC) web/.angular/.updated
|
||||
npm --prefix web run build:tunnel
|
||||
touch web/dist/raw/start-tunnel/index.html
|
||||
|
||||
web/dist/static/%/index.html: web/dist/raw/%/index.html
|
||||
./web/compress-uis.sh $*
|
||||
./compress-uis.sh $*
|
||||
|
||||
web/config.json: $(GIT_HASH_FILE) $(ENVIRONMENT_FILE) web/config-sample.json web/update-config.sh
|
||||
./web/update-config.sh
|
||||
web/config.json: $(GIT_HASH_FILE) web/config-sample.json
|
||||
jq '.useMocks = false' web/config-sample.json | jq '.gitHash = "$(shell cat GIT_HASH.txt)"' > web/config.json
|
||||
|
||||
patch-db/client/node_modules/.package-lock.json: patch-db/client/package.json
|
||||
npm --prefix patch-db/client ci
|
||||
@@ -365,17 +374,17 @@ uis: $(WEB_UIS)
|
||||
# this is a convenience step to build the UI
|
||||
ui: web/dist/raw/ui
|
||||
|
||||
target/aarch64-unknown-linux-musl/release/pi-beep: ./build/build-cargo-dep.sh
|
||||
ARCH=aarch64 ./build/build-cargo-dep.sh pi-beep
|
||||
cargo-deps/aarch64-unknown-linux-musl/release/pi-beep: ./build-cargo-dep.sh
|
||||
ARCH=aarch64 ./build-cargo-dep.sh pi-beep
|
||||
|
||||
target/$(RUST_ARCH)-unknown-linux-musl/release/tokio-console: ./build/build-cargo-dep.sh
|
||||
ARCH=$(ARCH) ./build/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 $@
|
||||
|
||||
target/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs: ./build/build-cargo-dep.sh
|
||||
ARCH=$(ARCH) ./build/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 $@
|
||||
|
||||
target/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph: ./build/build-cargo-dep.sh
|
||||
ARCH=$(ARCH) ./build/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 $@
|
||||
|
||||
95
START-TUNNEL.md
Normal file
95
START-TUNNEL.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# StartTunnel
|
||||
|
||||
A self-hosted WireGuard VPN optimized for creating VLANs and reverse tunneling to personal servers.
|
||||
|
||||
You can think of StartTunnel as "virtual router in the cloud".
|
||||
|
||||
Use it for private remote access to self-hosted services running on a personal server, or to expose self-hosted services to the public Internet without revealing the host server's IP address.
|
||||
|
||||
## Features
|
||||
|
||||
- **Create Subnets**: Each subnet creates a private, virtual local area network (VLAN), similar to the LAN created by a home router.
|
||||
|
||||
- **Add Devices**: When you add a device (server, phone, laptop) to a subnet, it receives a LAN IP address on that subnet as well as a unique WireGuard config that must be copied, downloaded, or scanned into the device.
|
||||
|
||||
- **Forward Ports**: Forwarding a port creates a "reverse tunnel", exposing a specific port on a specific device to the public Internet.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Rent a low cost VPS. For most use cases, the cheapest option should be enough.
|
||||
|
||||
- It must have a dedicated public IP address.
|
||||
- For compute (CPU), memory (RAM), and storage (disk), choose the minimum spec.
|
||||
- For transfer (bandwidth), it depends on (1) your use case and (2) your home Internet's _upload_ speed. Even if you intend to serve large files or stream content from your server, there is no reason to pay for speeds that exceed your home Internet's upload speed.
|
||||
|
||||
1. Provision the VPS with the latest version of Debian.
|
||||
|
||||
1. Access the VPS via SSH.
|
||||
|
||||
1. Run the StartTunnel install script:
|
||||
|
||||
curl -fsSL https://start9labs.github.io/start-tunnel | sh
|
||||
|
||||
1. [Initialize the web interface](#web-interface) (recommended)
|
||||
|
||||
## Updating
|
||||
|
||||
Simply re-run the install command:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://start9labs.github.io/start-tunnel | sh
|
||||
```
|
||||
|
||||
## CLI
|
||||
|
||||
By default, StartTunnel is managed via the `start-tunnel` command line interface, which is self-documented.
|
||||
|
||||
```
|
||||
start-tunnel --help
|
||||
```
|
||||
|
||||
## Web Interface
|
||||
|
||||
Enable the web interface (recommended in most cases) to access your StartTunnel from the browser or via API.
|
||||
|
||||
1. Initialize the web interface.
|
||||
|
||||
start-tunnel web init
|
||||
|
||||
1. If your VPS has multiple public IP addresses, you will be prompted to select the IP address at which to host the web interface.
|
||||
|
||||
1. When prompted, enter the port at which to host the web interface. The default is 8443, and we recommend using it. If you change the default, choose an uncommon port to avoid future conflicts.
|
||||
|
||||
1. To access your StartTunnel web interface securely over HTTPS, you need an SSL certificate. When prompted, select whether to autogenerate a certificate or provide your own. _This is only for accessing your StartTunnel web interface_.
|
||||
|
||||
1. You will receive a success message with 3 pieces of information:
|
||||
|
||||
- **<https://IP:port>**: the URL where you can reach your personal web interface.
|
||||
- **Password**: an autogenerated password for your interface. If you lose/forget it, you can reset it using the start-tunnel CLI.
|
||||
- **Root Certificate Authority**: the Root CA of your StartTunnel instance.
|
||||
|
||||
1. If you autogenerated your SSL certificate, visiting the `https://IP:port` URL in the browser will warn you that the website is insecure. This is expected. You have two options for getting past this warning:
|
||||
- option 1 (recommended): [Trust your StartTunnel Root CA on your connecting device](#trusting-your-starttunnel-root-ca).
|
||||
- Option 2: bypass the warning in the browser, creating a one-time security exception.
|
||||
|
||||
### Trusting your StartTunnel Root CA
|
||||
|
||||
1. Copy the contents of your Root CA (starting with -----BEGIN CERTIFICATE----- and ending with -----END CERTIFICATE-----).
|
||||
|
||||
2. Open a text editor:
|
||||
|
||||
- Linux: gedit, nano, or any editor
|
||||
- Mac: TextEdit
|
||||
- Windows: Notepad
|
||||
|
||||
3. Paste the contents of your Root CA.
|
||||
|
||||
4. Save the file with a `.crt` extension (e.g. `start-tunnel.crt`) (make sure it saves as plain text, not rich text).
|
||||
|
||||
5. Trust the Root CA on your client device(s):
|
||||
|
||||
- [Linux](https://staging.docs.start9.com/device-guides/linux/ca.html)
|
||||
- [Mac](https://staging.docs.start9.com/device-guides/mac/ca.html)
|
||||
- [Windows](https://staging.docs.start9.com/device-guides/windows/ca.html)
|
||||
- [Android/Graphene](https://staging.docs.start9.com/device-guides/android/ca.html)
|
||||
- [iOS](https://staging.docs.start9.com/device-guides/ios/ca.html)
|
||||
@@ -10,7 +10,7 @@ When bumping from version `X.Y.Z-alpha.N` to `X.Y.Z-alpha.N+1`, you need to upda
|
||||
|
||||
### 1. Core Rust Crate Version
|
||||
|
||||
**File: `core/Cargo.toml`**
|
||||
**File: `core/startos/Cargo.toml`**
|
||||
|
||||
Update the version string (line ~18):
|
||||
|
||||
@@ -31,7 +31,7 @@ This will update the version in `Cargo.lock` automatically.
|
||||
|
||||
### 2. Create New Version Migration Module
|
||||
|
||||
**File: `core/src/version/vX_Y_Z_alpha_N+1.rs`**
|
||||
**File: `core/startos/src/version/vX_Y_Z_alpha_N+1.rs`**
|
||||
|
||||
Create a new version file by copying the previous version and updating:
|
||||
|
||||
@@ -79,7 +79,7 @@ impl VersionT for Version {
|
||||
|
||||
### 3. Update Version Module Registry
|
||||
|
||||
**File: `core/src/version/mod.rs`**
|
||||
**File: `core/startos/src/version/mod.rs`**
|
||||
|
||||
Make changes in **5 locations**:
|
||||
|
||||
@@ -176,9 +176,9 @@ This pattern helps you quickly find all the places that need updating in the nex
|
||||
|
||||
## Summary Checklist
|
||||
|
||||
- [ ] Update `core/Cargo.toml` version
|
||||
- [ ] Create new `core/src/version/vX_Y_Z_alpha_N+1.rs` file
|
||||
- [ ] Update `core/src/version/mod.rs` in 5 locations
|
||||
- [ ] 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
|
||||
|
||||
29
build-cargo-dep.sh
Executable file
29
build-cargo-dep.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
shopt -s expand_aliases
|
||||
|
||||
if [ "$0" != "./build-cargo-dep.sh" ]; then
|
||||
>&2 echo "Must be run from start-os directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$ARCH" ]; then
|
||||
ARCH=$(uname -m)
|
||||
fi
|
||||
|
||||
RUST_ARCH="$ARCH"
|
||||
if [ "$ARCH" = "riscv64" ]; then
|
||||
RUST_ARCH="riscv64gc"
|
||||
fi
|
||||
|
||||
mkdir -p cargo-deps
|
||||
|
||||
source core/builder-alias.sh
|
||||
|
||||
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,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||
|
||||
set -e
|
||||
shopt -s expand_aliases
|
||||
|
||||
if [ -z "$ARCH" ]; then
|
||||
ARCH=$(uname -m)
|
||||
fi
|
||||
|
||||
RUST_ARCH="$ARCH"
|
||||
if [ "$ARCH" = "riscv64" ]; then
|
||||
RUST_ARCH="riscv64gc"
|
||||
fi
|
||||
|
||||
mkdir -p target
|
||||
|
||||
source core/build/builder-alias.sh
|
||||
|
||||
RUSTFLAGS="-C target-feature=+crt-static"
|
||||
|
||||
rust-zig-builder cargo-zigbuild install $* --target-dir /workdir/target/ --target=$RUST_ARCH-unknown-linux-musl
|
||||
if [ "$(ls -nd "target/$RUST_ARCH-unknown-linux-musl/release/${!#}" | awk '{ print $3 }')" != "$UID" ]; then
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID target && chown -R $UID:$UID /usr/local/cargo"
|
||||
fi
|
||||
@@ -3,7 +3,6 @@ avahi-utils
|
||||
b3sum
|
||||
bash-completion
|
||||
beep
|
||||
binfmt-support
|
||||
bmon
|
||||
btrfs-progs
|
||||
ca-certificates
|
||||
@@ -16,7 +15,6 @@ dnsutils
|
||||
dosfstools
|
||||
e2fsprogs
|
||||
ecryptfs-utils
|
||||
equivs
|
||||
exfatprogs
|
||||
flashrom
|
||||
fuse3
|
||||
@@ -46,7 +44,6 @@ openssh-server
|
||||
podman
|
||||
psmisc
|
||||
qemu-guest-agent
|
||||
qemu-user-static
|
||||
rfkill
|
||||
rsync
|
||||
samba-common-bin
|
||||
|
||||
@@ -9,9 +9,6 @@ FEATURES+=("${ARCH}")
|
||||
if [ "$ARCH" != "$PLATFORM" ]; then
|
||||
FEATURES+=("${PLATFORM}")
|
||||
fi
|
||||
if [[ "$PLATFORM" =~ -nonfree$ ]]; then
|
||||
FEATURES+=("nonfree")
|
||||
fi
|
||||
|
||||
feature_file_checker='
|
||||
/^#/ { next }
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
+ firmware-amd-graphics
|
||||
+ firmware-atheros
|
||||
+ firmware-brcm80211
|
||||
+ firmware-iwlwifi
|
||||
+ firmware-libertas
|
||||
+ firmware-misc-nonfree
|
||||
+ firmware-realtek
|
||||
+ nvidia-container-toolkit
|
||||
# + nvidia-driver
|
||||
# + nvidia-kernel-dkms
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
|
||||
|
||||
BASEDIR="$(pwd -P)"
|
||||
|
||||
SUITE=trixie
|
||||
|
||||
USE_TTY=
|
||||
if tty -s; then
|
||||
USE_TTY="-it"
|
||||
fi
|
||||
|
||||
dockerfile_hash=$(sha256sum ${BASEDIR}/build/image-recipe/Dockerfile | head -c 7)
|
||||
|
||||
docker_img_name="start9/build-iso:${SUITE}-${dockerfile_hash}"
|
||||
|
||||
platform=linux/${ARCH}
|
||||
case $ARCH in
|
||||
x86_64)
|
||||
platform=linux/amd64;;
|
||||
aarch64)
|
||||
platform=linux/arm64;;
|
||||
esac
|
||||
|
||||
if ! docker run --rm --platform=$platform "${docker_img_name}" true 2> /dev/null; then
|
||||
docker buildx build --load --platform=$platform --build-arg=SUITE=${SUITE} -t "${docker_img_name}" ./build/image-recipe
|
||||
fi
|
||||
|
||||
docker run $USE_TTY --rm --platform=$platform --privileged -v "$(pwd)/build/image-recipe:/root/image-recipe" -v "$(pwd)/results:/root/results" \
|
||||
-e IB_SUITE="$SUITE" \
|
||||
-e IB_UID="$UID" \
|
||||
-e IB_INCLUDE \
|
||||
"${docker_img_name}" /root/image-recipe/build.sh $@
|
||||
@@ -1,51 +0,0 @@
|
||||
desktop-image: "../splash.png"
|
||||
title-color: "#ffffff"
|
||||
title-font: "Unifont Regular 16"
|
||||
title-text: "StartOS Boot Menu with GRUB"
|
||||
message-font: "Unifont Regular 16"
|
||||
terminal-font: "Unifont Regular 16"
|
||||
|
||||
#help bar at the bottom
|
||||
+ label {
|
||||
top = 100%-50
|
||||
left = 0
|
||||
width = 100%
|
||||
height = 20
|
||||
text = "@KEYMAP_SHORT@"
|
||||
align = "center"
|
||||
color = "#ffffff"
|
||||
font = "Unifont Regular 16"
|
||||
}
|
||||
|
||||
#boot menu
|
||||
+ boot_menu {
|
||||
left = 10%
|
||||
width = 80%
|
||||
top = 52%
|
||||
height = 48%-80
|
||||
item_color = "#a8a8a8"
|
||||
item_font = "Unifont Regular 16"
|
||||
selected_item_color= "#ffffff"
|
||||
selected_item_font = "Unifont Regular 16"
|
||||
item_height = 16
|
||||
item_padding = 0
|
||||
item_spacing = 4
|
||||
icon_width = 0
|
||||
icon_heigh = 0
|
||||
item_icon_space = 0
|
||||
}
|
||||
|
||||
#progress bar
|
||||
+ progress_bar {
|
||||
id = "__timeout__"
|
||||
left = 15%
|
||||
top = 100%-80
|
||||
height = 16
|
||||
width = 70%
|
||||
font = "Unifont Regular 16"
|
||||
text_color = "#000000"
|
||||
fg_color = "#ffffff"
|
||||
bg_color = "#a8a8a8"
|
||||
border_color = "#ffffff"
|
||||
text = "@TIMEOUT_NOTIFICATION_LONG@"
|
||||
}
|
||||
@@ -4,7 +4,7 @@ parse_essential_db_info() {
|
||||
DB_DUMP="/tmp/startos_db.json"
|
||||
|
||||
if command -v start-cli >/dev/null 2>&1; then
|
||||
timeout 30 start-cli db dump > "$DB_DUMP" 2>/dev/null || return 1
|
||||
start-cli db dump > "$DB_DUMP" 2>/dev/null || return 1
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -63,7 +63,7 @@ mount --bind /proc /media/startos/next/proc
|
||||
mount --bind /boot /media/startos/next/boot
|
||||
mount --bind /media/startos/root /media/startos/next/media/startos/root
|
||||
|
||||
if mountpoint /sys/firmware/efi/efivars 2>&1 > /dev/null; then
|
||||
if mountpoint /sys/firmware/efi/efivars 2> /dev/null; then
|
||||
mount --bind /sys/firmware/efi/efivars /media/startos/next/sys/firmware/efi/efivars
|
||||
fi
|
||||
|
||||
@@ -75,7 +75,7 @@ else
|
||||
CHROOT_RES=$?
|
||||
fi
|
||||
|
||||
if mountpoint /media/startos/next/sys/firmware/efi/efivars 2>&1 > /dev/null; then
|
||||
if mountpoint /media/startos/next/sys/firmware/efi/efivars 2> /dev/null; then
|
||||
umount /media/startos/next/sys/firmware/efi/efivars
|
||||
fi
|
||||
|
||||
|
||||
@@ -35,20 +35,16 @@ if [ "$UNDO" = 1 ]; then
|
||||
exit $err
|
||||
fi
|
||||
|
||||
# DNAT: rewrite destination for incoming packets (external traffic)
|
||||
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"
|
||||
|
||||
# DNAT: rewrite destination for locally-originated packets (hairpin from host itself)
|
||||
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"
|
||||
|
||||
# MASQUERADE: rewrite source for all forwarded traffic to the destination
|
||||
# This ensures responses are routed back through the host regardless of source IP
|
||||
iptables -t nat -A ${NAME}_POSTROUTING -d "$dip" -p tcp --dport "$dport" -j MASQUERADE
|
||||
iptables -t nat -A ${NAME}_POSTROUTING -d "$dip" -p udp --dport "$dport" -j MASQUERADE
|
||||
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
|
||||
|
||||
# Allow new connections to be forwarded to the destination
|
||||
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
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
export DEBCONF_NONINTERACTIVE_SEEN=true
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
|
||||
(
|
||||
set -e
|
||||
cd $TMP_DIR
|
||||
|
||||
cat > control.equivs
|
||||
equivs-build control.equivs
|
||||
apt-get install -y ./*.deb < /dev/null
|
||||
)
|
||||
|
||||
rm -rf $TMP_DIR
|
||||
|
||||
echo Install complete. >&2
|
||||
exit 0
|
||||
@@ -29,13 +29,10 @@ if [ -z "$needed" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MARGIN=${MARGIN:-1073741824}
|
||||
target=$((needed + MARGIN))
|
||||
|
||||
if [ -h /media/startos/config/current.rootfs ] && [ -e /media/startos/config/current.rootfs ]; then
|
||||
echo 'Pruning...'
|
||||
current="$(readlink -f /media/startos/config/current.rootfs)"
|
||||
while [[ "$(df -B1 --output=avail --sync /media/startos/images | tail -n1)" -lt "$target" ]]; do
|
||||
while [[ "$(df -B1 --output=avail --sync /media/startos/images | tail -n1)" -lt "$needed" ]]; do
|
||||
to_prune="$(ls -t1 /media/startos/images/*.rootfs /media/startos/images/*.squashfs 2> /dev/null | grep -v "$current" | tail -n1)"
|
||||
if [ -e "$to_prune" ]; then
|
||||
echo " Pruning $to_prune"
|
||||
|
||||
@@ -50,12 +50,12 @@ mount --bind /proc /media/startos/next/proc
|
||||
mount --bind /boot /media/startos/next/boot
|
||||
mount --bind /media/startos/root /media/startos/next/media/startos/root
|
||||
|
||||
if mountpoint /boot/efi 2>&1 > /dev/null; then
|
||||
if mountpoint /boot/efi 2> /dev/null; then
|
||||
mkdir -p /media/startos/next/boot/efi
|
||||
mount --bind /boot/efi /media/startos/next/boot/efi
|
||||
fi
|
||||
|
||||
if mountpoint /sys/firmware/efi/efivars 2>&1 > /dev/null; then
|
||||
if mountpoint /sys/firmware/efi/efivars 2> /dev/null; then
|
||||
mount --bind /sys/firmware/efi/efivars /media/startos/next/sys/firmware/efi/efivars
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:trixie
|
||||
FROM debian:forky
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
@@ -12,14 +12,35 @@ RUN apt-get update && \
|
||||
jq \
|
||||
gzip \
|
||||
brotli \
|
||||
qemu-user-static \
|
||||
binfmt-support \
|
||||
squashfs-tools \
|
||||
git \
|
||||
debspawn \
|
||||
rsync \
|
||||
b3sum \
|
||||
fuse-overlayfs \
|
||||
sudo \
|
||||
systemd \
|
||||
systemd-container \
|
||||
systemd-sysv \
|
||||
dbus \
|
||||
dbus-user-session \
|
||||
nodejs
|
||||
|
||||
RUN systemctl mask \
|
||||
systemd-firstboot.service \
|
||||
systemd-udevd.service \
|
||||
getty@tty1.service \
|
||||
console-getty.service
|
||||
|
||||
RUN git config --global --add safe.directory /root/start-os
|
||||
|
||||
RUN mkdir -p /etc/debspawn && \
|
||||
echo "AllowUnsafePermissions=true" > /etc/debspawn/global.toml
|
||||
|
||||
RUN mkdir -p /root/start-os
|
||||
WORKDIR /root/start-os
|
||||
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
ENTRYPOINT [ "/docker-entrypoint.sh" ]
|
||||
3
build/os-compat/docker-entrypoint.sh
Executable file
3
build/os-compat/docker-entrypoint.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
exec /lib/systemd/systemd --unit=multi-user.target --show-status=false --log-target=journal
|
||||
@@ -1,30 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
pwd=$(pwd)
|
||||
if [ "$FORCE_COMPAT" = 1 ] || ( [ "$REQUIRES" = "linux" ] && [ "$(uname -s)" != "Linux" ] ) || ( [ "$REQUIRES" = "debian" ] && ! which dpkg > /dev/null ) || ( [ "$REQUIRES" = "qemu" ] && ! which qemu-$ARCH > /dev/null ); then
|
||||
project_pwd="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)/"
|
||||
pwd="$(pwd)/"
|
||||
if ! [[ "$pwd" = "$project_pwd"* ]]; then
|
||||
>&2 echo "Must be run from start-os project dir"
|
||||
exit 1
|
||||
fi
|
||||
rel_pwd="${pwd#"$project_pwd"}"
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
|
||||
|
||||
set -e
|
||||
|
||||
rel_pwd="${pwd#"$(pwd)"}"
|
||||
|
||||
COMPAT_ARCH=$(uname -m)
|
||||
|
||||
platform=linux/$COMPAT_ARCH
|
||||
|
||||
case $COMPAT_ARCH in
|
||||
x86_64)
|
||||
platform=linux/amd64;;
|
||||
aarch64)
|
||||
platform=linux/arm64;;
|
||||
esac
|
||||
|
||||
if [ "$FORCE_COMPAT" = 1 ] || ( [ "$REQUIRES" = "linux" ] && [ "$(uname -s)" != "Linux" ] ) || ( [ "$REQUIRES" = "debian" ] && ! which dpkg > /dev/null ); then
|
||||
SYSTEMD_TTY="-P"
|
||||
USE_TTY=
|
||||
if tty -s; then
|
||||
USE_TTY="-it"
|
||||
SYSTEMD_TTY="-t"
|
||||
fi
|
||||
|
||||
docker run $USE_TTY --platform=$platform -eARCH -eENVIRONMENT -ePLATFORM -eGIT_BRANCH_AS_HASH -ePROJECT -eDEPENDS -eCONFLICTS -w "/root/start-os${rel_pwd}" --rm -v "$(pwd):/root/start-os" start9/build-env $@
|
||||
docker run -d --rm --name os-compat --privileged --security-opt apparmor=unconfined -v "${project_pwd}:/root/start-os" -v /lib/modules:/lib/modules:ro start9/build-env
|
||||
while ! docker exec os-compat systemctl is-active --quiet multi-user.target 2> /dev/null; do sleep .5; done
|
||||
docker exec -eARCH -eENVIRONMENT -ePLATFORM -eGIT_BRANCH_AS_HASH -ePROJECT -eDEPENDS -eCONFLICTS $USE_TTY -w "/root/start-os${rel_pwd}" os-compat $@
|
||||
code=$?
|
||||
docker stop os-compat > /dev/null
|
||||
exit $code
|
||||
else
|
||||
exec $@
|
||||
fi
|
||||
fi
|
||||
87
build/raspberrypi/make-image.sh
Executable file
87
build/raspberrypi/make-image.sh
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
function partition_for () {
|
||||
if [[ "$1" =~ [0-9]+$ ]]; then
|
||||
echo "$1p$2"
|
||||
else
|
||||
echo "$1$2"
|
||||
fi
|
||||
}
|
||||
|
||||
VERSION=$(cat VERSION.txt)
|
||||
ENVIRONMENT=$(cat ENVIRONMENT.txt)
|
||||
GIT_HASH=$(cat GIT_HASH.txt | head -c 7)
|
||||
DATE=$(date +%Y%m%d)
|
||||
|
||||
ROOT_PART_END=7217792
|
||||
|
||||
VERSION_FULL="$VERSION-$GIT_HASH"
|
||||
|
||||
if [ -n "$ENVIRONMENT" ]; then
|
||||
VERSION_FULL="$VERSION_FULL~$ENVIRONMENT"
|
||||
fi
|
||||
|
||||
TARGET_NAME=startos-${VERSION_FULL}-${DATE}_raspberrypi.img
|
||||
TARGET_SIZE=$[($ROOT_PART_END+1)*512]
|
||||
|
||||
rm -f $TARGET_NAME
|
||||
truncate -s $TARGET_SIZE $TARGET_NAME
|
||||
(
|
||||
echo o
|
||||
echo x
|
||||
echo i
|
||||
echo "0xcb15ae4d"
|
||||
echo r
|
||||
echo n
|
||||
echo p
|
||||
echo 1
|
||||
echo 2048
|
||||
echo 526335
|
||||
echo t
|
||||
echo c
|
||||
echo n
|
||||
echo p
|
||||
echo 2
|
||||
echo 526336
|
||||
echo $ROOT_PART_END
|
||||
echo a
|
||||
echo 1
|
||||
echo w
|
||||
) | fdisk $TARGET_NAME
|
||||
OUTPUT_DEVICE=$(sudo losetup --show -fP $TARGET_NAME)
|
||||
sudo mkfs.ext4 `partition_for ${OUTPUT_DEVICE} 2`
|
||||
sudo mkfs.vfat `partition_for ${OUTPUT_DEVICE} 1`
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
|
||||
sudo mount `partition_for ${OUTPUT_DEVICE} 2` $TMPDIR
|
||||
sudo mkdir $TMPDIR/boot
|
||||
sudo mount `partition_for ${OUTPUT_DEVICE} 1` $TMPDIR/boot
|
||||
sudo unsquashfs -f -d $TMPDIR startos.raspberrypi.squashfs
|
||||
REAL_GIT_HASH=$(cat $TMPDIR/usr/lib/startos/GIT_HASH.txt)
|
||||
REAL_VERSION=$(cat $TMPDIR/usr/lib/startos/VERSION.txt)
|
||||
REAL_ENVIRONMENT=$(cat $TMPDIR/usr/lib/startos/ENVIRONMENT.txt)
|
||||
sudo sed -i 's| boot=startos| init=/usr/lib/startos/scripts/init_resize\.sh|' $TMPDIR/boot/cmdline.txt
|
||||
sudo cp ./build/raspberrypi/fstab $TMPDIR/etc/
|
||||
sudo cp ./build/raspberrypi/init_resize.sh $TMPDIR/usr/lib/startos/scripts/init_resize.sh
|
||||
sudo umount $TMPDIR/boot
|
||||
sudo umount $TMPDIR
|
||||
sudo losetup -d $OUTPUT_DEVICE
|
||||
|
||||
if [ "$ALLOW_VERSION_MISMATCH" != 1 ]; then
|
||||
if [ "$(cat GIT_HASH.txt)" != "$REAL_GIT_HASH" ]; then
|
||||
>&2 echo "startos.raspberrypi.squashfs GIT_HASH.txt mismatch"
|
||||
>&2 echo "expected $REAL_GIT_HASH (dpkg) found $(cat GIT_HASH.txt) (repo)"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(cat VERSION.txt)" != "$REAL_VERSION" ]; then
|
||||
>&2 echo "startos.raspberrypi.squashfs VERSION.txt mismatch"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(cat ENVIRONMENT.txt)" != "$REAL_ENVIRONMENT" ]; then
|
||||
>&2 echo "startos.raspberrypi.squashfs ENVIRONMENT.txt mismatch"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
@@ -1,142 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
>&2 echo '$VERSION required'
|
||||
exit 2
|
||||
fi
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$SKIP_DL" != "1" ]; then
|
||||
if [ "$SKIP_CLEAN" != "1" ]; then
|
||||
rm -rf ~/Downloads/v$VERSION
|
||||
mkdir ~/Downloads/v$VERSION
|
||||
cd ~/Downloads/v$VERSION
|
||||
fi
|
||||
|
||||
if [ -n "$RUN_ID" ]; then
|
||||
for arch in aarch64 aarch64-nonfree riscv64 x86_64 x86_64-nonfree; do
|
||||
while ! gh run download -R Start9Labs/start-os $RUN_ID -n $arch.squashfs -D $(pwd); do sleep 1; done
|
||||
done
|
||||
for arch in aarch64 aarch64-nonfree riscv64 x86_64 x86_64-nonfree; do
|
||||
while ! gh run download -R Start9Labs/start-os $RUN_ID -n $arch.iso -D $(pwd); do sleep 1; done
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -n "$ST_RUN_ID" ]; then
|
||||
for arch in aarch64 riscv64 x86_64; do
|
||||
while ! gh run download -R Start9Labs/start-os $ST_RUN_ID -n start-tunnel_$arch.deb -D $(pwd); do sleep 1; done
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -n "$CLI_RUN_ID" ]; then
|
||||
for arch in aarch64 riscv64 x86_64; do
|
||||
for os in linux macos; do
|
||||
pair=${arch}-${os}
|
||||
if [ "${pair}" = "riscv64-linux" ]; then
|
||||
target=riscv64gc-unknown-linux-musl
|
||||
elif [ "${pair}" = "riscv64-macos" ]; then
|
||||
continue
|
||||
elif [ "${os}" = "linux" ]; then
|
||||
target="${arch}-unknown-linux-musl"
|
||||
elif [ "${os}" = "macos" ]; then
|
||||
target="${arch}-apple-darwin"
|
||||
fi
|
||||
while ! gh run download -R Start9Labs/start-os $CLI_RUN_ID -n start-cli_$target -D $(pwd); do sleep 1; done
|
||||
mv start-cli "start-cli_${pair}"
|
||||
done
|
||||
done
|
||||
fi
|
||||
else
|
||||
cd ~/Downloads/v$VERSION
|
||||
fi
|
||||
|
||||
start-cli --registry=https://alpha-registry-x.start9.com registry os version add $VERSION "v$VERSION" '' ">=0.3.5 <=$VERSION"
|
||||
|
||||
if [ "$SKIP_UL" = "2" ]; then
|
||||
exit 2
|
||||
elif [ "$SKIP_UL" != "1" ]; then
|
||||
for file in *.deb start-cli_*; do
|
||||
gh release upload -R Start9Labs/start-os v$VERSION $file
|
||||
done
|
||||
for file in *.iso *.squashfs; do
|
||||
s3cmd put -P $file s3://startos-images/v$VERSION/$file
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$SKIP_INDEX" != "1" ]; then
|
||||
for arch in aarch64 aarch64-nonfree riscv64 x86_64 x86_64-nonfree; do
|
||||
for file in *_$arch.squashfs *_$arch.iso; do
|
||||
start-cli --registry=https://alpha-registry-x.start9.com registry os asset add --platform=$arch --version=$VERSION $file https://startos-images.nyc3.cdn.digitaloceanspaces.com/v$VERSION/$file
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
for file in *.iso *.squashfs *.deb start-cli_*; do
|
||||
gpg -u 7CFFDA41CA66056A --detach-sign --armor -o "${file}.asc" "$file"
|
||||
done
|
||||
|
||||
gpg --export -a 7CFFDA41CA66056A > dr-bonez.key.asc
|
||||
tar -czvf signatures.tar.gz *.asc
|
||||
|
||||
gh release upload -R Start9Labs/start-os v$VERSION signatures.tar.gz
|
||||
|
||||
cat << EOF
|
||||
# ISO Downloads
|
||||
|
||||
- [x86_64/AMD64](https://startos-images.nyc3.cdn.digitaloceanspaces.com/v$VERSION/$(ls *_x86_64-nonfree.iso))
|
||||
- [x86_64/AMD64-slim (FOSS-only)](https://startos-images.nyc3.cdn.digitaloceanspaces.com/v$VERSION/$(ls *_x86_64.iso) "Without proprietary software or drivers")
|
||||
- [aarch64/ARM64](https://startos-images.nyc3.cdn.digitaloceanspaces.com/v$VERSION/$(ls *_aarch64-nonfree.iso))
|
||||
- [aarch64/ARM64-slim (FOSS-Only)](https://startos-images.nyc3.cdn.digitaloceanspaces.com/v$VERSION/$(ls *_aarch64.iso) "Without proprietary software or drivers")
|
||||
- [RISCV64 (RVA23)](https://startos-images.nyc3.cdn.digitaloceanspaces.com/v$VERSION/$(ls *_riscv64.iso))
|
||||
|
||||
EOF
|
||||
cat << 'EOF'
|
||||
# StartOS Checksums
|
||||
|
||||
## SHA-256
|
||||
```
|
||||
EOF
|
||||
sha256sum *.iso *.squashfs
|
||||
cat << 'EOF'
|
||||
```
|
||||
|
||||
## BLAKE-3
|
||||
```
|
||||
EOF
|
||||
b3sum *.iso *.squashfs
|
||||
cat << 'EOF'
|
||||
```
|
||||
|
||||
# Start-Tunnel Checksums
|
||||
|
||||
## SHA-256
|
||||
```
|
||||
EOF
|
||||
sha256sum start-tunnel*.deb
|
||||
cat << 'EOF'
|
||||
```
|
||||
|
||||
## BLAKE-3
|
||||
```
|
||||
EOF
|
||||
b3sum start-tunnel*.deb
|
||||
cat << 'EOF'
|
||||
```
|
||||
|
||||
# start-cli Checksums
|
||||
|
||||
## SHA-256
|
||||
```
|
||||
EOF
|
||||
sha256sum start-cli_*
|
||||
cat << 'EOF'
|
||||
```
|
||||
|
||||
## BLAKE-3
|
||||
```
|
||||
EOF
|
||||
b3sum start-cli_*
|
||||
cat << 'EOF'
|
||||
```
|
||||
EOF
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
if ! [ -f ./ENVIRONMENT.txt ] || [ "$(cat ./ENVIRONMENT.txt)" != "$ENVIRONMENT" ]; then
|
||||
>&2 echo "Updating ENVIRONMENT.txt to \"$ENVIRONMENT\""
|
||||
echo -n "$ENVIRONMENT" > ./ENVIRONMENT.txt
|
||||
fi
|
||||
|
||||
echo -n ./build/env/ENVIRONMENT.txt
|
||||
echo -n ./ENVIRONMENT.txt
|
||||
@@ -1,7 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
if [ "$GIT_BRANCH_AS_HASH" != 1 ]; then
|
||||
GIT_HASH="$(git rev-parse HEAD)$(if ! git diff-index --quiet HEAD --; then echo '-modified'; fi)"
|
||||
else
|
||||
@@ -13,4 +11,4 @@ if ! [ -f ./GIT_HASH.txt ] || [ "$(cat ./GIT_HASH.txt)" != "$GIT_HASH" ]; then
|
||||
echo -n "$GIT_HASH" > ./GIT_HASH.txt
|
||||
fi
|
||||
|
||||
echo -n ./build/env/GIT_HASH.txt
|
||||
echo -n ./GIT_HASH.txt
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
if ! [ -f ./PLATFORM.txt ] || [ "$(cat ./PLATFORM.txt)" != "$PLATFORM" ] && [ -n "$PLATFORM" ]; then
|
||||
>&2 echo "Updating PLATFORM.txt to \"$PLATFORM\""
|
||||
echo -n "$PLATFORM" > ./PLATFORM.txt
|
||||
fi
|
||||
|
||||
echo -n ./build/env/PLATFORM.txt
|
||||
echo -n ./PLATFORM.txt
|
||||
@@ -1,8 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
FE_VERSION="$(cat ../../web/package.json | grep '"version"' | sed 's/[ \t]*"version":[ \t]*"\([^"]*\)",/\1/')"
|
||||
FE_VERSION="$(cat web/package.json | grep '"version"' | sed 's/[ \t]*"version":[ \t]*"\([^"]*\)",/\1/')"
|
||||
|
||||
# TODO: Validate other version sources - backend/Cargo.toml, backend/src/version/mod.rs
|
||||
|
||||
@@ -12,4 +10,4 @@ if ! [ -f ./VERSION.txt ] || [ "$(cat ./VERSION.txt)" != "$VERSION" ]; then
|
||||
echo -n "$VERSION" > ./VERSION.txt
|
||||
fi
|
||||
|
||||
echo -n ./build/env/VERSION.txt
|
||||
echo -n ./VERSION.txt
|
||||
@@ -4,8 +4,8 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
set -e
|
||||
|
||||
STATIC_DIR=dist/static/$1
|
||||
RAW_DIR=dist/raw/$1
|
||||
STATIC_DIR=web/dist/static/$1
|
||||
RAW_DIR=web/dist/raw/$1
|
||||
|
||||
mkdir -p $STATIC_DIR
|
||||
rm -rf $STATIC_DIR
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
set -e
|
||||
|
||||
mkdir -p /run/systemd/resolve
|
||||
echo "nameserver 8.8.8.8" > /run/systemd/resolve/stub-resolv.conf
|
||||
|
||||
apt-get update
|
||||
apt-get install -y curl rsync qemu-user-static nodejs
|
||||
|
||||
@@ -13,4 +16,7 @@ sed -i '/\(^\|#\)ForwardToSyslog=/c\ForwardToSyslog=no' /etc/systemd/journald.co
|
||||
|
||||
systemctl enable container-runtime.service
|
||||
|
||||
rm -rf /run/systemd
|
||||
|
||||
rm -f /etc/resolv.conf
|
||||
echo "nameserver 10.0.3.1" > /etc/resolv.conf
|
||||
28
container-runtime/mkcontainer.sh
Normal file
28
container-runtime/mkcontainer.sh
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
IMAGE=$1
|
||||
|
||||
if [ -z "$IMAGE" ]; then
|
||||
>&2 echo "usage: $0 <image id>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -d "/media/images/$IMAGE" ]; then
|
||||
>&2 echo "image does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
container=$(mktemp -d)
|
||||
mkdir -p $container/rootfs $container/upper $container/work
|
||||
mount -t overlay -olowerdir=/media/images/$IMAGE,upperdir=$container/upper,workdir=$container/work overlay $container/rootfs
|
||||
|
||||
rootfs=$container/rootfs
|
||||
|
||||
for special in dev sys proc run; do
|
||||
mkdir -p $rootfs/$special
|
||||
mount --bind /$special $rootfs/$special
|
||||
done
|
||||
|
||||
echo $rootfs
|
||||
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.48",
|
||||
"version": "0.4.0-beta.45",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^3.0.0",
|
||||
|
||||
12
container-runtime/rmcontainer.sh
Normal file
12
container-runtime/rmcontainer.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
rootfs=$1
|
||||
if [ -z "$rootfs" ]; then
|
||||
>&2 echo "usage: $0 <container rootfs path>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
umount --recursive $rootfs
|
||||
rm -rf $rootfs/..
|
||||
@@ -178,13 +178,6 @@ export function makeEffects(context: EffectContext): Effects {
|
||||
T.Effects["getInstalledPackages"]
|
||||
>
|
||||
},
|
||||
getServiceManifest(
|
||||
...[options]: Parameters<T.Effects["getServiceManifest"]>
|
||||
) {
|
||||
return rpcRound("get-service-manifest", options) as ReturnType<
|
||||
T.Effects["getServiceManifest"]
|
||||
>
|
||||
},
|
||||
subcontainer: {
|
||||
createFs(options: { imageId: string; name: string }) {
|
||||
return rpcRound("subcontainer.create-fs", options) as ReturnType<
|
||||
@@ -319,7 +312,6 @@ export function makeEffects(context: EffectContext): Effects {
|
||||
}
|
||||
if (context.callbacks?.onLeaveContext)
|
||||
self.onLeaveContext(() => {
|
||||
self.constRetry = undefined
|
||||
self.isInContext = false
|
||||
self.onLeaveContext = () => {
|
||||
console.warn(
|
||||
|
||||
@@ -10,6 +10,7 @@ import { SDKManifest } from "@start9labs/start-sdk/base/lib/types"
|
||||
import { SubContainerRc } from "@start9labs/start-sdk/package/lib/util/SubContainer"
|
||||
|
||||
const EMBASSY_HEALTH_INTERVAL = 15 * 1000
|
||||
const EMBASSY_PROPERTIES_LOOP = 30 * 1000
|
||||
/**
|
||||
* We wanted something to represent what the main loop is doing, and
|
||||
* in this case it used to run the properties, health, and the docker/ js main.
|
||||
|
||||
@@ -50,7 +50,6 @@ import {
|
||||
transformOldConfigToNew,
|
||||
} from "./transformConfigSpec"
|
||||
import { partialDiff } from "@start9labs/start-sdk/base/lib/util"
|
||||
import { Volume } from "@start9labs/start-sdk/package/lib/util/Volume"
|
||||
|
||||
type Optional<A> = A | undefined | null
|
||||
function todo(): never {
|
||||
@@ -62,14 +61,14 @@ export const EMBASSY_JS_LOCATION = "/usr/lib/startos/package/embassy.js"
|
||||
|
||||
const configFile = FileHelper.json(
|
||||
{
|
||||
base: new Volume("embassy"),
|
||||
volumeId: "embassy",
|
||||
subpath: "config.json",
|
||||
},
|
||||
matches.any,
|
||||
)
|
||||
const dependsOnFile = FileHelper.json(
|
||||
{
|
||||
base: new Volume("embassy"),
|
||||
volumeId: "embassy",
|
||||
subpath: "dependsOn.json",
|
||||
},
|
||||
dictionary([string, array(string)]),
|
||||
@@ -288,6 +287,7 @@ function convertProperties(
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_REGISTRY = "https://registry.start9.com"
|
||||
export class SystemForEmbassy implements System {
|
||||
private version: ExtendedVersion
|
||||
currentRunning: MainLoop | undefined
|
||||
@@ -331,10 +331,6 @@ export class SystemForEmbassy implements System {
|
||||
) {
|
||||
this.version.upstream.prerelease = ["alpha"]
|
||||
}
|
||||
|
||||
if (this.manifest.id === "nostr") {
|
||||
this.manifest.id = "nostr-rs-relay"
|
||||
}
|
||||
}
|
||||
|
||||
async init(
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||
|
||||
USE_TTY=
|
||||
if tty -s; then
|
||||
USE_TTY="-it"
|
||||
fi
|
||||
|
||||
DOCKER_PLATFORM=linux/${ARCH}
|
||||
case $ARCH in
|
||||
x86_64)
|
||||
DOCKER_PLATFORM=linux/amd64;;
|
||||
aarch64)
|
||||
DOCKER_PLATFORM=linux/arm64;;
|
||||
esac
|
||||
|
||||
docker run --rm $USE_TTY --platform=$DOCKER_PLATFORM -eARCH --privileged -v "$(pwd):/root/start-os" start9/build-env /root/start-os/container-runtime/update-image.sh
|
||||
if [ "$(ls -nd "rootfs.${ARCH}.squashfs" | awk '{ print $3 }')" != "$UID" ]; then
|
||||
docker run --rm $USE_TTY -v "$(pwd):/root/start-os" start9/build-env chown -R $UID:$UID /root/start-os/container-runtime
|
||||
fi
|
||||
@@ -9,34 +9,56 @@ if [ "$ARCH" = "riscv64" ]; then
|
||||
RUST_ARCH="riscv64gc"
|
||||
fi
|
||||
|
||||
mount -t tmpfs tmpfs /tmp
|
||||
mkdir -p /tmp/lower /tmp/upper /tmp/work /tmp/combined
|
||||
mount -o loop debian.${ARCH}.squashfs /tmp/lower
|
||||
mount -t overlay -olowerdir=/tmp/lower,upperdir=/tmp/upper,workdir=/tmp/work overlay /tmp/combined
|
||||
if mountpoint -q tmp/combined; then sudo umount -l tmp/combined; fi
|
||||
if mountpoint -q tmp/lower; then sudo umount tmp/lower; fi
|
||||
sudo rm -rf tmp
|
||||
mkdir -p tmp/lower tmp/upper tmp/work tmp/combined
|
||||
if which squashfuse > /dev/null; then
|
||||
sudo squashfuse debian.${ARCH}.squashfs tmp/lower
|
||||
else
|
||||
sudo mount debian.${ARCH}.squashfs tmp/lower
|
||||
fi
|
||||
if which fuse-overlayfs > /dev/null; then
|
||||
sudo fuse-overlayfs -olowerdir=tmp/lower,upperdir=tmp/upper,workdir=tmp/work overlay tmp/combined
|
||||
else
|
||||
sudo mount -t overlay -olowerdir=tmp/lower,upperdir=tmp/upper,workdir=tmp/work overlay tmp/combined
|
||||
fi
|
||||
|
||||
mkdir -p /tmp/combined/usr/lib/startos/
|
||||
rsync -a --copy-unsafe-links --info=progress2 dist/ /tmp/combined/usr/lib/startos/init/
|
||||
chown -R 0:0 /tmp/combined/usr/lib/startos/
|
||||
cp container-runtime.service /tmp/combined/lib/systemd/system/container-runtime.service
|
||||
chown 0:0 /tmp/combined/lib/systemd/system/container-runtime.service
|
||||
cp container-runtime-failure.service /tmp/combined/lib/systemd/system/container-runtime-failure.service
|
||||
chown 0:0 /tmp/combined/lib/systemd/system/container-runtime-failure.service
|
||||
cp ../core/target/${RUST_ARCH}-unknown-linux-musl/release/start-container /tmp/combined/usr/bin/start-container
|
||||
echo -e '#!/bin/bash\nexec start-container "$@"' > /tmp/combined/usr/bin/start-cli # TODO: remove
|
||||
chmod +x /tmp/combined/usr/bin/start-cli
|
||||
chown 0:0 /tmp/combined/usr/bin/start-container
|
||||
echo container-runtime | sha256sum | head -c 32 | cat - <(echo) > /tmp/combined/etc/machine-id
|
||||
rm -f /tmp/combined/etc/resolv.conf
|
||||
cp /etc/resolv.conf /tmp/combined/etc/resolv.conf
|
||||
for fs in proc sys dev; do
|
||||
mount --bind /$fs /tmp/combined/$fs
|
||||
done
|
||||
cat deb-install.sh | chroot /tmp/combined /bin/bash
|
||||
for fs in proc sys dev; do
|
||||
umount /tmp/combined/$fs
|
||||
done
|
||||
truncate -s 0 /tmp/combined/etc/machine-id
|
||||
QEMU=
|
||||
if [ "$ARCH" != "$(uname -m)" ]; then
|
||||
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) tmp/combined${QEMU}
|
||||
fi
|
||||
|
||||
sudo mkdir -p tmp/combined/usr/lib/startos/
|
||||
sudo rsync -a --copy-unsafe-links dist/ tmp/combined/usr/lib/startos/init/
|
||||
sudo chown -R 0:0 tmp/combined/usr/lib/startos/
|
||||
sudo cp container-runtime.service tmp/combined/lib/systemd/system/container-runtime.service
|
||||
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime.service
|
||||
sudo 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/start-container tmp/combined/usr/bin/start-container
|
||||
echo -e '#!/bin/bash\nexec start-container "$@"' | sudo tee tmp/combined/usr/bin/start-cli # TODO: remove
|
||||
sudo chmod +x tmp/combined/usr/bin/start-cli
|
||||
sudo chown 0:0 tmp/combined/usr/bin/start-container
|
||||
echo container-runtime | sha256sum | head -c 32 | cat - <(echo) | sudo tee tmp/combined/etc/machine-id
|
||||
cat deb-install.sh | sudo systemd-nspawn --console=pipe -D tmp/combined $QEMU /bin/bash
|
||||
sudo truncate -s 0 tmp/combined/etc/machine-id
|
||||
|
||||
if [ -n "$QEMU" ]; then
|
||||
sudo rm tmp/combined${QEMU}
|
||||
fi
|
||||
|
||||
rm -f rootfs.${ARCH}.squashfs
|
||||
mkdir -p ../build/lib/container-runtime
|
||||
mksquashfs /tmp/combined rootfs.${ARCH}.squashfs
|
||||
sudo mksquashfs tmp/combined rootfs.${ARCH}.squashfs
|
||||
sudo umount tmp/combined
|
||||
sudo umount tmp/lower
|
||||
sudo rm -rf tmp
|
||||
2
core/.gitignore
vendored
2
core/.gitignore
vendored
@@ -8,4 +8,4 @@ secrets.db
|
||||
.env
|
||||
.editorconfig
|
||||
proptest-regressions/**/*
|
||||
/bindings/*
|
||||
/startos/bindings/*
|
||||
1072
core/Cargo.lock
generated
1072
core/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
293
core/Cargo.toml
293
core/Cargo.toml
@@ -1,292 +1,3 @@
|
||||
[package]
|
||||
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
||||
description = "The core of StartOS"
|
||||
documentation = "https://docs.rs/start-os"
|
||||
edition = "2024"
|
||||
keywords = [
|
||||
"bitcoin",
|
||||
"full-node",
|
||||
"lightning",
|
||||
"privacy",
|
||||
"raspberry-pi",
|
||||
"self-hosted",
|
||||
]
|
||||
license = "MIT"
|
||||
name = "start-os"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/Start9Labs/start-os"
|
||||
version = "0.4.0-alpha.19" # VERSION_BUMP
|
||||
[workspace]
|
||||
|
||||
[lib]
|
||||
name = "startos"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "startbox"
|
||||
path = "src/main/startbox.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "start-cli"
|
||||
path = "src/main/start-cli.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "start-container"
|
||||
path = "src/main/start-container.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "registrybox"
|
||||
path = "src/main/registrybox.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tunnelbox"
|
||||
path = "src/main/tunnelbox.rs"
|
||||
|
||||
[features]
|
||||
arti = [
|
||||
"arti-client",
|
||||
"safelog",
|
||||
"tor-cell",
|
||||
"tor-hscrypto",
|
||||
"tor-hsservice",
|
||||
"tor-keymgr",
|
||||
"tor-llcrypto",
|
||||
"tor-proto",
|
||||
"tor-rtcompat",
|
||||
]
|
||||
beta = []
|
||||
console = ["console-subscriber", "tokio/tracing"]
|
||||
default = []
|
||||
dev = []
|
||||
test = []
|
||||
unstable = ["backtrace-on-stack-overflow"]
|
||||
|
||||
[dependencies]
|
||||
aes = { version = "0.7.5", features = ["ctr"] }
|
||||
arti-client = { version = "0.33", features = [
|
||||
"compression",
|
||||
"ephemeral-keystore",
|
||||
"experimental-api",
|
||||
"onion-service-client",
|
||||
"onion-service-service",
|
||||
"rustls",
|
||||
"static",
|
||||
"tokio",
|
||||
], default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
async-acme = { version = "0.6.0", git = "https://github.com/dr-bonez/async-acme.git", features = [
|
||||
"use_rustls",
|
||||
"use_tokio",
|
||||
] }
|
||||
async-compression = { version = "0.4.32", features = [
|
||||
"brotli",
|
||||
"gzip",
|
||||
"tokio",
|
||||
"zstd",
|
||||
] }
|
||||
async-stream = "0.3.5"
|
||||
async-trait = "0.1.74"
|
||||
axum = { version = "0.8.4", features = ["ws", "http2"] }
|
||||
backtrace-on-stack-overflow = { version = "0.3.0", optional = true }
|
||||
base32 = "0.5.0"
|
||||
base64 = "0.22.1"
|
||||
base64ct = "1.6.0"
|
||||
basic-cookies = "0.1.4"
|
||||
blake3 = { version = "1.5.0", features = ["mmap", "rayon"] }
|
||||
bytes = "1"
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
clap = { version = "4.4.12", features = ["string"] }
|
||||
color-eyre = "0.6.2"
|
||||
console = "0.16.2"
|
||||
console-subscriber = { version = "0.5.0", optional = true }
|
||||
const_format = "0.2.34"
|
||||
cookie = "0.18.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 = "3.0.1"
|
||||
ed25519 = { version = "2.2.3", features = ["alloc", "pem", "pkcs8"] }
|
||||
ed25519-dalek = { version = "2.2.0", features = [
|
||||
"digest",
|
||||
"hazmat",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"serde",
|
||||
"zeroize",
|
||||
] }
|
||||
ed25519-dalek-v1 = { package = "ed25519-dalek", version = "1" }
|
||||
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
||||
"serde",
|
||||
] }
|
||||
fd-lock-rs = "0.1.4"
|
||||
form_urlencoded = "1.2.1"
|
||||
futures = "0.3.28"
|
||||
gpt = "4.1.0"
|
||||
hex = "0.4.3"
|
||||
hickory-server = { version = "0.25.2", features = ["resolver"] }
|
||||
hmac = "0.12.1"
|
||||
http = "1.0.0"
|
||||
http-body-util = "0.1"
|
||||
hyper = { version = "1.5", features = ["http1", "http2", "server"] }
|
||||
hyper-util = { version = "0.1.10", features = [
|
||||
"http1",
|
||||
"http2",
|
||||
"server",
|
||||
"server-auto",
|
||||
"server-graceful",
|
||||
"service",
|
||||
"tokio",
|
||||
] }
|
||||
id-pool = { version = "0.2.2", default-features = false, features = [
|
||||
"serde",
|
||||
"u16",
|
||||
] }
|
||||
iddqd = "0.3.14"
|
||||
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.18.3", features = ["tokio"] }
|
||||
inotify = "0.11.0"
|
||||
integer-encoding = { version = "4.0.0", features = ["tokio_async"] }
|
||||
ipnet = { version = "2.8.0", features = ["serde"] }
|
||||
isocountry = "0.3.2"
|
||||
itertools = "0.14.0"
|
||||
jaq-core = "0.10.1"
|
||||
jaq-std = "0.10.0"
|
||||
josekit = "0.10.3"
|
||||
jsonpath_lib = { git = "https://github.com/Start9Labs/jsonpath.git" }
|
||||
lazy_async_pool = "0.3.3"
|
||||
lazy_format = "2.0"
|
||||
lazy_static = "1.4.0"
|
||||
lettre = { version = "0.11.18", default-features = false, features = [
|
||||
"aws-lc-rs",
|
||||
"builder",
|
||||
"hostname",
|
||||
"pool",
|
||||
"rustls-platform-verifier",
|
||||
"smtp-transport",
|
||||
"tokio1-rustls",
|
||||
] }
|
||||
libc = "0.2.149"
|
||||
log = "0.4.20"
|
||||
mbrman = "0.6.0"
|
||||
miette = { version = "7.6.0", features = ["fancy"] }
|
||||
mio = "1"
|
||||
new_mime_guess = "4"
|
||||
nix = { version = "0.30.1", features = [
|
||||
"fs",
|
||||
"hostname",
|
||||
"mount",
|
||||
"net",
|
||||
"process",
|
||||
"sched",
|
||||
"signal",
|
||||
"user",
|
||||
] }
|
||||
nom = "8.0.0"
|
||||
num = "0.4.1"
|
||||
num_cpus = "1.16.0"
|
||||
num_enum = "0.7.0"
|
||||
once_cell = "1.19.0"
|
||||
openssh-keys = "0.6.2"
|
||||
openssl = { version = "0.10.57", features = ["vendored"] }
|
||||
p256 = { version = "0.13.2", features = ["pem"] }
|
||||
patch-db = { version = "*", path = "../patch-db/patch-db", features = [
|
||||
"trace",
|
||||
] }
|
||||
pbkdf2 = "0.12.2"
|
||||
pin-project = "1.1.3"
|
||||
pkcs8 = { version = "0.10.2", features = ["std"] }
|
||||
prettytable-rs = "0.10.0"
|
||||
proptest = "1.3.1"
|
||||
proptest-derive = "0.7.0"
|
||||
qrcode = "0.14.1"
|
||||
r3bl_tui = "0.7.6"
|
||||
rand = "0.9.2"
|
||||
regex = "1.10.2"
|
||||
reqwest = { version = "0.12.25", features = [
|
||||
"json",
|
||||
"socks",
|
||||
"stream",
|
||||
"http2",
|
||||
] }
|
||||
reqwest_cookie_store = "0.9.0"
|
||||
rpassword = "7.2.0"
|
||||
rust-argon2 = "3.0.0"
|
||||
rust-i18n = "3.1.5"
|
||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git" }
|
||||
safelog = { version = "0.4.8", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
semver = { version = "1.0.20", features = ["serde"] }
|
||||
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.9.9+spec-1.0.0" }
|
||||
serde_yaml = { package = "serde_yml", version = "0.0.12" }
|
||||
sha-crypt = "0.5.0"
|
||||
sha2 = "0.10.2"
|
||||
signal-hook = "0.3.17"
|
||||
socket2 = { version = "0.6.0", features = ["all"] }
|
||||
socks5-impl = { version = "0.7.2", features = ["client", "server"] }
|
||||
sqlx = { version = "0.8.6", features = [
|
||||
"postgres",
|
||||
"runtime-tokio-rustls",
|
||||
], default-features = false }
|
||||
sscanf = "0.4.1"
|
||||
ssh-key = { version = "0.6.2", features = ["ed25519"] }
|
||||
tar = "0.4.40"
|
||||
termion = "4.0.5"
|
||||
textwrap = "0.16.1"
|
||||
thiserror = "2.0.12"
|
||||
tokio = { version = "1.38.1", features = ["full"] }
|
||||
tokio-rustls = "0.26.4"
|
||||
tokio-stream = { version = "0.1.14", features = ["io-util", "net", "sync"] }
|
||||
tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" }
|
||||
tokio-tungstenite = { version = "0.26.2", features = ["native-tls", "url"] }
|
||||
tokio-util = { version = "0.7.9", features = ["io"] }
|
||||
tor-cell = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
tor-hscrypto = { version = "0.33", features = [
|
||||
"full",
|
||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
tor-hsservice = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
tor-keymgr = { version = "0.33", features = [
|
||||
"ephemeral-keystore",
|
||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
tor-llcrypto = { version = "0.33", features = [
|
||||
"full",
|
||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
tor-proto = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
tor-rtcompat = { version = "0.33", features = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
torut = "0.2.1"
|
||||
tower-service = "0.3.3"
|
||||
tracing = "0.1.39"
|
||||
tracing-error = "0.2.0"
|
||||
tracing-journald = "0.3.0"
|
||||
tracing-subscriber = { version = "=0.3.19", features = ["env-filter"] }
|
||||
ts-rs = "9.0.1"
|
||||
typed-builder = "0.23.2"
|
||||
url = { version = "2.4.1", features = ["serde"] }
|
||||
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"
|
||||
hashing-serializer = "0.1.1"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
procfs = "0.18.0"
|
||||
pty-process = "0.5.1"
|
||||
|
||||
[profile.test]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.backtrace]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
opt-level = 3
|
||||
members = ["startos"]
|
||||
|
||||
@@ -26,7 +26,7 @@ PROFILE=${PROFILE:-release}
|
||||
if [ "${PROFILE}" = "release" ]; then
|
||||
BUILD_FLAGS="--release"
|
||||
else
|
||||
if [ "$PROFILE" != "debug" ]; then
|
||||
if [ "$PROFILE" != "debug"]; then
|
||||
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||
PROFILE=debug
|
||||
fi
|
||||
@@ -60,7 +60,7 @@ if [ -z "${TARGET:-}" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
cd ../..
|
||||
cd ..
|
||||
FEATURES="$(echo "${ENVIRONMENT:-}" | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
if [[ "${ENVIRONMENT:-}" =~ (^|-)console($|-) ]]; then
|
||||
@@ -30,7 +30,7 @@ if [ "$ARCH" = "riscv64" ]; then
|
||||
RUST_ARCH="riscv64gc"
|
||||
fi
|
||||
|
||||
cd ../..
|
||||
cd ..
|
||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
|
||||
@@ -30,7 +30,7 @@ if [ "$ARCH" = "riscv64" ]; then
|
||||
RUST_ARCH="riscv64gc"
|
||||
fi
|
||||
|
||||
cd ../..
|
||||
cd ..
|
||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
|
||||
@@ -30,7 +30,7 @@ if [ "$ARCH" = "riscv64" ]; then
|
||||
RUST_ARCH="riscv64gc"
|
||||
fi
|
||||
|
||||
cd ../..
|
||||
cd ..
|
||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
|
||||
@@ -30,7 +30,7 @@ if [ "$ARCH" = "riscv64" ]; then
|
||||
RUST_ARCH="riscv64gc"
|
||||
fi
|
||||
|
||||
cd ../..
|
||||
cd ..
|
||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||
@@ -39,6 +39,6 @@ fi
|
||||
echo "FEATURES=\"$FEATURES\""
|
||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||
rust-zig-builder cargo test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features test,$FEATURES --locked 'export_bindings_'
|
||||
if [ "$(ls -nd "core/bindings" | awk '{ print $3 }')" != "$UID" ]; then
|
||||
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID core/bindings && chown -R $UID:$UID /usr/local/cargo"
|
||||
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 /usr/local/cargo"
|
||||
fi
|
||||
@@ -30,7 +30,7 @@ if [ "$ARCH" = "riscv64" ]; then
|
||||
RUST_ARCH="riscv64gc"
|
||||
fi
|
||||
|
||||
cd ../..
|
||||
cd ..
|
||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||
RUSTFLAGS=""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
source ./build/builder-alias.sh
|
||||
source ./builder-alias.sh
|
||||
|
||||
set -ea
|
||||
shopt -s expand_aliases
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
use rust_i18n::t;
|
||||
|
||||
pub fn renamed(old: &str, new: &str) -> ! {
|
||||
eprintln!("{}", t!("bins.deprecated.renamed", old = old, new = new));
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
pub fn removed(name: &str) -> ! {
|
||||
eprintln!("{}", t!("bins.deprecated.removed", name = name));
|
||||
std::process::exit(1)
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::ffi::OsString;
|
||||
use std::path::Path;
|
||||
|
||||
use rust_i18n::t;
|
||||
|
||||
pub mod container_cli;
|
||||
pub mod deprecated;
|
||||
pub mod registry;
|
||||
pub mod start_cli;
|
||||
pub mod start_init;
|
||||
pub mod startd;
|
||||
pub mod tunnel;
|
||||
|
||||
pub fn set_locale_from_env() {
|
||||
let lang = std::env::var("LANG").ok();
|
||||
let lang = lang
|
||||
.as_deref()
|
||||
.map_or("C", |l| l.strip_suffix(".UTF-8").unwrap_or(l));
|
||||
set_locale(lang)
|
||||
}
|
||||
|
||||
pub fn set_locale(lang: &str) {
|
||||
let mut best = None;
|
||||
let prefix = lang.split_inclusive("_").next().unwrap();
|
||||
for l in rust_i18n::available_locales!() {
|
||||
if l == lang {
|
||||
best = Some(l);
|
||||
break;
|
||||
}
|
||||
if best.is_none() && l.starts_with(prefix) {
|
||||
best = Some(l);
|
||||
}
|
||||
}
|
||||
rust_i18n::set_locale(best.unwrap_or(lang));
|
||||
}
|
||||
|
||||
pub fn translate_cli(mut cmd: clap::Command) -> clap::Command {
|
||||
fn translate(s: impl std::fmt::Display) -> String {
|
||||
t!(s.to_string()).into_owned()
|
||||
}
|
||||
if let Some(s) = cmd.get_about() {
|
||||
let s = translate(s);
|
||||
cmd = cmd.about(s);
|
||||
}
|
||||
if let Some(s) = cmd.get_long_about() {
|
||||
let s = translate(s);
|
||||
cmd = cmd.long_about(s);
|
||||
}
|
||||
if let Some(s) = cmd.get_before_help() {
|
||||
let s = translate(s);
|
||||
cmd = cmd.before_help(s);
|
||||
}
|
||||
if let Some(s) = cmd.get_before_long_help() {
|
||||
let s = translate(s);
|
||||
cmd = cmd.before_long_help(s);
|
||||
}
|
||||
if let Some(s) = cmd.get_after_help() {
|
||||
let s = translate(s);
|
||||
cmd = cmd.after_help(s);
|
||||
}
|
||||
if let Some(s) = cmd.get_after_long_help() {
|
||||
let s = translate(s);
|
||||
cmd = cmd.after_long_help(s);
|
||||
}
|
||||
|
||||
let arg_ids = cmd
|
||||
.get_arguments()
|
||||
.map(|a| a.get_id().clone())
|
||||
.collect::<Vec<_>>();
|
||||
for id in arg_ids {
|
||||
cmd = cmd.mut_arg(id, |arg| {
|
||||
let arg = if let Some(s) = arg.get_help() {
|
||||
let s = translate(s);
|
||||
arg.help(s)
|
||||
} else {
|
||||
arg
|
||||
};
|
||||
if let Some(s) = arg.get_long_help() {
|
||||
let s = translate(s);
|
||||
arg.long_help(s)
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
});
|
||||
}
|
||||
for cmd in cmd.get_subcommands_mut() {
|
||||
*cmd = translate_cli(cmd.clone());
|
||||
}
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MultiExecutable {
|
||||
default: Option<&'static str>,
|
||||
bins: BTreeMap<&'static str, fn(VecDeque<OsString>)>,
|
||||
}
|
||||
impl MultiExecutable {
|
||||
pub fn enable_startd(&mut self) -> &mut Self {
|
||||
self.bins.insert("startd", startd::main);
|
||||
self.bins
|
||||
.insert("embassyd", |_| deprecated::renamed("embassyd", "startd"));
|
||||
self.bins
|
||||
.insert("embassy-init", |_| deprecated::removed("embassy-init"));
|
||||
self
|
||||
}
|
||||
pub fn enable_start_cli(&mut self) -> &mut Self {
|
||||
self.bins.insert("start-cli", start_cli::main);
|
||||
self.bins.insert("embassy-cli", |_| {
|
||||
deprecated::renamed("embassy-cli", "start-cli")
|
||||
});
|
||||
self.bins
|
||||
.insert("embassy-sdk", |_| deprecated::removed("embassy-sdk"));
|
||||
self
|
||||
}
|
||||
pub fn enable_start_container(&mut self) -> &mut Self {
|
||||
self.bins.insert("start-container", container_cli::main);
|
||||
self
|
||||
}
|
||||
pub fn enable_start_registryd(&mut self) -> &mut Self {
|
||||
self.bins.insert("start-registryd", registry::main);
|
||||
self
|
||||
}
|
||||
pub fn enable_start_registry(&mut self) -> &mut Self {
|
||||
self.bins.insert("start-registry", registry::cli);
|
||||
self
|
||||
}
|
||||
pub fn enable_start_tunneld(&mut self) -> &mut Self {
|
||||
self.bins.insert("start-tunneld", tunnel::main);
|
||||
self
|
||||
}
|
||||
pub fn enable_start_tunnel(&mut self) -> &mut Self {
|
||||
self.bins.insert("start-tunnel", tunnel::cli);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_default(&mut self, name: &str) -> &mut Self {
|
||||
if let Some((name, _)) = self.bins.get_key_value(name) {
|
||||
self.default = Some(*name);
|
||||
} else {
|
||||
panic!("{}", t!("bins.mod.does-not-exist", name = name));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn select_executable(&self, name: &str) -> Option<fn(VecDeque<OsString>)> {
|
||||
self.bins.get(&name).copied()
|
||||
}
|
||||
|
||||
pub fn execute(&self) {
|
||||
set_locale_from_env();
|
||||
|
||||
let mut popped = Vec::with_capacity(2);
|
||||
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.bins.keys() {
|
||||
println!("{name}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if let Some(x) = self.select_executable(&name) {
|
||||
args.push_front(s);
|
||||
return x(args);
|
||||
}
|
||||
}
|
||||
popped.push(s);
|
||||
}
|
||||
}
|
||||
if let Some(default) = self.default {
|
||||
while let Some(arg) = popped.pop() {
|
||||
args.push_front(arg);
|
||||
}
|
||||
return self.bins[default](args);
|
||||
}
|
||||
let args = std::env::args().collect::<VecDeque<_>>();
|
||||
eprintln!(
|
||||
"{}",
|
||||
t!(
|
||||
"bins.mod.unknown-executable",
|
||||
name = args
|
||||
.get(1)
|
||||
.or_else(|| args.get(0))
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("N/A")
|
||||
)
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
use std::path::Path;
|
||||
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::Error;
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
|
||||
pub async fn is_mountpoint(path: impl AsRef<Path>) -> Result<bool, Error> {
|
||||
let is_mountpoint = tokio::process::Command::new("mountpoint")
|
||||
.arg(path.as_ref())
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.status()
|
||||
.await?;
|
||||
Ok(is_mountpoint.success())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn bind<P0: AsRef<Path>, P1: AsRef<Path>>(
|
||||
src: P0,
|
||||
dst: P1,
|
||||
read_only: bool,
|
||||
) -> Result<(), Error> {
|
||||
tracing::info!(
|
||||
"{}",
|
||||
t!(
|
||||
"disk.mount.binding",
|
||||
src = src.as_ref().display(),
|
||||
dst = dst.as_ref().display()
|
||||
)
|
||||
);
|
||||
if is_mountpoint(&dst).await? {
|
||||
unmount(dst.as_ref(), true).await?;
|
||||
}
|
||||
tokio::fs::create_dir_all(&src).await?;
|
||||
tokio::fs::create_dir_all(&dst).await?;
|
||||
let mut mount_cmd = tokio::process::Command::new("mount");
|
||||
mount_cmd.arg("--bind");
|
||||
if read_only {
|
||||
mount_cmd.arg("-o").arg("ro");
|
||||
}
|
||||
mount_cmd
|
||||
.arg(src.as_ref())
|
||||
.arg(dst.as_ref())
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn unmount<P: AsRef<Path>>(mountpoint: P, lazy: bool) -> Result<(), Error> {
|
||||
tracing::debug!("Unmounting {}.", mountpoint.as_ref().display());
|
||||
let mut cmd = tokio::process::Command::new("umount");
|
||||
if lazy {
|
||||
cmd.arg("-l");
|
||||
}
|
||||
cmd.arg(mountpoint.as_ref())
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unmounts all mountpoints under (and including) the given path, in reverse
|
||||
/// depth order so that nested mounts are unmounted before their parents.
|
||||
#[instrument(skip_all)]
|
||||
pub async fn unmount_all_under<P: AsRef<Path>>(path: P, lazy: bool) -> Result<(), Error> {
|
||||
let path = path.as_ref();
|
||||
let canonical_path = tokio::fs::canonicalize(path)
|
||||
.await
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("canonicalize {path:?}")))?;
|
||||
|
||||
let mounts_content = tokio::fs::read_to_string("/proc/mounts")
|
||||
.await
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, "read /proc/mounts"))?;
|
||||
|
||||
// Collect all mountpoints under our path
|
||||
let mut mountpoints: Vec<&str> = mounts_content
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let mountpoint = line.split_whitespace().nth(1)?;
|
||||
// Check if this mountpoint is under our target path
|
||||
let mp_path = Path::new(mountpoint);
|
||||
if mp_path.starts_with(&canonical_path) {
|
||||
Some(mountpoint)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Sort by path length descending so we unmount deepest first
|
||||
mountpoints.sort_by(|a, b| b.len().cmp(&a.len()));
|
||||
|
||||
for mountpoint in mountpoints {
|
||||
tracing::debug!("Unmounting nested mountpoint: {}", mountpoint);
|
||||
unmount(mountpoint, lazy).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use gpt::GptConfig;
|
||||
use gpt::disk::LogicalBlockSize;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::os_install::partition_for;
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
|
||||
pub async fn partition(
|
||||
disk_path: &Path,
|
||||
capacity: u64,
|
||||
protect: Option<&Path>,
|
||||
use_efi: bool,
|
||||
) -> Result<OsPartitionInfo, Error> {
|
||||
// Guard: cannot protect the whole disk
|
||||
if let Some(p) = protect {
|
||||
if p == disk_path {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Cannot protect the entire disk {}; must specify a partition",
|
||||
disk_path.display()
|
||||
),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let disk_path = disk_path.to_owned();
|
||||
let disk_path_clone = disk_path.clone();
|
||||
let protect = protect.map(|p| p.to_owned());
|
||||
let (efi, data_part) = tokio::task::spawn_blocking(move || {
|
||||
let disk_path = disk_path_clone;
|
||||
|
||||
let protected_partition_info: Option<(u64, u64, PathBuf)> =
|
||||
if let Some(ref protect_path) = protect {
|
||||
let existing_gpt = GptConfig::new()
|
||||
.writable(false)
|
||||
.logical_block_size(LogicalBlockSize::Lb512)
|
||||
.open_from_device(Box::new(
|
||||
std::fs::File::options().read(true).open(&disk_path)?,
|
||||
))?;
|
||||
let info = existing_gpt
|
||||
.partitions()
|
||||
.iter()
|
||||
.find(|(num, _)| partition_for(&disk_path, **num) == *protect_path)
|
||||
.map(|(_, p)| (p.first_lba, p.last_lba, protect_path.clone()));
|
||||
if info.is_none() {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Protected partition {} not found in GPT on {}",
|
||||
protect_path.display(),
|
||||
disk_path.display()
|
||||
),
|
||||
crate::ErrorKind::NotFound,
|
||||
));
|
||||
}
|
||||
info
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut device = Box::new(
|
||||
std::fs::File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&disk_path)?,
|
||||
);
|
||||
|
||||
let mbr = gpt::mbr::ProtectiveMBR::with_lb_size(
|
||||
u32::try_from((capacity / 512) - 1).unwrap_or(0xFF_FF_FF_FF),
|
||||
);
|
||||
mbr.overwrite_lba0(&mut device)?;
|
||||
let mut gpt = GptConfig::new()
|
||||
.writable(true)
|
||||
.logical_block_size(LogicalBlockSize::Lb512)
|
||||
.create_from_device(device, None)?;
|
||||
|
||||
gpt.update_partitions(Default::default())?;
|
||||
|
||||
let efi = if use_efi {
|
||||
gpt.add_partition("efi", 100 * 1024 * 1024, gpt::partition_types::EFI, 0, None)?;
|
||||
true
|
||||
} else {
|
||||
gpt.add_partition(
|
||||
"bios-grub",
|
||||
8 * 1024 * 1024,
|
||||
gpt::partition_types::BIOS,
|
||||
0,
|
||||
None,
|
||||
)?;
|
||||
false
|
||||
};
|
||||
gpt.add_partition(
|
||||
"boot",
|
||||
2 * 1024 * 1024 * 1024,
|
||||
gpt::partition_types::LINUX_FS,
|
||||
0,
|
||||
None,
|
||||
)?;
|
||||
gpt.add_partition(
|
||||
"root",
|
||||
14 * 1024 * 1024 * 1024,
|
||||
match crate::ARCH {
|
||||
"x86_64" => gpt::partition_types::LINUX_ROOT_X64,
|
||||
"aarch64" => gpt::partition_types::LINUX_ROOT_ARM_64,
|
||||
_ => gpt::partition_types::LINUX_FS,
|
||||
},
|
||||
0,
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Check if protected partition would be overwritten by OS partitions
|
||||
if let Some((first_lba, _, ref path)) = protected_partition_info {
|
||||
// Get the actual end sector of the last OS partition (root = partition 3)
|
||||
let os_partitions_end_sector =
|
||||
gpt.partitions().get(&3).map(|p| p.last_lba).unwrap_or(0);
|
||||
if first_lba <= os_partitions_end_sector {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
concat!(
|
||||
"Protected partition {} starts at sector {}",
|
||||
" which would be overwritten by OS partitions ending at sector {}"
|
||||
),
|
||||
path.display(),
|
||||
first_lba,
|
||||
os_partitions_end_sector
|
||||
),
|
||||
crate::ErrorKind::DiskManagement,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let data_part = if let Some((first_lba, last_lba, path)) = protected_partition_info {
|
||||
// Re-create the data partition entry at the same location
|
||||
let length_lba = last_lba - first_lba + 1;
|
||||
let next_id = gpt.partitions().keys().max().map(|k| k + 1).unwrap_or(1);
|
||||
gpt.add_partition_at(
|
||||
"data",
|
||||
next_id,
|
||||
first_lba,
|
||||
length_lba,
|
||||
gpt::partition_types::LINUX_LVM,
|
||||
0,
|
||||
)?;
|
||||
Some(path)
|
||||
} else {
|
||||
gpt.add_partition(
|
||||
"data",
|
||||
gpt.find_free_sectors()
|
||||
.iter()
|
||||
.map(|(_, size)| *size * u64::from(*gpt.logical_block_size()))
|
||||
.max()
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("No free space left on device"),
|
||||
crate::ErrorKind::BlockDevice,
|
||||
)
|
||||
})?,
|
||||
gpt::partition_types::LINUX_LVM,
|
||||
0,
|
||||
None,
|
||||
)?;
|
||||
gpt.partitions()
|
||||
.last_key_value()
|
||||
.map(|(num, _)| partition_for(&disk_path, *num))
|
||||
};
|
||||
|
||||
gpt.write()?;
|
||||
|
||||
Ok::<_, Error>((efi, data_part))
|
||||
})
|
||||
.await
|
||||
.unwrap()?;
|
||||
|
||||
// Re-read partition table and wait for udev to create device nodes
|
||||
Command::new("vgchange")
|
||||
.arg("-an")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await
|
||||
.ok();
|
||||
Command::new("dmsetup")
|
||||
.arg("remove_all")
|
||||
.arg("--force")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await
|
||||
.ok();
|
||||
Command::new("blockdev")
|
||||
.arg("--rereadpt")
|
||||
.arg(&disk_path)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
Command::new("udevadm")
|
||||
.arg("settle")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
|
||||
Ok(OsPartitionInfo {
|
||||
efi: efi.then(|| partition_for(&disk_path, 1)),
|
||||
bios: (!efi).then(|| partition_for(&disk_path, 1)),
|
||||
boot: partition_for(&disk_path, 2),
|
||||
root: partition_for(&disk_path, 3),
|
||||
data: data_part,
|
||||
})
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use mbrman::{CHS, MBR, MBRPartitionEntry};
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::os_install::partition_for;
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
|
||||
pub async fn partition(
|
||||
disk_path: &Path,
|
||||
capacity: u64,
|
||||
protect: Option<&Path>,
|
||||
) -> Result<OsPartitionInfo, Error> {
|
||||
// Guard: cannot protect the whole disk
|
||||
if let Some(p) = protect {
|
||||
if p == disk_path {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Cannot protect the entire disk {}; must specify a partition",
|
||||
disk_path.display()
|
||||
),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let disk_path = disk_path.to_owned();
|
||||
let disk_path_clone = disk_path.clone();
|
||||
let protect = protect.map(|p| p.to_owned());
|
||||
let sectors = (capacity / 512) as u32;
|
||||
let data_part = tokio::task::spawn_blocking(move || {
|
||||
let disk_path = disk_path_clone;
|
||||
|
||||
// If protecting a partition, read its location from the existing MBR
|
||||
let protected_partition_info: Option<(u32, u32, PathBuf)> =
|
||||
if let Some(ref protect_path) = protect {
|
||||
let mut file = std::fs::File::options().read(true).open(&disk_path)?;
|
||||
let existing_mbr = MBR::read_from(&mut file, 512)?;
|
||||
// Find the partition matching the protected path (check partitions 1-4)
|
||||
let info = (1..=4u32)
|
||||
.find(|&idx| partition_for(&disk_path, idx) == *protect_path)
|
||||
.and_then(|idx| {
|
||||
let entry = &existing_mbr[idx as usize];
|
||||
if entry.sectors > 0 {
|
||||
Some((entry.starting_lba, entry.sectors, protect_path.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
if info.is_none() {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Protected partition {} not found in MBR on {}",
|
||||
protect_path.display(),
|
||||
disk_path.display()
|
||||
),
|
||||
crate::ErrorKind::NotFound,
|
||||
));
|
||||
}
|
||||
info
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// MBR partition layout:
|
||||
// Partition 1 (boot): starts at 2048, ends at 4196352 (sectors: 4194304 = 2GB)
|
||||
// Partition 2 (root): starts at 4196352, ends at 33556480 (sectors: 29360128 = 14GB)
|
||||
// OS partitions end at sector 33556480
|
||||
let os_partitions_end_sector: u32 = 33556480;
|
||||
|
||||
// Check if protected partition would be overwritten
|
||||
if let Some((starting_lba, _, ref path)) = protected_partition_info {
|
||||
if starting_lba < os_partitions_end_sector {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
concat!(
|
||||
"Protected partition {} starts at sector {}",
|
||||
" which would be overwritten by OS partitions ending at sector {}"
|
||||
),
|
||||
path.display(),
|
||||
starting_lba,
|
||||
os_partitions_end_sector
|
||||
),
|
||||
crate::ErrorKind::DiskManagement,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut file = std::fs::File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&disk_path)?;
|
||||
let mut mbr = MBR::new_from(&mut file, 512, rand::random())?;
|
||||
|
||||
mbr[1] = MBRPartitionEntry {
|
||||
boot: 0x80,
|
||||
first_chs: CHS::empty(),
|
||||
sys: 0x0b,
|
||||
last_chs: CHS::empty(),
|
||||
starting_lba: 2048,
|
||||
sectors: 4196352 - 2048,
|
||||
};
|
||||
mbr[2] = MBRPartitionEntry {
|
||||
boot: 0,
|
||||
first_chs: CHS::empty(),
|
||||
sys: 0x83,
|
||||
last_chs: CHS::empty(),
|
||||
starting_lba: 4196352,
|
||||
sectors: 33556480 - 4196352,
|
||||
};
|
||||
|
||||
let data_part = if let Some((starting_lba, part_sectors, path)) = protected_partition_info {
|
||||
// Re-create the data partition entry at the same location
|
||||
mbr[3] = MBRPartitionEntry {
|
||||
boot: 0,
|
||||
first_chs: CHS::empty(),
|
||||
sys: 0x8e,
|
||||
last_chs: CHS::empty(),
|
||||
starting_lba,
|
||||
sectors: part_sectors,
|
||||
};
|
||||
Some(path)
|
||||
} else {
|
||||
mbr[3] = MBRPartitionEntry {
|
||||
boot: 0,
|
||||
first_chs: CHS::empty(),
|
||||
sys: 0x8e,
|
||||
last_chs: CHS::empty(),
|
||||
starting_lba: 33556480,
|
||||
sectors: sectors - 33556480,
|
||||
};
|
||||
Some(partition_for(&disk_path, 3))
|
||||
};
|
||||
mbr.write_into(&mut file)?;
|
||||
|
||||
Ok::<_, Error>(data_part)
|
||||
})
|
||||
.await
|
||||
.unwrap()?;
|
||||
|
||||
// Re-read partition table and wait for udev to create device nodes
|
||||
Command::new("vgchange")
|
||||
.arg("-an")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await
|
||||
.ok();
|
||||
Command::new("dmsetup")
|
||||
.arg("remove_all")
|
||||
.arg("--force")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await
|
||||
.ok();
|
||||
Command::new("blockdev")
|
||||
.arg("--rereadpt")
|
||||
.arg(&disk_path)
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
Command::new("udevadm")
|
||||
.arg("settle")
|
||||
.invoke(crate::ErrorKind::DiskManagement)
|
||||
.await?;
|
||||
|
||||
Ok(OsPartitionInfo {
|
||||
efi: None,
|
||||
bios: None,
|
||||
boot: partition_for(&disk_path, 1),
|
||||
root: partition_for(&disk_path, 2),
|
||||
data: data_part,
|
||||
})
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Deref;
|
||||
|
||||
use axum::extract::Request;
|
||||
use axum::response::Response;
|
||||
use exver::{Version, VersionRange};
|
||||
use http::HeaderValue;
|
||||
use imbl_value::InternedString;
|
||||
use patch_db::ModelExt;
|
||||
use rpc_toolkit::yajrc::RpcMethod;
|
||||
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
use url::Url;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::prelude::*;
|
||||
use crate::registry::context::RegistryContext;
|
||||
use crate::registry::os::index::OsVersionInfoMap;
|
||||
use crate::registry::package::get::{
|
||||
GetPackageParams, GetPackageResponse, GetPackageResponseFull, PackageDetailLevel,
|
||||
};
|
||||
use crate::registry::package::index::PackageVersionInfo;
|
||||
use crate::util::VersionString;
|
||||
use crate::util::lshw::LshwDevice;
|
||||
use crate::version::VersionT;
|
||||
|
||||
pub const DEVICE_INFO_HEADER: &str = "X-StartOS-Device-Info";
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DeviceInfo {
|
||||
pub os: OsInfo,
|
||||
pub hardware: Option<HardwareInfo>,
|
||||
}
|
||||
impl DeviceInfo {
|
||||
pub async fn load(ctx: &RpcContext) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
os: OsInfo::from(ctx),
|
||||
hardware: Some(HardwareInfo::load(ctx).await?),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl DeviceInfo {
|
||||
pub fn to_header_value(&self) -> HeaderValue {
|
||||
let mut url: Url = "http://localhost".parse().unwrap();
|
||||
let mut qp = url.query_pairs_mut();
|
||||
qp.append_pair("os.version", &self.os.version.to_string())
|
||||
.append_pair("os.compat", &self.os.compat.to_string())
|
||||
.append_pair("os.platform", &*self.os.platform);
|
||||
if let Some(lang) = self.os.language.as_deref() {
|
||||
qp.append_pair("os.language", lang);
|
||||
}
|
||||
drop(qp);
|
||||
|
||||
HeaderValue::from_str(url.query().unwrap_or_default()).unwrap()
|
||||
}
|
||||
pub fn from_header_value(header: &HeaderValue) -> Result<Self, Error> {
|
||||
let query: BTreeMap<_, _> = form_urlencoded::parse(header.as_bytes()).collect();
|
||||
let has_hw_info = query.keys().any(|k| k.starts_with("hardware."));
|
||||
let version = query
|
||||
.get("os.version")
|
||||
.or_not_found("os.version")?
|
||||
.parse()?;
|
||||
Ok(Self {
|
||||
os: OsInfo {
|
||||
compat: query.get("os.compat").or_not_found("os.compat")?.parse()?,
|
||||
platform: query
|
||||
.get("os.platform")
|
||||
.or_not_found("os.platform")?
|
||||
.deref()
|
||||
.into(),
|
||||
language: query
|
||||
.get("os.language")
|
||||
.map(|v| v.deref())
|
||||
.map(InternedString::intern)
|
||||
.or_else(|| {
|
||||
if version < "0.4.0-alpha.18".parse().ok()? {
|
||||
Some(rust_i18n::locale().deref().into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
version,
|
||||
},
|
||||
hardware: has_hw_info
|
||||
.then(|| {
|
||||
Ok::<_, Error>(HardwareInfo {
|
||||
arch: query
|
||||
.get("hardware.arch")
|
||||
.or_not_found("hardware.arch")?
|
||||
.parse()?,
|
||||
ram: query
|
||||
.get("hardware.ram")
|
||||
.or_not_found("hardware.ram")?
|
||||
.parse()?,
|
||||
devices: None,
|
||||
})
|
||||
})
|
||||
.transpose()?,
|
||||
})
|
||||
}
|
||||
pub fn filter_for_hardware(
|
||||
&self,
|
||||
method: &str,
|
||||
params: Value,
|
||||
res: &mut Value,
|
||||
) -> Result<(), Error> {
|
||||
match method {
|
||||
"package.get" => {
|
||||
let params: Model<GetPackageParams> = ModelExt::from_value(params);
|
||||
|
||||
let other = params.as_other_versions().de()?;
|
||||
if params.as_id().transpose_ref().is_some() {
|
||||
if other.unwrap_or_default() == PackageDetailLevel::Full {
|
||||
self.filter_package_get_full(ModelExt::value_as_mut(res))?;
|
||||
} else {
|
||||
self.filter_package_get(ModelExt::value_as_mut(res))?;
|
||||
}
|
||||
} else {
|
||||
for (_, v) in res.as_object_mut().into_iter().flat_map(|o| o.iter_mut()) {
|
||||
if other.unwrap_or_default() == PackageDetailLevel::Full {
|
||||
self.filter_package_get_full(ModelExt::value_as_mut(v))?;
|
||||
} else {
|
||||
self.filter_package_get(ModelExt::value_as_mut(v))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
"os.version.get" => self.filter_os_version(ModelExt::value_as_mut(res)),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_package_versions(
|
||||
&self,
|
||||
versions: &mut Model<BTreeMap<VersionString, PackageVersionInfo>>,
|
||||
) -> Result<(), Error> {
|
||||
let alpha_17: Version = "0.4.0-alpha.17".parse()?;
|
||||
|
||||
// Filter package versions using for_device
|
||||
versions.retain(|_, info| info.for_device(self))?;
|
||||
|
||||
// Alpha.17 compatibility: add legacy fields
|
||||
if self.os.version <= alpha_17 {
|
||||
for (_, info) in versions.as_entries_mut()? {
|
||||
let v = info.as_value_mut();
|
||||
if let Some(mut tup) = v["s9pks"].get(0).cloned() {
|
||||
v["s9pk"] = tup[1].take();
|
||||
v["hardwareRequirements"] = tup[0].take();
|
||||
v["s9pk"]["url"] = v["s9pk"]["urls"][0].clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn filter_package_get(&self, res: &mut Model<GetPackageResponse>) -> Result<(), Error> {
|
||||
self.filter_package_versions(res.as_best_mut())
|
||||
}
|
||||
|
||||
fn filter_package_get_full(
|
||||
&self,
|
||||
res: &mut Model<GetPackageResponseFull>,
|
||||
) -> Result<(), Error> {
|
||||
self.filter_package_versions(res.as_best_mut())?;
|
||||
self.filter_package_versions(res.as_other_versions_mut())
|
||||
}
|
||||
|
||||
fn filter_os_version(&self, res: &mut Model<OsVersionInfoMap>) -> Result<(), Error> {
|
||||
let alpha_17: Version = "0.4.0-alpha.17".parse()?;
|
||||
|
||||
// Filter OS versions based on source_version compatibility
|
||||
res.retain(|_, info| {
|
||||
let source_version = info.as_source_version().de()?;
|
||||
Ok(self.os.version.satisfies(&source_version))
|
||||
})?;
|
||||
|
||||
// Alpha.17 compatibility: add url field from urls array
|
||||
if self.os.version <= alpha_17 {
|
||||
for (_, info) in res.as_entries_mut()? {
|
||||
let v = info.as_value_mut();
|
||||
for asset_ty in ["iso", "squashfs", "img"] {
|
||||
for (_, asset) in v[asset_ty]
|
||||
.as_object_mut()
|
||||
.into_iter()
|
||||
.flat_map(|o| o.iter_mut())
|
||||
{
|
||||
asset["url"] = asset["urls"][0].clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OsInfo {
|
||||
#[ts(as = "VersionString")]
|
||||
pub version: Version,
|
||||
#[ts(type = "string")]
|
||||
pub compat: VersionRange,
|
||||
pub platform: InternedString,
|
||||
pub language: Option<InternedString>,
|
||||
}
|
||||
impl From<&RpcContext> for OsInfo {
|
||||
fn from(_: &RpcContext) -> Self {
|
||||
Self {
|
||||
version: crate::version::Current::default().semver(),
|
||||
compat: crate::version::Current::default().compat().clone(),
|
||||
platform: InternedString::intern(&*crate::PLATFORM),
|
||||
language: Some(InternedString::intern(&*rust_i18n::locale())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct HardwareInfo {
|
||||
#[ts(type = "string")]
|
||||
pub arch: InternedString,
|
||||
#[ts(type = "number")]
|
||||
pub ram: u64,
|
||||
pub devices: Option<Vec<LshwDevice>>,
|
||||
}
|
||||
impl HardwareInfo {
|
||||
pub async fn load(ctx: &RpcContext) -> Result<Self, Error> {
|
||||
let s = ctx.db.peek().await.into_public().into_server_info();
|
||||
Ok(Self {
|
||||
arch: s.as_arch().de()?,
|
||||
ram: s.as_ram().de()?,
|
||||
devices: Some(s.as_devices().de()?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Metadata {
|
||||
#[serde(default)]
|
||||
get_device_info: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DeviceInfoMiddleware {
|
||||
device_info_header: Option<HeaderValue>,
|
||||
device_info: Option<DeviceInfo>,
|
||||
req: Option<RpcRequest>,
|
||||
}
|
||||
impl DeviceInfoMiddleware {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
device_info_header: None,
|
||||
device_info: None,
|
||||
req: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Middleware<RegistryContext> for DeviceInfoMiddleware {
|
||||
type Metadata = Metadata;
|
||||
async fn process_http_request(
|
||||
&mut self,
|
||||
_: &RegistryContext,
|
||||
request: &mut Request,
|
||||
) -> Result<(), Response> {
|
||||
self.device_info_header = request.headers_mut().remove(DEVICE_INFO_HEADER);
|
||||
Ok(())
|
||||
}
|
||||
async fn process_rpc_request(
|
||||
&mut self,
|
||||
_: &RegistryContext,
|
||||
metadata: Self::Metadata,
|
||||
request: &mut RpcRequest,
|
||||
) -> Result<(), RpcResponse> {
|
||||
async move {
|
||||
if metadata.get_device_info {
|
||||
if let Some(device_info) = &self.device_info_header {
|
||||
let device_info = DeviceInfo::from_header_value(device_info)?;
|
||||
request.params["__DeviceInfo_device_info"] = to_value(&device_info)?;
|
||||
self.device_info = Some(device_info);
|
||||
self.req = Some(request.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
.map_err(|e| RpcResponse::from_result(Err(e)))
|
||||
}
|
||||
async fn process_rpc_response(
|
||||
&mut self,
|
||||
_: &RegistryContext,
|
||||
response: &mut RpcResponse,
|
||||
) -> () {
|
||||
if let (Some(req), Some(device_info), Ok(res)) =
|
||||
(&self.req, &self.device_info, &mut response.result)
|
||||
{
|
||||
if let Err(e) =
|
||||
device_info.filter_for_hardware(req.method.as_str(), req.params.clone(), res)
|
||||
{
|
||||
response.result = Err(e).map_err(From::from);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
use imbl::vector;
|
||||
|
||||
use super::RegistryMigration;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct RegistryAssetArray;
|
||||
impl RegistryMigration for RegistryAssetArray {
|
||||
fn action(&self, db: &mut Value) -> Result<(), Error> {
|
||||
for (_, info) in db["index"]["package"]["packages"]
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.iter_mut()
|
||||
{
|
||||
for (_, info) in info["versions"].as_object_mut().unwrap().iter_mut() {
|
||||
let hw_req = info["hardwareRequirements"].take();
|
||||
let mut s9pk = info["s9pk"].take();
|
||||
s9pk["urls"] = Value::Array(vector![s9pk["url"].take()]);
|
||||
info["s9pks"] = Value::Array(vector![Value::Array(vector![hw_req, s9pk])]);
|
||||
}
|
||||
}
|
||||
for (_, info) in db["index"]["os"]["versions"]
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.iter_mut()
|
||||
{
|
||||
for asset_ty in ["iso", "squashfs", "img"] {
|
||||
for (_, info) in info[asset_ty].as_object_mut().unwrap().iter_mut() {
|
||||
info["urls"] = Value::Array(vector![info["url"].take()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,541 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use imbl_value::InternedString;
|
||||
use itertools::Itertools;
|
||||
use rpc_toolkit::HandlerArgs;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
use url::Url;
|
||||
|
||||
use crate::PackageId;
|
||||
use crate::context::CliContext;
|
||||
use crate::prelude::*;
|
||||
use crate::progress::FullProgressTracker;
|
||||
use crate::registry::asset::BufferedHttpSource;
|
||||
use crate::registry::context::RegistryContext;
|
||||
use crate::registry::package::index::PackageVersionInfo;
|
||||
use crate::s9pk::S9pk;
|
||||
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
||||
use crate::s9pk::v2::SIG_CONTEXT;
|
||||
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||
use crate::sign::ed25519::Ed25519;
|
||||
use crate::sign::{AnySignature, AnyVerifyingKey, SignatureScheme};
|
||||
use crate::util::VersionString;
|
||||
use crate::util::io::TrackingIO;
|
||||
use crate::util::serde::Base64;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct AddPackageParams {
|
||||
#[ts(type = "string[]")]
|
||||
pub urls: Vec<Url>,
|
||||
#[ts(skip)]
|
||||
#[serde(rename = "__Auth_signer")]
|
||||
pub uploader: AnyVerifyingKey,
|
||||
pub commitment: MerkleArchiveCommitment,
|
||||
pub signature: AnySignature,
|
||||
}
|
||||
|
||||
pub async fn add_package(
|
||||
ctx: RegistryContext,
|
||||
AddPackageParams {
|
||||
urls,
|
||||
uploader,
|
||||
commitment,
|
||||
signature,
|
||||
}: AddPackageParams,
|
||||
) -> Result<(), Error> {
|
||||
uploader
|
||||
.scheme()
|
||||
.verify_commitment(&uploader, &commitment, SIG_CONTEXT, &signature)?;
|
||||
let peek = ctx.db.peek().await;
|
||||
let uploader_guid = peek.as_index().as_signers().get_signer(&uploader)?;
|
||||
|
||||
let Some(([url], rest)) = urls.split_at_checked(1) else {
|
||||
return Err(Error::new(
|
||||
eyre!("{}", t!("registry.package.add.must-specify-url")),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
};
|
||||
|
||||
let s9pk = S9pk::deserialize(
|
||||
&Arc::new(HttpSource::new(ctx.client.clone(), url.clone()).await?),
|
||||
Some(&commitment),
|
||||
)
|
||||
.await?;
|
||||
|
||||
for url in rest {
|
||||
S9pk::deserialize(
|
||||
&Arc::new(HttpSource::new(ctx.client.clone(), url.clone()).await?),
|
||||
Some(&commitment),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let manifest = s9pk.as_manifest();
|
||||
|
||||
let mut info = PackageVersionInfo::from_s9pk(&s9pk, urls).await?;
|
||||
for (_, s9pk) in &mut info.s9pks {
|
||||
if !s9pk.signatures.contains_key(&uploader) && s9pk.commitment == commitment {
|
||||
s9pk.signatures.insert(uploader.clone(), signature.clone());
|
||||
}
|
||||
}
|
||||
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
if db.as_admins().de()?.contains(&uploader_guid)
|
||||
|| db
|
||||
.as_index()
|
||||
.as_package()
|
||||
.as_packages()
|
||||
.as_idx(&manifest.id)
|
||||
.or_not_found(&manifest.id)?
|
||||
.as_authorized()
|
||||
.de()?
|
||||
.get(&uploader_guid)
|
||||
.map_or(false, |v| manifest.version.satisfies(v))
|
||||
{
|
||||
let package = db
|
||||
.as_index_mut()
|
||||
.as_package_mut()
|
||||
.as_packages_mut()
|
||||
.upsert(&manifest.id, || Ok(Default::default()))?;
|
||||
let v = package.as_versions_mut();
|
||||
if let Some(prev) = v.as_idx_mut(&manifest.version) {
|
||||
prev.mutate(|p| p.merge_with(info, true))?;
|
||||
} else {
|
||||
v.insert(&manifest.version, &info)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("{}", t!("registry.package.add.unauthorized")),
|
||||
ErrorKind::Authorization,
|
||||
))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.result
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Parser)]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliAddPackageParams {
|
||||
#[arg(help = "help.arg.s9pk-file-path")]
|
||||
pub file: PathBuf,
|
||||
#[arg(long, help = "help.arg.package-url")]
|
||||
pub url: Vec<Url>,
|
||||
#[arg(long, help = "help.arg.no-verify")]
|
||||
pub no_verify: bool,
|
||||
}
|
||||
|
||||
pub async fn cli_add_package(
|
||||
ctx: CliContext,
|
||||
CliAddPackageParams {
|
||||
file,
|
||||
url,
|
||||
no_verify,
|
||||
}: CliAddPackageParams,
|
||||
) -> Result<(), Error> {
|
||||
let s9pk = S9pk::open(&file, None).await?;
|
||||
cli_add_package_impl(ctx, s9pk, url, no_verify).await
|
||||
}
|
||||
|
||||
pub async fn cli_add_package_impl(
|
||||
ctx: CliContext,
|
||||
s9pk: S9pk,
|
||||
url: Vec<Url>,
|
||||
no_verify: bool,
|
||||
) -> Result<(), Error> {
|
||||
let manifest = s9pk.as_manifest();
|
||||
|
||||
let progress = FullProgressTracker::new();
|
||||
let mut sign_phase = progress.add_phase(InternedString::intern("Signing File"), Some(1));
|
||||
let verify = if !no_verify {
|
||||
url.iter()
|
||||
.map(|url| {
|
||||
let phase = progress.add_phase(
|
||||
InternedString::from_display(&lazy_format!("Verifying {url}")),
|
||||
Some(100),
|
||||
);
|
||||
(url.clone(), phase)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let mut index_phase = progress.add_phase(
|
||||
InternedString::intern("Adding File to Registry Index"),
|
||||
Some(1),
|
||||
);
|
||||
|
||||
let progress_task = progress.progress_bar_task(&format!(
|
||||
"Adding {}@{}{} to registry...",
|
||||
manifest.id,
|
||||
manifest.version,
|
||||
manifest
|
||||
.hardware_requirements
|
||||
.arch
|
||||
.as_ref()
|
||||
.map_or(String::new(), |a| format!(" ({})", a.iter().join("/")))
|
||||
));
|
||||
|
||||
sign_phase.start();
|
||||
let commitment = s9pk.as_archive().commitment().await?;
|
||||
let signature = Ed25519.sign_commitment(ctx.developer_key()?, &commitment, SIG_CONTEXT)?;
|
||||
sign_phase.complete();
|
||||
|
||||
for (url, mut phase) in verify {
|
||||
phase.start();
|
||||
let source = BufferedHttpSource::new(ctx.client.clone(), url, phase).await?;
|
||||
let mut src = S9pk::deserialize(&Arc::new(source), Some(&commitment)).await?;
|
||||
src.serialize(&mut TrackingIO::new(0, &mut tokio::io::sink()), true)
|
||||
.await?;
|
||||
}
|
||||
|
||||
index_phase.start();
|
||||
ctx.call_remote::<RegistryContext>(
|
||||
"package.add",
|
||||
imbl_value::json!({
|
||||
"urls": &url,
|
||||
"signature": AnySignature::Ed25519(signature),
|
||||
"commitment": commitment,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
index_phase.complete();
|
||||
|
||||
progress.complete();
|
||||
|
||||
progress_task.await.with_kind(ErrorKind::Unknown)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct RemovePackageParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub id: PackageId,
|
||||
#[arg(help = "help.arg.package-version")]
|
||||
pub version: VersionString,
|
||||
#[arg(long, help = "help.arg.signature-hash")]
|
||||
pub sighash: Option<Base64<[u8; 32]>>,
|
||||
#[ts(skip)]
|
||||
#[arg(skip)]
|
||||
#[serde(rename = "__Auth_signer")]
|
||||
pub signer: Option<AnyVerifyingKey>,
|
||||
}
|
||||
|
||||
pub async fn remove_package(
|
||||
ctx: RegistryContext,
|
||||
RemovePackageParams {
|
||||
id,
|
||||
version,
|
||||
sighash,
|
||||
signer,
|
||||
}: RemovePackageParams,
|
||||
) -> Result<bool, Error> {
|
||||
let peek = ctx.db.peek().await;
|
||||
let signer = signer.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("{}", t!("registry.package.missing-signer")),
|
||||
ErrorKind::InvalidRequest,
|
||||
)
|
||||
})?;
|
||||
let signer_guid = peek.as_index().as_signers().get_signer(&signer)?;
|
||||
|
||||
let rev = ctx
|
||||
.db
|
||||
.mutate(|db| {
|
||||
if db.as_admins().de()?.contains(&signer_guid)
|
||||
|| db
|
||||
.as_index()
|
||||
.as_package()
|
||||
.as_packages()
|
||||
.as_idx(&id)
|
||||
.or_not_found(&id)?
|
||||
.as_authorized()
|
||||
.de()?
|
||||
.get(&signer_guid)
|
||||
.map_or(false, |v| version.satisfies(v))
|
||||
{
|
||||
if let Some(package) = db
|
||||
.as_index_mut()
|
||||
.as_package_mut()
|
||||
.as_packages_mut()
|
||||
.as_idx_mut(&id)
|
||||
{
|
||||
if let Some(sighash) = sighash {
|
||||
if if let Some(package) = package.as_versions_mut().as_idx_mut(&version) {
|
||||
package.as_s9pks_mut().mutate(|s| {
|
||||
s.retain(|(_, asset)| asset.commitment.root_sighash != sighash);
|
||||
Ok(s.is_empty())
|
||||
})?
|
||||
} else {
|
||||
false
|
||||
} {
|
||||
package.as_versions_mut().remove(&version)?;
|
||||
}
|
||||
} else {
|
||||
package.as_versions_mut().remove(&version)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("{}", t!("registry.package.unauthorized")),
|
||||
ErrorKind::Authorization,
|
||||
))
|
||||
}
|
||||
})
|
||||
.await;
|
||||
rev.result.map(|_| rev.revision.is_some())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct AddMirrorParams {
|
||||
#[ts(type = "string")]
|
||||
pub url: Url,
|
||||
#[ts(skip)]
|
||||
#[serde(rename = "__Auth_signer")]
|
||||
pub uploader: AnyVerifyingKey,
|
||||
pub commitment: MerkleArchiveCommitment,
|
||||
pub signature: AnySignature,
|
||||
}
|
||||
|
||||
pub async fn add_mirror(
|
||||
ctx: RegistryContext,
|
||||
AddMirrorParams {
|
||||
url,
|
||||
uploader,
|
||||
commitment,
|
||||
signature,
|
||||
}: AddMirrorParams,
|
||||
) -> Result<(), Error> {
|
||||
uploader
|
||||
.scheme()
|
||||
.verify_commitment(&uploader, &commitment, SIG_CONTEXT, &signature)?;
|
||||
let peek = ctx.db.peek().await;
|
||||
let uploader_guid = peek.as_index().as_signers().get_signer(&uploader)?;
|
||||
|
||||
let s9pk = S9pk::deserialize(
|
||||
&Arc::new(HttpSource::new(ctx.client.clone(), url.clone()).await?),
|
||||
Some(&commitment),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let manifest = s9pk.as_manifest();
|
||||
|
||||
let mut info = PackageVersionInfo::from_s9pk(&s9pk, vec![url]).await?;
|
||||
for (_, s9pk) in &mut info.s9pks {
|
||||
if !s9pk.signatures.contains_key(&uploader) && s9pk.commitment == commitment {
|
||||
s9pk.signatures.insert(uploader.clone(), signature.clone());
|
||||
}
|
||||
}
|
||||
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
if db.as_admins().de()?.contains(&uploader_guid)
|
||||
|| db
|
||||
.as_index()
|
||||
.as_package()
|
||||
.as_packages()
|
||||
.as_idx(&manifest.id)
|
||||
.or_not_found(&manifest.id)?
|
||||
.as_authorized()
|
||||
.de()?
|
||||
.get(&uploader_guid)
|
||||
.map_or(false, |v| manifest.version.satisfies(v))
|
||||
{
|
||||
let package = db
|
||||
.as_index_mut()
|
||||
.as_package_mut()
|
||||
.as_packages_mut()
|
||||
.as_idx_mut(&manifest.id)
|
||||
.and_then(|p| p.as_versions_mut().as_idx_mut(&manifest.version))
|
||||
.or_not_found(&lazy_format!("{}@{}", &manifest.id, &manifest.version))?;
|
||||
package.mutate(|p| p.merge_with(info, false))?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("{}", t!("registry.package.add-mirror.unauthorized")),
|
||||
ErrorKind::Authorization,
|
||||
))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.result
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Parser)]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliAddMirrorParams {
|
||||
#[arg(help = "help.arg.s9pk-file-path")]
|
||||
pub file: PathBuf,
|
||||
#[arg(help = "help.arg.mirror-url")]
|
||||
pub url: Url,
|
||||
#[arg(long, help = "help.arg.no-verify")]
|
||||
pub no_verify: bool,
|
||||
}
|
||||
|
||||
pub async fn cli_add_mirror(
|
||||
HandlerArgs {
|
||||
context: ctx,
|
||||
parent_method,
|
||||
method,
|
||||
params:
|
||||
CliAddMirrorParams {
|
||||
file,
|
||||
url,
|
||||
no_verify,
|
||||
},
|
||||
..
|
||||
}: HandlerArgs<CliContext, CliAddMirrorParams>,
|
||||
) -> Result<(), Error> {
|
||||
let s9pk = S9pk::open(&file, None).await?;
|
||||
|
||||
let progress = FullProgressTracker::new();
|
||||
let mut sign_phase = progress.add_phase(InternedString::intern("Signing File"), Some(1));
|
||||
let verify = if !no_verify {
|
||||
let url = &url;
|
||||
vec![(
|
||||
url.clone(),
|
||||
progress.add_phase(
|
||||
InternedString::from_display(&lazy_format!("Verifying {url}")),
|
||||
Some(100),
|
||||
),
|
||||
)]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let mut index_phase = progress.add_phase(
|
||||
InternedString::intern("Adding File to Registry Index"),
|
||||
Some(1),
|
||||
);
|
||||
|
||||
let progress_task =
|
||||
progress.progress_bar_task(&format!("Adding {} to registry...", file.display()));
|
||||
|
||||
sign_phase.start();
|
||||
let commitment = s9pk.as_archive().commitment().await?;
|
||||
let signature = Ed25519.sign_commitment(ctx.developer_key()?, &commitment, SIG_CONTEXT)?;
|
||||
sign_phase.complete();
|
||||
|
||||
for (url, mut phase) in verify {
|
||||
phase.start();
|
||||
let source = BufferedHttpSource::new(ctx.client.clone(), url, phase).await?;
|
||||
let mut src = S9pk::deserialize(&Arc::new(source), Some(&commitment)).await?;
|
||||
src.serialize(&mut TrackingIO::new(0, &mut tokio::io::sink()), true)
|
||||
.await?;
|
||||
}
|
||||
|
||||
index_phase.start();
|
||||
ctx.call_remote::<RegistryContext>(
|
||||
&parent_method.into_iter().chain(method).join("."),
|
||||
imbl_value::json!({
|
||||
"url": &url,
|
||||
"signature": AnySignature::Ed25519(signature),
|
||||
"commitment": commitment,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
index_phase.complete();
|
||||
|
||||
progress.complete();
|
||||
|
||||
progress_task.await.with_kind(ErrorKind::Unknown)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct RemoveMirrorParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub id: PackageId,
|
||||
#[arg(help = "help.arg.package-version")]
|
||||
pub version: VersionString,
|
||||
#[arg(long, help = "help.arg.mirror-url")]
|
||||
#[ts(type = "string")]
|
||||
pub url: Url,
|
||||
#[ts(skip)]
|
||||
#[arg(skip)]
|
||||
#[serde(rename = "__Auth_signer")]
|
||||
pub signer: Option<AnyVerifyingKey>,
|
||||
}
|
||||
|
||||
pub async fn remove_mirror(
|
||||
ctx: RegistryContext,
|
||||
RemoveMirrorParams {
|
||||
id,
|
||||
version,
|
||||
url,
|
||||
signer,
|
||||
}: RemoveMirrorParams,
|
||||
) -> Result<(), Error> {
|
||||
let peek = ctx.db.peek().await;
|
||||
let signer = signer.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("{}", t!("registry.package.missing-signer")),
|
||||
ErrorKind::InvalidRequest,
|
||||
)
|
||||
})?;
|
||||
let signer_guid = peek.as_index().as_signers().get_signer(&signer)?;
|
||||
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
if db.as_admins().de()?.contains(&signer_guid)
|
||||
|| db
|
||||
.as_index()
|
||||
.as_package()
|
||||
.as_packages()
|
||||
.as_idx(&id)
|
||||
.or_not_found(&id)?
|
||||
.as_authorized()
|
||||
.de()?
|
||||
.get(&signer_guid)
|
||||
.map_or(false, |v| version.satisfies(v))
|
||||
{
|
||||
if let Some(package) = db
|
||||
.as_index_mut()
|
||||
.as_package_mut()
|
||||
.as_packages_mut()
|
||||
.as_idx_mut(&id)
|
||||
.and_then(|p| p.as_versions_mut().as_idx_mut(&version))
|
||||
{
|
||||
package.as_s9pks_mut().mutate(|s| {
|
||||
s.iter_mut()
|
||||
.for_each(|(_, asset)| asset.urls.retain(|u| u != &url));
|
||||
if s.iter().any(|(_, asset)| asset.urls.is_empty()) {
|
||||
Err(Error::new(
|
||||
eyre!("{}", t!("registry.package.cannot-remove-last-mirror")),
|
||||
ErrorKind::InvalidRequest,
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("{}", t!("registry.package.remove-mirror.unauthorized")),
|
||||
ErrorKind::Authorization,
|
||||
))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.result
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::u32;
|
||||
|
||||
use chrono::Utc;
|
||||
use exver::{Version, VersionRange};
|
||||
use imbl_value::InternedString;
|
||||
use patch_db::ModelExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
use url::Url;
|
||||
|
||||
use crate::PackageId;
|
||||
use crate::prelude::*;
|
||||
use crate::registry::asset::RegistryAsset;
|
||||
use crate::registry::context::RegistryContext;
|
||||
use crate::registry::device_info::DeviceInfo;
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::s9pk::S9pk;
|
||||
use crate::s9pk::git_hash::GitHash;
|
||||
use crate::s9pk::manifest::{Alerts, Description, HardwareRequirements, LocaleString};
|
||||
use crate::s9pk::merkle_archive::source::FileSource;
|
||||
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||
use crate::sign::{AnySignature, AnyVerifyingKey};
|
||||
use crate::util::{DataUrl, VersionString};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct PackageIndex {
|
||||
pub categories: BTreeMap<InternedString, Category>,
|
||||
pub packages: BTreeMap<PackageId, PackageInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct PackageInfo {
|
||||
#[ts(as = "BTreeMap::<Guid, String>")]
|
||||
pub authorized: BTreeMap<Guid, VersionRange>,
|
||||
pub versions: BTreeMap<VersionString, PackageVersionInfo>,
|
||||
#[ts(type = "string[]")]
|
||||
pub categories: BTreeSet<InternedString>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct Category {
|
||||
pub name: LocaleString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct DependencyMetadata {
|
||||
pub title: Option<LocaleString>,
|
||||
pub icon: Option<DataUrl<'static>>,
|
||||
pub description: Option<LocaleString>,
|
||||
pub optional: bool,
|
||||
}
|
||||
impl DependencyMetadata {
|
||||
pub fn localize_for(&mut self, locale: &str) {
|
||||
self.title.as_mut().map(|t| t.localize_for(locale));
|
||||
self.description.as_mut().map(|d| d.localize_for(locale));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct PackageMetadata {
|
||||
#[ts(type = "string")]
|
||||
pub title: InternedString,
|
||||
pub icon: DataUrl<'static>,
|
||||
pub description: Description,
|
||||
pub release_notes: LocaleString,
|
||||
pub git_hash: Option<GitHash>,
|
||||
#[ts(type = "string")]
|
||||
pub license: InternedString,
|
||||
#[ts(type = "string")]
|
||||
pub wrapper_repo: Url,
|
||||
#[ts(type = "string")]
|
||||
pub upstream_repo: Url,
|
||||
#[ts(type = "string")]
|
||||
pub support_site: Url,
|
||||
#[ts(type = "string")]
|
||||
pub marketing_site: Url,
|
||||
#[ts(type = "string | null")]
|
||||
pub donation_url: Option<Url>,
|
||||
#[ts(type = "string | null")]
|
||||
pub docs_url: Option<Url>,
|
||||
pub alerts: Alerts,
|
||||
pub dependency_metadata: BTreeMap<PackageId, DependencyMetadata>,
|
||||
#[ts(type = "string")]
|
||||
pub os_version: Version,
|
||||
#[ts(type = "string | null")]
|
||||
pub sdk_version: Option<Version>,
|
||||
#[serde(default)]
|
||||
pub hardware_acceleration: bool,
|
||||
}
|
||||
impl PackageMetadata {
|
||||
pub async fn load<S: FileSource + Clone>(s9pk: &S9pk<S>) -> Result<Self, Error> {
|
||||
let manifest = s9pk.as_manifest();
|
||||
let mut dependency_metadata = BTreeMap::new();
|
||||
for (id, info) in &manifest.dependencies.0 {
|
||||
let metadata = s9pk.dependency_metadata(id).await?;
|
||||
dependency_metadata.insert(
|
||||
id.clone(),
|
||||
DependencyMetadata {
|
||||
title: metadata.map(|m| m.title),
|
||||
icon: s9pk.dependency_icon_data_url(id).await?,
|
||||
description: info.description.clone(),
|
||||
optional: info.optional,
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(Self {
|
||||
title: manifest.title.clone(),
|
||||
icon: s9pk.icon_data_url().await?,
|
||||
description: manifest.description.clone(),
|
||||
release_notes: manifest.release_notes.clone(),
|
||||
git_hash: manifest.git_hash.clone(),
|
||||
license: manifest.license.clone(),
|
||||
wrapper_repo: manifest.wrapper_repo.clone(),
|
||||
upstream_repo: manifest.upstream_repo.clone(),
|
||||
support_site: manifest.support_site.clone(),
|
||||
marketing_site: manifest.marketing_site.clone(),
|
||||
donation_url: manifest.donation_url.clone(),
|
||||
docs_url: manifest.docs_url.clone(),
|
||||
alerts: manifest.alerts.clone(),
|
||||
dependency_metadata,
|
||||
os_version: manifest.os_version.clone(),
|
||||
sdk_version: manifest.sdk_version.clone(),
|
||||
hardware_acceleration: manifest.hardware_acceleration.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct PackageVersionInfo {
|
||||
#[serde(flatten)]
|
||||
pub metadata: PackageMetadata,
|
||||
#[ts(type = "string | null")]
|
||||
pub source_version: Option<VersionRange>,
|
||||
pub s9pks: Vec<(HardwareRequirements, RegistryAsset<MerkleArchiveCommitment>)>,
|
||||
}
|
||||
impl PackageVersionInfo {
|
||||
pub async fn from_s9pk<S: FileSource + Clone>(
|
||||
s9pk: &S9pk<S>,
|
||||
urls: Vec<Url>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
metadata: PackageMetadata::load(s9pk).await?,
|
||||
source_version: None, // TODO
|
||||
s9pks: vec![(
|
||||
s9pk.as_manifest().hardware_requirements.clone(),
|
||||
RegistryAsset {
|
||||
published_at: Utc::now(),
|
||||
urls,
|
||||
commitment: s9pk.as_archive().commitment().await?,
|
||||
signatures: [(
|
||||
AnyVerifyingKey::Ed25519(s9pk.as_archive().signer()),
|
||||
AnySignature::Ed25519(s9pk.as_archive().signature().await?),
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
},
|
||||
)],
|
||||
})
|
||||
}
|
||||
pub fn merge_with(&mut self, other: Self, replace_urls: bool) -> Result<(), Error> {
|
||||
for (hw_req, asset) in other.s9pks {
|
||||
if let Some((_, matching)) = self
|
||||
.s9pks
|
||||
.iter_mut()
|
||||
.find(|(h, s)| s.commitment == asset.commitment && *h == hw_req)
|
||||
{
|
||||
if replace_urls {
|
||||
matching.urls = asset.urls;
|
||||
} else {
|
||||
for url in asset.urls {
|
||||
if matching.urls.contains(&url) {
|
||||
continue;
|
||||
}
|
||||
matching.urls.push(url);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some((h, matching)) = self.s9pks.iter_mut().find(|(h, _)| *h == hw_req) {
|
||||
*matching = asset;
|
||||
*h = hw_req;
|
||||
} else {
|
||||
self.s9pks.push((hw_req, asset));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.s9pks.sort_by_key(|(h, _)| h.specificity_desc());
|
||||
Ok(())
|
||||
}
|
||||
pub fn table(self, version: &VersionString) -> prettytable::Table {
|
||||
use prettytable::*;
|
||||
|
||||
let mut table = Table::new();
|
||||
|
||||
table.add_row(row![bc => &self.metadata.title]);
|
||||
table.add_row(row![br -> "VERSION", AsRef::<str>::as_ref(version)]);
|
||||
table.add_row(row![br -> "RELEASE NOTES", &self.metadata.release_notes.localized()]);
|
||||
table.add_row(
|
||||
row![br -> "ABOUT", &textwrap::wrap(&self.metadata.description.short.localized(), 80).join("\n")],
|
||||
);
|
||||
table.add_row(row![
|
||||
br -> "DESCRIPTION",
|
||||
&textwrap::wrap(&self.metadata.description.long.localized(), 80).join("\n")
|
||||
]);
|
||||
table.add_row(row![br -> "GIT HASH", self.metadata.git_hash.as_deref().unwrap_or("N/A")]);
|
||||
table.add_row(row![br -> "LICENSE", &self.metadata.license]);
|
||||
table.add_row(row![br -> "PACKAGE REPO", &self.metadata.wrapper_repo.to_string()]);
|
||||
table.add_row(row![br -> "SERVICE REPO", &self.metadata.upstream_repo.to_string()]);
|
||||
table.add_row(row![br -> "WEBSITE", &self.metadata.marketing_site.to_string()]);
|
||||
table.add_row(row![br -> "SUPPORT", &self.metadata.support_site.to_string()]);
|
||||
|
||||
table
|
||||
}
|
||||
}
|
||||
impl Model<PackageVersionInfo> {
|
||||
/// Filters this package version for compatibility with the given device.
|
||||
/// Returns false if the package is incompatible (should be removed).
|
||||
/// Modifies s9pks in place to only include compatible variants.
|
||||
pub fn for_device(&mut self, device_info: &DeviceInfo) -> Result<bool, Error> {
|
||||
if !self
|
||||
.as_metadata()
|
||||
.as_os_version()
|
||||
.de()?
|
||||
.satisfies(&device_info.os.compat)
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
if let Some(hw) = &device_info.hardware {
|
||||
self.as_s9pks_mut().mutate(|s9pks| {
|
||||
s9pks.retain(|(hw_req, _)| {
|
||||
if let Some(arch) = &hw_req.arch {
|
||||
if !arch.contains(&hw.arch) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(ram) = hw_req.ram {
|
||||
if hw.ram < ram {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(dev) = &hw.devices {
|
||||
for device_filter in &hw_req.device {
|
||||
if !dev
|
||||
.iter()
|
||||
.filter(|d| d.class() == &*device_filter.class)
|
||||
.any(|d| device_filter.matches(d))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
if hw.devices.is_some() {
|
||||
s9pks.sort_by_key(|(req, _)| req.specificity_desc());
|
||||
} else {
|
||||
s9pks.sort_by_key(|(req, _)| {
|
||||
let (dev, arch, ram) = req.specificity_desc();
|
||||
(u32::MAX - dev, arch, ram)
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
if ModelExt::as_value(self.as_s9pks())
|
||||
.as_array()
|
||||
.map_or(true, |s| s.is_empty())
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Some(locale) = device_info.os.language.as_deref() {
|
||||
let metadata = self.as_metadata_mut();
|
||||
metadata
|
||||
.as_alerts_mut()
|
||||
.mutate(|a| Ok(a.localize_for(locale)))?;
|
||||
metadata
|
||||
.as_dependency_metadata_mut()
|
||||
.as_entries_mut()?
|
||||
.into_iter()
|
||||
.try_for_each(|(_, d)| d.mutate(|d| Ok(d.localize_for(locale))))?;
|
||||
metadata
|
||||
.as_description_mut()
|
||||
.mutate(|d| Ok(d.localize_for(locale)))?;
|
||||
metadata
|
||||
.as_release_notes_mut()
|
||||
.mutate(|r| Ok(r.localize_for(locale)))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_package_index(ctx: RegistryContext) -> Result<PackageIndex, Error> {
|
||||
ctx.db.peek().await.into_index().into_package().de()
|
||||
}
|
||||
@@ -1,463 +0,0 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::Path;
|
||||
|
||||
use clap::builder::ValueParserFactory;
|
||||
use color_eyre::eyre::eyre;
|
||||
use exver::{Version, VersionRange};
|
||||
use imbl_value::{InOMap, InternedString};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
use url::Url;
|
||||
|
||||
pub use crate::PackageId;
|
||||
use crate::dependencies::Dependencies;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::git_hash::GitHash;
|
||||
use crate::s9pk::merkle_archive::directory_contents::DirectoryContents;
|
||||
use crate::s9pk::merkle_archive::expected::{Expected, Filter};
|
||||
use crate::s9pk::v2::pack::ImageConfig;
|
||||
use crate::util::lshw::{LshwDevice, LshwDisplay, LshwProcessor};
|
||||
use crate::util::serde::Regex;
|
||||
use crate::util::{FromStrParser, VersionString, mime};
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::{ImageId, VolumeId};
|
||||
|
||||
fn current_version() -> Version {
|
||||
Current::default().semver()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct Manifest {
|
||||
pub id: PackageId,
|
||||
#[ts(type = "string")]
|
||||
pub title: InternedString,
|
||||
pub version: VersionString,
|
||||
pub satisfies: BTreeSet<VersionString>,
|
||||
pub release_notes: LocaleString,
|
||||
#[ts(type = "string")]
|
||||
pub can_migrate_to: VersionRange,
|
||||
#[ts(type = "string")]
|
||||
pub can_migrate_from: VersionRange,
|
||||
#[ts(type = "string")]
|
||||
pub license: InternedString, // type of license
|
||||
#[ts(type = "string")]
|
||||
pub wrapper_repo: Url,
|
||||
#[ts(type = "string")]
|
||||
pub upstream_repo: Url,
|
||||
#[ts(type = "string")]
|
||||
pub support_site: Url,
|
||||
#[ts(type = "string")]
|
||||
pub marketing_site: Url,
|
||||
#[ts(type = "string | null")]
|
||||
pub donation_url: Option<Url>,
|
||||
#[ts(type = "string | null")]
|
||||
pub docs_url: Option<Url>,
|
||||
pub description: Description,
|
||||
pub images: BTreeMap<ImageId, ImageConfig>,
|
||||
pub volumes: BTreeSet<VolumeId>,
|
||||
#[serde(default)]
|
||||
pub alerts: Alerts,
|
||||
#[serde(default)]
|
||||
pub dependencies: Dependencies,
|
||||
#[serde(default)]
|
||||
pub hardware_requirements: HardwareRequirements,
|
||||
#[serde(default)]
|
||||
pub hardware_acceleration: bool,
|
||||
pub git_hash: Option<GitHash>,
|
||||
#[serde(default = "current_version")]
|
||||
#[ts(type = "string")]
|
||||
pub os_version: Version,
|
||||
#[ts(type = "string | null")]
|
||||
pub sdk_version: Option<Version>,
|
||||
}
|
||||
impl Manifest {
|
||||
pub fn validate_for<'a, T: Clone>(
|
||||
&self,
|
||||
arch: Option<&str>,
|
||||
archive: &'a DirectoryContents<T>,
|
||||
) -> Result<Filter, Error> {
|
||||
let mut expected = Expected::new(archive);
|
||||
expected.check_file("manifest.json")?;
|
||||
expected.check_stem("icon", |ext| {
|
||||
ext.and_then(|e| e.to_str())
|
||||
.and_then(mime)
|
||||
.map_or(false, |mime| mime.starts_with("image/"))
|
||||
})?;
|
||||
expected.check_file("LICENSE.md")?;
|
||||
expected.check_file("javascript.squashfs")?;
|
||||
for (dependency, _) in &self.dependencies.0 {
|
||||
let dep_path = Path::new("dependencies").join(dependency);
|
||||
let _ = expected.check_file(dep_path.join("metadata.json"));
|
||||
let _ = expected.check_stem(dep_path.join("icon"), |ext| {
|
||||
ext.and_then(|e| e.to_str())
|
||||
.and_then(mime)
|
||||
.map_or(false, |mime| mime.starts_with("image/"))
|
||||
});
|
||||
}
|
||||
if let Err(e) = expected.check_file(Path::new("assets.squashfs")) {
|
||||
// backwards compatibility for alpha s9pks - remove eventually
|
||||
if expected.check_dir("assets").is_err() {
|
||||
tracing::warn!("{e}");
|
||||
tracing::debug!("{e:?}");
|
||||
// return Err(e);
|
||||
}
|
||||
}
|
||||
for (image_id, config) in &self.images {
|
||||
let mut check_arch = |arch: &str| {
|
||||
let mut arch = arch;
|
||||
if let Err(e) = expected.check_file(
|
||||
Path::new("images")
|
||||
.join(arch)
|
||||
.join(image_id)
|
||||
.with_extension("squashfs"),
|
||||
) {
|
||||
if let Some(emulate_as) = &config.emulate_missing_as {
|
||||
expected.check_file(
|
||||
Path::new("images")
|
||||
.join(emulate_as)
|
||||
.join(image_id)
|
||||
.with_extension("squashfs"),
|
||||
)?;
|
||||
arch = &**emulate_as;
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
expected.check_file(
|
||||
Path::new("images")
|
||||
.join(arch)
|
||||
.join(image_id)
|
||||
.with_extension("json"),
|
||||
)?;
|
||||
expected.check_file(
|
||||
Path::new("images")
|
||||
.join(arch)
|
||||
.join(image_id)
|
||||
.with_extension("env"),
|
||||
)?;
|
||||
Ok(())
|
||||
};
|
||||
if let Some(arch) = arch {
|
||||
check_arch(arch)?;
|
||||
} else if let Some(arches) = &self.hardware_requirements.arch {
|
||||
for arch in arches {
|
||||
check_arch(arch)?;
|
||||
}
|
||||
} else if let Some(arch) = config.emulate_missing_as.as_deref() {
|
||||
if !config.arch.contains(arch) {
|
||||
return Err(Error::new(
|
||||
eyre!("`emulateMissingAs` must match an included `arch`"),
|
||||
ErrorKind::ParseS9pk,
|
||||
));
|
||||
}
|
||||
for arch in &config.arch {
|
||||
check_arch(&arch)?;
|
||||
}
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"`emulateMissingAs` required for all images if no `arch` specified in `hardwareRequirements`"
|
||||
),
|
||||
ErrorKind::ParseS9pk,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(expected.into_filter())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct HardwareRequirements {
|
||||
#[serde(default)]
|
||||
pub device: Vec<DeviceFilter>,
|
||||
#[ts(type = "number | null")]
|
||||
pub ram: Option<u64>,
|
||||
#[ts(type = "string[] | null")]
|
||||
pub arch: Option<BTreeSet<InternedString>>,
|
||||
}
|
||||
impl HardwareRequirements {
|
||||
/// returns a value that can be used as a sort key to get most specific requirements first
|
||||
pub fn specificity_desc(&self) -> (u32, u32, u64) {
|
||||
(
|
||||
u32::MAX - self.device.len() as u32, // more device requirements = more specific
|
||||
self.arch.as_ref().map_or(u32::MAX, |a| a.len() as u32), // more arches = less specific
|
||||
self.ram.map_or(0, |r| r), // more ram = more specific
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, TS)]
|
||||
#[ts(type = "string | Record<string, string>")]
|
||||
pub enum LocaleString {
|
||||
Translated(String),
|
||||
LanguageMap(InOMap<InternedString, String>),
|
||||
}
|
||||
impl std::str::FromStr for LocaleString {
|
||||
type Err = std::convert::Infallible;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// Try JSON parse first (for maps or quoted strings)
|
||||
if let Ok(parsed) = serde_json::from_str::<LocaleString>(s) {
|
||||
return Ok(parsed);
|
||||
}
|
||||
// Fall back to plain string
|
||||
Ok(LocaleString::Translated(s.to_owned()))
|
||||
}
|
||||
}
|
||||
impl LocaleString {
|
||||
pub fn localize_for(&mut self, locale: &str) {
|
||||
if let Self::LanguageMap(map) = self {
|
||||
if let Some(translated) = map.remove(locale) {
|
||||
*self = Self::Translated(translated);
|
||||
return;
|
||||
}
|
||||
let prefix = locale.split_inclusive("_").next().unwrap();
|
||||
let mut first = None;
|
||||
for (lang, translated) in std::mem::take(map) {
|
||||
if lang.starts_with(prefix) {
|
||||
*self = Self::Translated(translated);
|
||||
return;
|
||||
}
|
||||
if first.is_none() {
|
||||
first = Some(translated);
|
||||
}
|
||||
}
|
||||
*self = Self::Translated(first.unwrap_or_default())
|
||||
}
|
||||
}
|
||||
pub fn localized_for(mut self, locale: &str) -> String {
|
||||
self.localize_for(locale);
|
||||
if let Self::Translated(s) = self {
|
||||
s
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
pub fn localize(&mut self) {
|
||||
self.localize_for(&*rust_i18n::locale());
|
||||
}
|
||||
pub fn localized(mut self) -> String {
|
||||
self.localized_for(&*rust_i18n::locale())
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for LocaleString {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct LocaleStringVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for LocaleStringVisitor {
|
||||
type Value = LocaleString;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a string or a map of language codes to strings")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(LocaleString::Translated(value.to_owned()))
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(LocaleString::Translated(value))
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
|
||||
where
|
||||
M: serde::de::MapAccess<'de>,
|
||||
{
|
||||
let language_map =
|
||||
InOMap::deserialize(serde::de::value::MapAccessDeserializer::new(map))?;
|
||||
Ok(LocaleString::LanguageMap(language_map))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(LocaleStringVisitor)
|
||||
}
|
||||
}
|
||||
impl Serialize for LocaleString {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
LocaleString::Translated(s) => serializer.serialize_str(s),
|
||||
LocaleString::LanguageMap(map) => map.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ValueParserFactory for LocaleString {
|
||||
type Parser = FromStrParser<Self>;
|
||||
fn value_parser() -> Self::Parser {
|
||||
FromStrParser::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct DeviceFilter {
|
||||
pub description: String,
|
||||
#[ts(type = "\"processor\" | \"display\"")]
|
||||
pub class: InternedString,
|
||||
#[ts(type = "string | null")]
|
||||
pub product: Option<Regex>,
|
||||
#[ts(type = "string | null")]
|
||||
pub vendor: Option<Regex>,
|
||||
#[ts(optional)]
|
||||
pub capabilities: Option<BTreeSet<InternedString>>,
|
||||
#[ts(optional)]
|
||||
pub driver: Option<InternedString>,
|
||||
}
|
||||
// Omit description
|
||||
impl PartialEq for DeviceFilter {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.class == other.class
|
||||
&& self.product == other.product
|
||||
&& self.vendor == other.vendor
|
||||
&& self.capabilities == other.capabilities
|
||||
&& self.driver == other.driver
|
||||
}
|
||||
}
|
||||
impl DeviceFilter {
|
||||
pub fn matches(&self, device: &LshwDevice) -> bool {
|
||||
if &*self.class != device.class() {
|
||||
return false;
|
||||
}
|
||||
match device {
|
||||
LshwDevice::Processor(LshwProcessor {
|
||||
product,
|
||||
vendor,
|
||||
capabilities,
|
||||
}) => {
|
||||
if let Some(match_product) = &self.product {
|
||||
if !product
|
||||
.as_deref()
|
||||
.map_or(false, |p| match_product.as_ref().is_match(p))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(match_vendor) = &self.vendor {
|
||||
if !vendor
|
||||
.as_deref()
|
||||
.map_or(false, |v| match_vendor.as_ref().is_match(v))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if !self
|
||||
.capabilities
|
||||
.as_ref()
|
||||
.map_or(true, |c| c.is_subset(capabilities))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
LshwDevice::Display(LshwDisplay {
|
||||
product,
|
||||
vendor,
|
||||
capabilities,
|
||||
driver,
|
||||
}) => {
|
||||
if let Some(match_product) = &self.product {
|
||||
if !product
|
||||
.as_deref()
|
||||
.map_or(false, |p| match_product.as_ref().is_match(p))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(match_vendor) = &self.vendor {
|
||||
if !vendor
|
||||
.as_deref()
|
||||
.map_or(false, |v| match_vendor.as_ref().is_match(v))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if !self
|
||||
.capabilities
|
||||
.as_ref()
|
||||
.map_or(true, |c| c.is_subset(capabilities))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if !self
|
||||
.driver
|
||||
.as_ref()
|
||||
.map_or(true, |d| Some(d) == driver.as_ref())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS, PartialEq)]
|
||||
#[ts(export)]
|
||||
pub struct Description {
|
||||
pub short: LocaleString,
|
||||
pub long: LocaleString,
|
||||
}
|
||||
impl Description {
|
||||
pub fn localize_for(&mut self, locale: &str) {
|
||||
self.short.localize_for(locale);
|
||||
self.long.localize_for(locale);
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), Error> {
|
||||
if match &self.short {
|
||||
LocaleString::Translated(s) => s.len() > 160,
|
||||
LocaleString::LanguageMap(map) => map.values().any(|s| s.len() > 160),
|
||||
} {
|
||||
return Err(Error::new(
|
||||
eyre!("Short description must be 160 characters or less."),
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
));
|
||||
}
|
||||
if match &self.short {
|
||||
LocaleString::Translated(s) => s.len() > 5000,
|
||||
LocaleString::LanguageMap(map) => map.values().any(|s| s.len() > 5000),
|
||||
} {
|
||||
return Err(Error::new(
|
||||
eyre!("Long description must be 5000 characters or less."),
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct Alerts {
|
||||
pub install: Option<LocaleString>,
|
||||
pub uninstall: Option<LocaleString>,
|
||||
pub restore: Option<LocaleString>,
|
||||
pub start: Option<LocaleString>,
|
||||
pub stop: Option<LocaleString>,
|
||||
}
|
||||
impl Alerts {
|
||||
pub fn localize_for(&mut self, locale: &str) {
|
||||
self.install.as_mut().map(|s| s.localize_for(locale));
|
||||
self.uninstall.as_mut().map(|s| s.localize_for(locale));
|
||||
self.restore.as_mut().map(|s| s.localize_for(locale));
|
||||
self.start.as_mut().map(|s| s.localize_for(locale));
|
||||
self.stop.as_mut().map(|s| s.localize_for(locale));
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
Section "InputClass"
|
||||
Identifier "system-keyboard"
|
||||
MatchIsKeyboard "on"
|
||||
Option "XkbModel" "{model}"
|
||||
Option "XkbLayout" "{layout}"
|
||||
Option "XkbVariant" "{variant}"
|
||||
Option "XkbOptions" "{options}"
|
||||
EndSection
|
||||
@@ -1,7 +0,0 @@
|
||||
XKBMODEL="{model}"
|
||||
XKBLAYOUT="{layout}"
|
||||
XKBVARIANT="{variant}"
|
||||
XKBOPTIONS="{options}"
|
||||
|
||||
KEYMAP="{keymap}"
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use imbl_value::InternedString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::process::Command;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
|
||||
const KNOWN_CLASSES: &[&str] = &["processor", "display"];
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[serde(tag = "class")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub enum LshwDevice {
|
||||
Processor(LshwProcessor),
|
||||
Display(LshwDisplay),
|
||||
}
|
||||
impl LshwDevice {
|
||||
pub fn class(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Processor(_) => "processor",
|
||||
Self::Display(_) => "display",
|
||||
}
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
pub fn from_value(value: &Value) -> Option<Self> {
|
||||
match value["class"].as_str() {
|
||||
Some("processor") => Some(LshwDevice::Processor(LshwProcessor::from_value(value))),
|
||||
Some("display") => Some(LshwDevice::Display(LshwDisplay::from_value(value))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
pub struct LshwProcessor {
|
||||
pub product: Option<InternedString>,
|
||||
pub vendor: Option<InternedString>,
|
||||
pub capabilities: BTreeSet<InternedString>,
|
||||
}
|
||||
impl LshwProcessor {
|
||||
#[instrument(skip_all)]
|
||||
fn from_value(value: &Value) -> Self {
|
||||
Self {
|
||||
product: value["product"].as_str().map(From::from),
|
||||
vendor: value["vendor"].as_str().map(From::from),
|
||||
capabilities: value["capabilities"]
|
||||
.as_object()
|
||||
.into_iter()
|
||||
.flat_map(|o| o.keys())
|
||||
.map(|k| k.clone())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
pub struct LshwDisplay {
|
||||
pub product: Option<InternedString>,
|
||||
pub vendor: Option<InternedString>,
|
||||
pub capabilities: BTreeSet<InternedString>,
|
||||
pub driver: Option<InternedString>,
|
||||
}
|
||||
impl LshwDisplay {
|
||||
#[instrument(skip_all)]
|
||||
fn from_value(value: &Value) -> Self {
|
||||
Self {
|
||||
product: value["product"].as_str().map(From::from),
|
||||
vendor: value["vendor"].as_str().map(From::from),
|
||||
capabilities: value["capabilities"]
|
||||
.as_object()
|
||||
.into_iter()
|
||||
.flat_map(|o| o.keys())
|
||||
.map(|k| k.clone())
|
||||
.collect(),
|
||||
driver: value["configuration"]["driver"].as_str().map(From::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn lshw() -> Result<Vec<LshwDevice>, Error> {
|
||||
let mut cmd = Command::new("lshw");
|
||||
cmd.arg("-json");
|
||||
for class in KNOWN_CLASSES {
|
||||
cmd.arg("-class").arg(*class);
|
||||
}
|
||||
Ok(
|
||||
serde_json::from_slice::<Vec<Value>>(&cmd.invoke(crate::ErrorKind::Lshw).await?)
|
||||
.with_kind(crate::ErrorKind::Deserialization)?
|
||||
.iter()
|
||||
.filter_map(LshwDevice::from_value)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{VersionT, v0_4_0_alpha_16};
|
||||
use crate::db::model::public::AcmeSettings;
|
||||
use crate::net::acme::AcmeProvider;
|
||||
use crate::prelude::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref V0_4_0_alpha_17: exver::Version = exver::Version::new(
|
||||
[0, 4, 0],
|
||||
[PreReleaseSegment::String("alpha".into()), 17.into()]
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Version;
|
||||
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_4_0_alpha_16::Version;
|
||||
type PreUpRes = ();
|
||||
|
||||
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn semver(self) -> exver::Version {
|
||||
V0_4_0_alpha_17.clone()
|
||||
}
|
||||
fn compat(self) -> &'static VersionRange {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||
let acme = db["public"]["serverInfo"]["network"]["acme"]
|
||||
.as_object_mut()
|
||||
.or_not_found("public.serverInfo.network.acme")?;
|
||||
let letsencrypt =
|
||||
InternedString::intern::<&str>("letsencrypt".parse::<AcmeProvider>()?.as_ref());
|
||||
if !acme.contains_key(&letsencrypt) {
|
||||
acme.insert(
|
||||
letsencrypt,
|
||||
to_value(&AcmeSettings {
|
||||
contact: Vec::new(),
|
||||
})?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Value::Null)
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{VersionT, v0_4_0_alpha_17};
|
||||
use crate::context::RpcContext;
|
||||
use crate::prelude::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref V0_4_0_alpha_18: exver::Version = exver::Version::new(
|
||||
[0, 4, 0],
|
||||
[PreReleaseSegment::String("alpha".into()), 18.into()]
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Version;
|
||||
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_4_0_alpha_17::Version;
|
||||
type PreUpRes = ();
|
||||
|
||||
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn semver(self) -> exver::Version {
|
||||
V0_4_0_alpha_18.clone()
|
||||
}
|
||||
fn compat(self) -> &'static VersionRange {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||
db["public"]["serverInfo"]["devices"] = Value::Array(Default::default());
|
||||
|
||||
let lang = db["public"]["ui"]
|
||||
.as_object_mut()
|
||||
.map_or(Value::Null, |m| m.remove("language").unwrap_or_default());
|
||||
if let Some(lang) = lang.as_str() {
|
||||
let lang = match lang {
|
||||
"en" => "en_US",
|
||||
"de" => "de_DE",
|
||||
"es" => "es_ES",
|
||||
"fr" => "fr_FR",
|
||||
"pl" => "pl_PL",
|
||||
_ => return Ok(Value::Null),
|
||||
};
|
||||
|
||||
let lang = Value::String(Arc::new(lang.into()));
|
||||
|
||||
db["public"]["serverInfo"]["language"] = lang.clone();
|
||||
|
||||
return Ok(lang);
|
||||
}
|
||||
Ok(Value::Null)
|
||||
}
|
||||
async fn post_up(self, _: &RpcContext, input: Value) -> Result<(), Error> {
|
||||
if let Some(language) = input.as_str() {
|
||||
crate::system::save_language(language).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{VersionT, v0_4_0_alpha_18};
|
||||
use crate::prelude::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref V0_4_0_alpha_19: exver::Version = exver::Version::new(
|
||||
[0, 4, 0],
|
||||
[PreReleaseSegment::String("alpha".into()), 19.into()]
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Version;
|
||||
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_4_0_alpha_18::Version;
|
||||
type PreUpRes = ();
|
||||
|
||||
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn semver(self) -> exver::Version {
|
||||
V0_4_0_alpha_19.clone()
|
||||
}
|
||||
fn compat(self) -> &'static VersionRange {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||
Ok(Value::Null)
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
289
core/startos/Cargo.toml
Normal file
289
core/startos/Cargo.toml
Normal file
@@ -0,0 +1,289 @@
|
||||
[package]
|
||||
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
||||
description = "The core of StartOS"
|
||||
documentation = "https://docs.rs/start-os"
|
||||
edition = "2024"
|
||||
keywords = [
|
||||
"bitcoin",
|
||||
"full-node",
|
||||
"lightning",
|
||||
"privacy",
|
||||
"raspberry-pi",
|
||||
"self-hosted",
|
||||
]
|
||||
license = "MIT"
|
||||
name = "start-os"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/Start9Labs/start-os"
|
||||
version = "0.4.0-alpha.16" # VERSION_BUMP
|
||||
|
||||
[lib]
|
||||
name = "startos"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "startbox"
|
||||
path = "src/main/startbox.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "start-cli"
|
||||
path = "src/main/start-cli.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "start-container"
|
||||
path = "src/main/start-container.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "registrybox"
|
||||
path = "src/main/registrybox.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tunnelbox"
|
||||
path = "src/main/tunnelbox.rs"
|
||||
|
||||
[features]
|
||||
arti = [
|
||||
"arti-client",
|
||||
"safelog",
|
||||
"tor-cell",
|
||||
"tor-hscrypto",
|
||||
"tor-hsservice",
|
||||
"tor-keymgr",
|
||||
"tor-llcrypto",
|
||||
"tor-proto",
|
||||
"tor-rtcompat",
|
||||
]
|
||||
console = ["console-subscriber", "tokio/tracing"]
|
||||
default = []
|
||||
dev = []
|
||||
test = []
|
||||
unstable = ["backtrace-on-stack-overflow"]
|
||||
|
||||
[dependencies]
|
||||
aes = { version = "0.7.5", features = ["ctr"] }
|
||||
arti-client = { version = "0.33", features = [
|
||||
"compression",
|
||||
"ephemeral-keystore",
|
||||
"experimental-api",
|
||||
"onion-service-client",
|
||||
"onion-service-service",
|
||||
"rustls",
|
||||
"static",
|
||||
"tokio",
|
||||
], default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
async-acme = { version = "0.6.0", git = "https://github.com/dr-bonez/async-acme.git", features = [
|
||||
"use_rustls",
|
||||
"use_tokio",
|
||||
] }
|
||||
async-compression = { version = "0.4.32", features = [
|
||||
"brotli",
|
||||
"gzip",
|
||||
"tokio",
|
||||
"zstd",
|
||||
] }
|
||||
async-stream = "0.3.5"
|
||||
async-trait = "0.1.74"
|
||||
axum = { version = "0.8.4", features = ["ws", "http2"] }
|
||||
backtrace-on-stack-overflow = { version = "0.3.0", optional = true }
|
||||
base32 = "0.5.0"
|
||||
base64 = "0.22.1"
|
||||
base64ct = "1.6.0"
|
||||
basic-cookies = "0.1.4"
|
||||
blake3 = { version = "1.5.0", features = ["mmap", "rayon"] }
|
||||
bytes = "1"
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
clap = { version = "4.4.12", features = ["string"] }
|
||||
color-eyre = "0.6.2"
|
||||
console = "0.16.2"
|
||||
console-subscriber = { version = "0.5.0", optional = true }
|
||||
const_format = "0.2.34"
|
||||
cookie = "0.18.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 = "3.0.1"
|
||||
ed25519 = { version = "2.2.3", features = ["alloc", "pem", "pkcs8"] }
|
||||
ed25519-dalek = { version = "2.2.0", features = [
|
||||
"digest",
|
||||
"hazmat",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"serde",
|
||||
"zeroize",
|
||||
] }
|
||||
ed25519-dalek-v1 = { package = "ed25519-dalek", version = "1" }
|
||||
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
||||
"serde",
|
||||
] }
|
||||
fd-lock-rs = "0.1.4"
|
||||
form_urlencoded = "1.2.1"
|
||||
futures = "0.3.28"
|
||||
gpt = "4.1.0"
|
||||
hex = "0.4.3"
|
||||
hickory-client = "0.25.2"
|
||||
hickory-server = "0.25.2"
|
||||
hmac = "0.12.1"
|
||||
http = "1.0.0"
|
||||
http-body-util = "0.1"
|
||||
hyper = { version = "1.5", features = ["http1", "http2", "server"] }
|
||||
hyper-util = { version = "0.1.10", features = [
|
||||
"http1",
|
||||
"http2",
|
||||
"server",
|
||||
"server-auto",
|
||||
"server-graceful",
|
||||
"service",
|
||||
"tokio",
|
||||
] }
|
||||
id-pool = { version = "0.2.2", default-features = false, features = [
|
||||
"serde",
|
||||
"u16",
|
||||
] }
|
||||
iddqd = "0.3.14"
|
||||
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.18.3", features = ["tokio"] }
|
||||
inotify = "0.11.0"
|
||||
integer-encoding = { version = "4.0.0", features = ["tokio_async"] }
|
||||
ipnet = { version = "2.8.0", features = ["serde"] }
|
||||
isocountry = "0.3.2"
|
||||
itertools = "0.14.0"
|
||||
jaq-core = "0.10.1"
|
||||
jaq-std = "0.10.0"
|
||||
josekit = "0.10.3"
|
||||
jsonpath_lib = { git = "https://github.com/Start9Labs/jsonpath.git" }
|
||||
lazy_async_pool = "0.3.3"
|
||||
lazy_format = "2.0"
|
||||
lazy_static = "1.4.0"
|
||||
lettre = { version = "0.11.18", default-features = false, features = [
|
||||
"aws-lc-rs",
|
||||
"builder",
|
||||
"hostname",
|
||||
"pool",
|
||||
"rustls-platform-verifier",
|
||||
"smtp-transport",
|
||||
"tokio1-rustls",
|
||||
] }
|
||||
libc = "0.2.149"
|
||||
log = "0.4.20"
|
||||
mbrman = "0.6.0"
|
||||
miette = { version = "7.6.0", features = ["fancy"] }
|
||||
mio = "1"
|
||||
new_mime_guess = "4"
|
||||
nix = { version = "0.30.1", features = [
|
||||
"fs",
|
||||
"mount",
|
||||
"net",
|
||||
"process",
|
||||
"sched",
|
||||
"signal",
|
||||
"user",
|
||||
] }
|
||||
nom = "8.0.0"
|
||||
num = "0.4.1"
|
||||
num_cpus = "1.16.0"
|
||||
num_enum = "0.7.0"
|
||||
once_cell = "1.19.0"
|
||||
openssh-keys = "0.6.2"
|
||||
openssl = { version = "0.10.57", features = ["vendored"] }
|
||||
p256 = { version = "0.13.2", features = ["pem"] }
|
||||
patch-db = { version = "*", path = "../../patch-db/patch-db", features = [
|
||||
"trace",
|
||||
] }
|
||||
pbkdf2 = "0.12.2"
|
||||
pin-project = "1.1.3"
|
||||
pkcs8 = { version = "0.10.2", features = ["std"] }
|
||||
prettytable-rs = "0.10.0"
|
||||
proptest = "1.3.1"
|
||||
proptest-derive = "0.7.0"
|
||||
qrcode = "0.14.1"
|
||||
r3bl_tui = "0.7.6"
|
||||
rand = "0.9.2"
|
||||
regex = "1.10.2"
|
||||
reqwest = { version = "0.12.25", features = [
|
||||
"json",
|
||||
"socks",
|
||||
"stream",
|
||||
"http2",
|
||||
] }
|
||||
reqwest_cookie_store = "0.9.0"
|
||||
rpassword = "7.2.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.9.9+spec-1.0.0" }
|
||||
serde_yaml = { package = "serde_yml", version = "0.0.12" }
|
||||
sha-crypt = "0.5.0"
|
||||
sha2 = "0.10.2"
|
||||
signal-hook = "0.3.17"
|
||||
socket2 = { version = "0.6.0", features = ["all"] }
|
||||
socks5-impl = { version = "0.7.2", features = ["client", "server"] }
|
||||
sqlx = { version = "0.8.6", features = [
|
||||
"postgres",
|
||||
"runtime-tokio-rustls",
|
||||
], default-features = false }
|
||||
sscanf = "0.4.1"
|
||||
ssh-key = { version = "0.6.2", features = ["ed25519"] }
|
||||
tar = "0.4.40"
|
||||
termion = "4.0.5"
|
||||
textwrap = "0.16.1"
|
||||
thiserror = "2.0.12"
|
||||
tokio = { version = "1.38.1", features = ["full"] }
|
||||
tokio-rustls = "0.26.4"
|
||||
tokio-stream = { version = "0.1.14", features = ["io-util", "net", "sync"] }
|
||||
tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" }
|
||||
tokio-tungstenite = { version = "0.26.2", features = ["native-tls", "url"] }
|
||||
tokio-util = { version = "0.7.9", features = ["io"] }
|
||||
tor-cell = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
tor-hscrypto = { version = "0.33", features = [
|
||||
"full",
|
||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
tor-hsservice = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
tor-keymgr = { version = "0.33", features = [
|
||||
"ephemeral-keystore",
|
||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
tor-llcrypto = { version = "0.33", features = [
|
||||
"full",
|
||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
tor-proto = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
tor-rtcompat = { version = "0.33", features = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||
torut = "0.2.1"
|
||||
tower-service = "0.3.3"
|
||||
tracing = "0.1.39"
|
||||
tracing-error = "0.2.0"
|
||||
tracing-journald = "0.3.0"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
ts-rs = "9.0.1"
|
||||
typed-builder = "0.23.2"
|
||||
url = { version = "2.4.1", features = ["serde"] }
|
||||
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"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
procfs = "0.18.0"
|
||||
pty-process = "0.5.1"
|
||||
|
||||
[profile.test]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.backtrace]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
opt-level = 3
|
||||
@@ -1,3 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Pem = string
|
||||
export interface SetStoreParams { value: any, path: string, }
|
||||
@@ -0,0 +1,7 @@
|
||||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc dbb4790c31f9e400ed29a9ba2dbd61e3c55ce8a3fbae16601ca3512e803020ed # shrinks to files = []
|
||||
@@ -23,7 +23,7 @@ pub fn action_api<C: Context>() -> ParentHandler<C> {
|
||||
"get-input",
|
||||
from_fn_async(get_action_input)
|
||||
.with_display_serializable()
|
||||
.with_about("about.get-action-input-spec")
|
||||
.with_about("Get action input spec")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -36,14 +36,14 @@ pub fn action_api<C: Context>() -> ParentHandler<C> {
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.with_about("about.run-service-action")
|
||||
.with_about("Run service action")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"clear-task",
|
||||
from_fn_async(clear_task)
|
||||
.no_display()
|
||||
.with_about("about.clear-service-task")
|
||||
.with_about("Clear a service task")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -63,9 +63,7 @@ pub struct ActionInput {
|
||||
#[derive(Deserialize, Serialize, TS, Parser)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetActionInputParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub package_id: PackageId,
|
||||
#[arg(help = "help.arg.action-id")]
|
||||
pub action_id: ActionId,
|
||||
}
|
||||
|
||||
@@ -282,11 +280,8 @@ pub struct RunActionParams {
|
||||
|
||||
#[derive(Parser)]
|
||||
struct CliRunActionParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub package_id: PackageId,
|
||||
#[arg(help = "help.arg.event-id")]
|
||||
pub event_id: Option<Guid>,
|
||||
#[arg(help = "help.arg.action-id")]
|
||||
pub action_id: ActionId,
|
||||
#[command(flatten)]
|
||||
pub input: StdinDeserializable<Option<Value>>,
|
||||
@@ -365,11 +360,9 @@ pub async fn run_action(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ClearTaskParams {
|
||||
#[arg(help = "help.arg.package-id")]
|
||||
pub package_id: PackageId,
|
||||
#[arg(help = "help.arg.replay-id")]
|
||||
pub replay_id: ReplayId,
|
||||
#[arg(long, help = "help.arg.force-clear-task")]
|
||||
#[arg(long)]
|
||||
#[serde(default)]
|
||||
pub force: bool,
|
||||
}
|
||||
@@ -51,10 +51,7 @@ pub async fn write_shadow(password: &str) -> Result<(), Error> {
|
||||
match line.split_once(":") {
|
||||
Some((user, rest)) if user == "start9" || user == "kiosk" => {
|
||||
let (_, rest) = rest.split_once(":").ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("{}", t!("auth.malformed-etc-shadow")),
|
||||
ErrorKind::ParseSysInfo,
|
||||
)
|
||||
Error::new(eyre!("malformed /etc/shadow"), ErrorKind::ParseSysInfo)
|
||||
})?;
|
||||
shadow_file
|
||||
.write_all(format!("{user}:{hash}:{rest}\n").as_bytes())
|
||||
@@ -84,7 +81,7 @@ impl PasswordType {
|
||||
PasswordType::String(x) => Ok(x),
|
||||
PasswordType::EncryptedWire(x) => x.decrypt(current_secret).ok_or_else(|| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("{}", t!("auth.couldnt-decode-password")),
|
||||
color_eyre::eyre::eyre!("Couldn't decode password"),
|
||||
crate::ErrorKind::Unknown,
|
||||
)
|
||||
}),
|
||||
@@ -128,19 +125,19 @@ where
|
||||
"login",
|
||||
from_fn_async(cli_login::<AC>)
|
||||
.no_display()
|
||||
.with_about("about.login-new-auth-session"),
|
||||
.with_about("Log in a new auth session"),
|
||||
)
|
||||
.subcommand(
|
||||
"logout",
|
||||
from_fn_async(logout::<AC>)
|
||||
.with_metadata("get_session", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("about.logout-current-auth-session")
|
||||
.with_about("Log out of current auth session")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"session",
|
||||
session::<C, AC>().with_about("about.list-or-kill-auth-sessions"),
|
||||
session::<C, AC>().with_about("List or kill auth sessions"),
|
||||
)
|
||||
.subcommand(
|
||||
"reset-password",
|
||||
@@ -150,14 +147,14 @@ where
|
||||
"reset-password",
|
||||
from_fn_async(cli_reset_password)
|
||||
.no_display()
|
||||
.with_about("about.reset-password"),
|
||||
.with_about("Reset password"),
|
||||
)
|
||||
.subcommand(
|
||||
"get-pubkey",
|
||||
from_fn_async(get_pubkey)
|
||||
.with_metadata("authenticated", Value::Bool(false))
|
||||
.no_display()
|
||||
.with_about("about.get-pubkey-from-server")
|
||||
.with_about("Get public key derived from server private key")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -187,11 +184,7 @@ async fn cli_login<C: SessionAuthContext>(
|
||||
where
|
||||
CliContext: CallRemote<C>,
|
||||
{
|
||||
let password = if let Ok(password) = std::env::var("PASSWORD") {
|
||||
password
|
||||
} else {
|
||||
rpassword::prompt_password("Password: ")?
|
||||
};
|
||||
let password = rpassword::prompt_password("Password: ")?;
|
||||
|
||||
ctx.call_remote::<C>(
|
||||
&parent_method.into_iter().chain(method).join("."),
|
||||
@@ -211,12 +204,12 @@ pub fn check_password(hash: &str, password: &str) -> Result<(), Error> {
|
||||
ensure_code!(
|
||||
argon2::verify_encoded(&hash, password.as_bytes()).map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("{}", t!("auth.password-incorrect")),
|
||||
eyre!("Password Incorrect"),
|
||||
crate::ErrorKind::IncorrectPassword,
|
||||
)
|
||||
})?,
|
||||
crate::ErrorKind::IncorrectPassword,
|
||||
t!("auth.password-incorrect")
|
||||
"Password Incorrect"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -330,14 +323,14 @@ where
|
||||
.with_metadata("get_session", Value::Bool(true))
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|handle, result| display_sessions(handle.params, result))
|
||||
.with_about("about.display-all-auth-sessions")
|
||||
.with_about("Display all auth sessions")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"kill",
|
||||
from_fn_async(kill::<AC>)
|
||||
.no_display()
|
||||
.with_about("about.terminate-auth-sessions")
|
||||
.with_about("Terminate existing auth session(s)")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -421,7 +414,6 @@ impl AsLogoutSessionId for KillSessionId {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct KillParams {
|
||||
#[arg(help = "help.arg.session-ids")]
|
||||
ids: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -438,9 +430,7 @@ pub async fn kill<C: SessionAuthContext>(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct ResetPasswordParams {
|
||||
#[arg(help = "help.arg.old-password")]
|
||||
old_password: Option<PasswordType>,
|
||||
#[arg(help = "help.arg.new-password")]
|
||||
new_password: Option<PasswordType>,
|
||||
}
|
||||
|
||||
@@ -453,13 +443,13 @@ async fn cli_reset_password(
|
||||
..
|
||||
}: HandlerArgs<CliContext>,
|
||||
) -> Result<(), RpcError> {
|
||||
let old_password = rpassword::prompt_password(&t!("auth.prompt-current-password"))?;
|
||||
let old_password = rpassword::prompt_password("Current Password: ")?;
|
||||
|
||||
let new_password = {
|
||||
let new_password = rpassword::prompt_password(&t!("auth.prompt-new-password"))?;
|
||||
if new_password != rpassword::prompt_password(&t!("auth.prompt-confirm"))? {
|
||||
let new_password = rpassword::prompt_password("New Password: ")?;
|
||||
if new_password != rpassword::prompt_password("Confirm: ")? {
|
||||
return Err(Error::new(
|
||||
eyre!("{}", t!("auth.passwords-do-not-match")),
|
||||
eyre!("Passwords do not match"),
|
||||
crate::ErrorKind::IncorrectPassword,
|
||||
)
|
||||
.into());
|
||||
@@ -492,7 +482,7 @@ pub async fn reset_password_impl(
|
||||
.with_kind(crate::ErrorKind::IncorrectPassword)?
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("{}", t!("auth.password-incorrect")),
|
||||
eyre!("Incorrect Password"),
|
||||
crate::ErrorKind::IncorrectPassword,
|
||||
));
|
||||
}
|
||||
@@ -33,13 +33,11 @@ use crate::version::VersionT;
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct BackupParams {
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
target_id: BackupTargetId,
|
||||
#[arg(long = "old-password", help = "help.arg.old-backup-password")]
|
||||
#[arg(long = "old-password")]
|
||||
old_password: Option<crate::auth::PasswordType>,
|
||||
#[arg(long = "package-ids", help = "help.arg.package-ids-to-backup")]
|
||||
#[arg(long = "package-ids")]
|
||||
package_ids: Option<Vec<PackageId>>,
|
||||
#[arg(help = "help.arg.backup-password")]
|
||||
password: crate::auth::PasswordType,
|
||||
}
|
||||
|
||||
@@ -71,8 +69,8 @@ impl BackupStatusGuard {
|
||||
db,
|
||||
None,
|
||||
NotificationLevel::Success,
|
||||
t!("backup.bulk.complete-title").to_string(),
|
||||
t!("backup.bulk.complete-message").to_string(),
|
||||
"Backup Complete".to_owned(),
|
||||
"Your backup has completed".to_owned(),
|
||||
BackupReport {
|
||||
server: ServerBackupReport {
|
||||
attempted: true,
|
||||
@@ -90,8 +88,9 @@ impl BackupStatusGuard {
|
||||
db,
|
||||
None,
|
||||
NotificationLevel::Warning,
|
||||
t!("backup.bulk.complete-title").to_string(),
|
||||
t!("backup.bulk.complete-with-failures").to_string(),
|
||||
"Backup Complete".to_owned(),
|
||||
"Your backup has completed, but some package(s) failed to backup"
|
||||
.to_owned(),
|
||||
BackupReport {
|
||||
server: ServerBackupReport {
|
||||
attempted: true,
|
||||
@@ -104,7 +103,7 @@ impl BackupStatusGuard {
|
||||
.await
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("{}", t!("backup.bulk.failed-error", error = e));
|
||||
tracing::error!("Backup Failed: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
let err_string = e.to_string();
|
||||
db.mutate(|db| {
|
||||
@@ -112,8 +111,8 @@ impl BackupStatusGuard {
|
||||
db,
|
||||
None,
|
||||
NotificationLevel::Error,
|
||||
t!("backup.bulk.failed-title").to_string(),
|
||||
t!("backup.bulk.failed-message").to_string(),
|
||||
"Backup Failed".to_owned(),
|
||||
"Your backup failed to complete.".to_owned(),
|
||||
BackupReport {
|
||||
server: ServerBackupReport {
|
||||
attempted: true,
|
||||
@@ -225,7 +224,7 @@ fn assure_backing_up<'a>(
|
||||
.as_backup_progress_mut();
|
||||
if backing_up.transpose_ref().is_some() {
|
||||
return Err(Error::new(
|
||||
eyre!("{}", t!("backup.bulk.already-backing-up")),
|
||||
eyre!("Server is already backing up!"),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
@@ -304,7 +303,7 @@ async fn perform_backup(
|
||||
|
||||
let mut backup_guard = Arc::try_unwrap(backup_guard).map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("{}", t!("backup.bulk.leaked-reference")),
|
||||
eyre!("leaked reference to BackupMountGuard"),
|
||||
ErrorKind::Incoherent,
|
||||
)
|
||||
})?;
|
||||
@@ -37,12 +37,12 @@ pub fn backup<C: Context>() -> ParentHandler<C> {
|
||||
"create",
|
||||
from_fn_async(backup_bulk::backup_all)
|
||||
.no_display()
|
||||
.with_about("about.create-backup-all-packages")
|
||||
.with_about("Create backup for all packages")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"target",
|
||||
target::target::<C>().with_about("about.commands-backup-target"),
|
||||
target::target::<C>().with_about("Commands related to a backup target"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ pub fn package_backup<C: Context>() -> ParentHandler<C> {
|
||||
"restore",
|
||||
from_fn_async(restore::restore_packages_rpc)
|
||||
.no_display()
|
||||
.with_about("about.restore-packages-from-backup")
|
||||
.with_about("Restore package(s) from backup")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -23,19 +23,16 @@ use crate::progress::ProgressUnits;
|
||||
use crate::s9pk::S9pk;
|
||||
use crate::service::service_map::DownloadInstallFuture;
|
||||
use crate::setup::SetupExecuteProgress;
|
||||
use crate::system::{save_language, sync_kiosk};
|
||||
use crate::util::serde::{IoFormat, Pem};
|
||||
use crate::system::sync_kiosk;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::{PLATFORM, PackageId};
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct RestorePackageParams {
|
||||
#[arg(help = "help.arg.package-ids")]
|
||||
pub ids: Vec<PackageId>,
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
pub target_id: BackupTargetId,
|
||||
#[arg(help = "help.arg.backup-password")]
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
@@ -66,10 +63,7 @@ pub async fn restore_packages_rpc(
|
||||
match async { res.await?.await }.await {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("backup.restore.package-error", id = id, error = err)
|
||||
);
|
||||
tracing::error!("Error restoring package {}: {}", id, err);
|
||||
tracing::debug!("{:?}", err);
|
||||
}
|
||||
}
|
||||
@@ -81,10 +75,10 @@ pub async fn restore_packages_rpc(
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn recover_full_server(
|
||||
pub async fn recover_full_embassy(
|
||||
ctx: &SetupContext,
|
||||
disk_guid: InternedString,
|
||||
password: String,
|
||||
disk_guid: Arc<String>,
|
||||
start_os_password: String,
|
||||
recovery_source: TmpMountGuard,
|
||||
server_id: &str,
|
||||
recovery_password: &str,
|
||||
@@ -108,7 +102,7 @@ pub async fn recover_full_server(
|
||||
)?;
|
||||
|
||||
os_backup.account.password = argon2::hash_encoded(
|
||||
password.as_bytes(),
|
||||
start_os_password.as_bytes(),
|
||||
&rand::random::<[u8; 16]>()[..],
|
||||
&argon2::Config::rfc9106_low_mem(),
|
||||
)
|
||||
@@ -117,32 +111,16 @@ pub async fn recover_full_server(
|
||||
let kiosk = Some(kiosk.unwrap_or(true)).filter(|_| &*PLATFORM != "raspberrypi");
|
||||
sync_kiosk(kiosk).await?;
|
||||
|
||||
let language = ctx.language.peek(|a| a.clone());
|
||||
let keyboard = ctx.keyboard.peek(|a| a.clone());
|
||||
|
||||
if let Some(language) = &language {
|
||||
save_language(&**language).await?;
|
||||
}
|
||||
|
||||
if let Some(keyboard) = &keyboard {
|
||||
keyboard.save().await?;
|
||||
}
|
||||
|
||||
let db = ctx.db().await?;
|
||||
db.put(
|
||||
&ROOT,
|
||||
&Database::init(&os_backup.account, kiosk, language, keyboard)?,
|
||||
)
|
||||
.await?;
|
||||
db.put(&ROOT, &Database::init(&os_backup.account, kiosk)?)
|
||||
.await?;
|
||||
drop(db);
|
||||
|
||||
let config = ctx.config.peek(|c| c.clone());
|
||||
|
||||
let init_result = init(&ctx.webserver, &config, init_phases).await?;
|
||||
let init_result = init(&ctx.webserver, &ctx.config, init_phases).await?;
|
||||
|
||||
let rpc_ctx = RpcContext::init(
|
||||
&ctx.webserver,
|
||||
&config,
|
||||
&ctx.config,
|
||||
disk_guid.clone(),
|
||||
Some(init_result),
|
||||
rpc_ctx_phases,
|
||||
@@ -167,10 +145,7 @@ pub async fn recover_full_server(
|
||||
match async { res.await?.await }.await {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!("backup.restore.package-error", id = id, error = err)
|
||||
);
|
||||
tracing::error!("Error restoring package {}: {}", id, err);
|
||||
tracing::debug!("{:?}", err);
|
||||
}
|
||||
}
|
||||
@@ -180,14 +155,7 @@ pub async fn recover_full_server(
|
||||
.await;
|
||||
restore_phase.lock().await.complete();
|
||||
|
||||
Ok((
|
||||
SetupResult {
|
||||
hostname: os_backup.account.hostname,
|
||||
root_ca: Pem(os_backup.account.root_ca_cert),
|
||||
needs_restart: ctx.install_rootfs.peek(|a| a.is_some()),
|
||||
},
|
||||
rpc_ctx,
|
||||
))
|
||||
Ok(((&os_backup.account).try_into()?, rpc_ctx))
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, backup_guard))]
|
||||
@@ -52,21 +52,21 @@ pub fn cifs<C: Context>() -> ParentHandler<C> {
|
||||
"add",
|
||||
from_fn_async(add)
|
||||
.no_display()
|
||||
.with_about("about.add-new-backup-target")
|
||||
.with_about("Add a new backup target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"update",
|
||||
from_fn_async(update)
|
||||
.no_display()
|
||||
.with_about("about.update-existing-backup-target")
|
||||
.with_about("Update an existing backup target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"remove",
|
||||
from_fn_async(remove)
|
||||
.no_display()
|
||||
.with_about("about.remove-existing-backup-target")
|
||||
.with_about("Remove an existing backup target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -75,13 +75,9 @@ pub fn cifs<C: Context>() -> ParentHandler<C> {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct AddParams {
|
||||
#[arg(help = "help.arg.cifs-hostname")]
|
||||
pub hostname: String,
|
||||
#[arg(help = "help.arg.cifs-path")]
|
||||
pub path: PathBuf,
|
||||
#[arg(help = "help.arg.cifs-username")]
|
||||
pub username: String,
|
||||
#[arg(help = "help.arg.cifs-password")]
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
@@ -134,15 +130,10 @@ pub async fn add(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct UpdateParams {
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
pub id: BackupTargetId,
|
||||
#[arg(help = "help.arg.cifs-hostname")]
|
||||
pub hostname: String,
|
||||
#[arg(help = "help.arg.cifs-path")]
|
||||
pub path: PathBuf,
|
||||
#[arg(help = "help.arg.cifs-username")]
|
||||
pub username: String,
|
||||
#[arg(help = "help.arg.cifs-password")]
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
@@ -160,7 +151,7 @@ pub async fn update(
|
||||
id
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("{}", t!("backup.target.cifs.target-not-found", id = id)),
|
||||
eyre!("Backup Target ID {} Not Found", id),
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
@@ -180,13 +171,7 @@ pub async fn update(
|
||||
.as_idx_mut(&id)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!(
|
||||
"{}",
|
||||
t!(
|
||||
"backup.target.cifs.target-not-found",
|
||||
id = BackupTargetId::Cifs { id }
|
||||
)
|
||||
),
|
||||
eyre!("Backup Target ID {} Not Found", BackupTargetId::Cifs { id }),
|
||||
ErrorKind::NotFound,
|
||||
)
|
||||
})?
|
||||
@@ -210,7 +195,6 @@ pub async fn update(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct RemoveParams {
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
pub id: BackupTargetId,
|
||||
}
|
||||
|
||||
@@ -219,7 +203,7 @@ pub async fn remove(ctx: RpcContext, RemoveParams { id }: RemoveParams) -> Resul
|
||||
id
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("{}", t!("backup.target.cifs.target-not-found", id = id)),
|
||||
eyre!("Backup Target ID {} Not Found", id),
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
@@ -236,7 +220,7 @@ pub fn load(db: &DatabaseModel, id: u32) -> Result<Cifs, Error> {
|
||||
.as_idx(&id)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("{}", t!("backup.target.cifs.target-not-found-id", id = id)),
|
||||
eyre!("Backup Target ID {} Not Found", id),
|
||||
ErrorKind::NotFound,
|
||||
)
|
||||
})?
|
||||
@@ -143,13 +143,13 @@ pub fn target<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"cifs",
|
||||
cifs::cifs::<C>().with_about("about.add-remove-update-backup-target"),
|
||||
cifs::cifs::<C>().with_about("Add, remove, or update a backup target"),
|
||||
)
|
||||
.subcommand(
|
||||
"list",
|
||||
from_fn_async(list)
|
||||
.with_display_serializable()
|
||||
.with_about("about.list-existing-backup-targets")
|
||||
.with_about("List existing backup targets")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -159,20 +159,20 @@ pub fn target<C: Context>() -> ParentHandler<C> {
|
||||
.with_custom_display_fn::<CliContext, _>(|params, info| {
|
||||
display_backup_info(params.params, info)
|
||||
})
|
||||
.with_about("about.display-package-backup-information")
|
||||
.with_about("Display package backup information")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"mount",
|
||||
from_fn_async(mount)
|
||||
.with_about("about.mount-backup-target")
|
||||
.with_about("Mount backup target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"umount",
|
||||
from_fn_async(umount)
|
||||
.no_display()
|
||||
.with_about("about.unmount-backup-target")
|
||||
.with_about("Unmount backup target")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
}
|
||||
@@ -268,11 +268,8 @@ fn display_backup_info(params: WithIoFormat<InfoParams>, info: BackupInfo) -> Re
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct InfoParams {
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
target_id: BackupTargetId,
|
||||
#[arg(help = "help.arg.server-id")]
|
||||
server_id: String,
|
||||
#[arg(help = "help.arg.backup-password")]
|
||||
password: String,
|
||||
}
|
||||
|
||||
@@ -308,13 +305,11 @@ lazy_static::lazy_static! {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct MountParams {
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
target_id: BackupTargetId,
|
||||
#[arg(long, help = "help.arg.server-id")]
|
||||
#[arg(long)]
|
||||
server_id: Option<String>,
|
||||
#[arg(help = "help.arg.backup-password")]
|
||||
password: String, // TODO: rpassword
|
||||
#[arg(long, help = "help.arg.allow-partial-backup")]
|
||||
#[arg(long)]
|
||||
allow_partial: bool,
|
||||
}
|
||||
|
||||
@@ -390,7 +385,6 @@ pub async fn mount(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct UmountParams {
|
||||
#[arg(help = "help.arg.backup-target-id")]
|
||||
target_id: Option<BackupTargetId>,
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||
|cfg: ContainerClientConfig| Ok(ContainerCliContext::init(cfg)),
|
||||
crate::service::effects::handler(),
|
||||
)
|
||||
.mutate_command(super::translate_cli)
|
||||
.run(args)
|
||||
{
|
||||
match e.data {
|
||||
9
core/startos/src/bins/deprecated.rs
Normal file
9
core/startos/src/bins/deprecated.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub fn renamed(old: &str, new: &str) -> ! {
|
||||
eprintln!("{old} has been renamed to {new}");
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
pub fn removed(name: &str) -> ! {
|
||||
eprintln!("{name} has been removed");
|
||||
std::process::exit(1)
|
||||
}
|
||||
85
core/startos/src/bins/mod.rs
Normal file
85
core/startos/src/bins/mod.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::ffi::OsString;
|
||||
use std::path::Path;
|
||||
|
||||
pub mod container_cli;
|
||||
pub mod deprecated;
|
||||
pub mod registry;
|
||||
pub mod start_cli;
|
||||
pub mod start_init;
|
||||
pub mod startd;
|
||||
pub mod tunnel;
|
||||
|
||||
#[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
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ use std::ffi::OsString;
|
||||
use clap::Parser;
|
||||
use futures::FutureExt;
|
||||
use rpc_toolkit::CliApp;
|
||||
use rust_i18n::t;
|
||||
use tokio::signal::unix::signal;
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -78,7 +77,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect(&t!("bins.registry.failed-to-initialize-runtime"));
|
||||
.expect("failed to initialize runtime");
|
||||
rt.block_on(inner_main(&config))
|
||||
};
|
||||
|
||||
@@ -100,7 +99,6 @@ pub fn cli(args: impl IntoIterator<Item = OsString>) {
|
||||
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||
crate::registry::registry_api(),
|
||||
)
|
||||
.mutate_command(super::translate_cli)
|
||||
.run(args)
|
||||
{
|
||||
match e.data {
|
||||
@@ -19,7 +19,6 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||
crate::main_api(),
|
||||
)
|
||||
.mutate_command(super::translate_cli)
|
||||
.run(args)
|
||||
{
|
||||
match e.data {
|
||||
@@ -1,9 +1,11 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::config::ServerConfig;
|
||||
use crate::context::rpc::InitRpcContextPhases;
|
||||
use crate::context::{DiagnosticContext, InitContext, RpcContext, SetupContext};
|
||||
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
||||
use crate::disk::REPAIR_DISK_PATH;
|
||||
use crate::disk::fsck::RepairStrategy;
|
||||
use crate::disk::main::DEFAULT_PASSWORD;
|
||||
@@ -25,13 +27,7 @@ async fn setup_or_init(
|
||||
if let Some(firmware) = check_for_firmware_update()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"bins.start-init.error-checking-firmware",
|
||||
error = e.to_string()
|
||||
)
|
||||
);
|
||||
tracing::warn!("Error checking for firmware update: {e}");
|
||||
tracing::debug!("{e:?}");
|
||||
})
|
||||
.ok()
|
||||
@@ -39,21 +35,14 @@ async fn setup_or_init(
|
||||
{
|
||||
let init_ctx = InitContext::init(config).await?;
|
||||
let handle = &init_ctx.progress;
|
||||
let mut update_phase =
|
||||
handle.add_phase(t!("bins.start-init.updating-firmware").into(), Some(10));
|
||||
let mut reboot_phase = handle.add_phase(t!("bins.start-init.rebooting").into(), Some(1));
|
||||
let mut update_phase = handle.add_phase("Updating Firmware".into(), Some(10));
|
||||
let mut reboot_phase = handle.add_phase("Rebooting".into(), Some(1));
|
||||
|
||||
server.serve_ui_for(init_ctx);
|
||||
|
||||
update_phase.start();
|
||||
if let Err(e) = update_firmware(firmware).await {
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
t!(
|
||||
"bins.start-init.error-firmware-update",
|
||||
error = e.to_string()
|
||||
)
|
||||
);
|
||||
tracing::warn!("Error performing firmware update: {e}");
|
||||
tracing::debug!("{e:?}");
|
||||
} else {
|
||||
update_phase.complete();
|
||||
@@ -90,11 +79,40 @@ async fn setup_or_init(
|
||||
.invoke(crate::ErrorKind::OpenSsl)
|
||||
.await?;
|
||||
|
||||
if tokio::fs::metadata("/run/live/medium").await.is_ok() {
|
||||
Command::new("sed")
|
||||
.arg("-i")
|
||||
.arg("s/PasswordAuthentication no/PasswordAuthentication yes/g")
|
||||
.arg("/etc/ssh/sshd_config")
|
||||
.invoke(crate::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
Command::new("systemctl")
|
||||
.arg("reload")
|
||||
.arg("ssh")
|
||||
.invoke(crate::ErrorKind::OpenSsh)
|
||||
.await?;
|
||||
|
||||
let ctx = InstallContext::init().await?;
|
||||
|
||||
server.serve_ui_for(ctx.clone());
|
||||
|
||||
ctx.shutdown
|
||||
.subscribe()
|
||||
.recv()
|
||||
.await
|
||||
.expect("context dropped");
|
||||
|
||||
return Ok(Err(Shutdown {
|
||||
disk_guid: None,
|
||||
restart: true,
|
||||
}));
|
||||
}
|
||||
|
||||
if tokio::fs::metadata("/media/startos/config/disk.guid")
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
let ctx = SetupContext::init(server, config.clone())?;
|
||||
let ctx = SetupContext::init(server, config)?;
|
||||
|
||||
server.serve_ui_for(ctx.clone());
|
||||
|
||||
@@ -109,13 +127,7 @@ async fn setup_or_init(
|
||||
.invoke(ErrorKind::NotFound)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"{}",
|
||||
t!(
|
||||
"bins.start-init.failed-to-kill-kiosk",
|
||||
error = e.to_string()
|
||||
)
|
||||
);
|
||||
tracing::error!("Failed to kill kiosk: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
|
||||
@@ -124,7 +136,7 @@ async fn setup_or_init(
|
||||
Some(Err(e)) => return Err(e.clone_output()),
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
eyre!("{}", t!("bins.start-init.setup-mode-exited")),
|
||||
eyre!("Setup mode exited before setup completed"),
|
||||
ErrorKind::Unknown,
|
||||
));
|
||||
}
|
||||
@@ -134,8 +146,7 @@ async fn setup_or_init(
|
||||
let handle = init_ctx.progress.clone();
|
||||
let err_channel = init_ctx.error.clone();
|
||||
|
||||
let mut disk_phase =
|
||||
handle.add_phase(t!("bins.start-init.opening-data-drive").into(), Some(10));
|
||||
let mut disk_phase = handle.add_phase("Opening data drive".into(), Some(10));
|
||||
let init_phases = InitPhases::new(&handle);
|
||||
let rpc_ctx_phases = InitRpcContextPhases::new(&handle);
|
||||
|
||||
@@ -145,9 +156,9 @@ async fn setup_or_init(
|
||||
disk_phase.start();
|
||||
let guid_string = tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
||||
.await?;
|
||||
let disk_guid = InternedString::intern(guid_string.trim());
|
||||
let disk_guid = Arc::new(String::from(guid_string.trim()));
|
||||
let requires_reboot = crate::disk::main::import(
|
||||
&*disk_guid,
|
||||
&**disk_guid,
|
||||
DATA_DIR,
|
||||
if tokio::fs::metadata(REPAIR_DISK_PATH).await.is_ok() {
|
||||
RepairStrategy::Aggressive
|
||||
@@ -167,12 +178,11 @@ async fn setup_or_init(
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, REPAIR_DISK_PATH))?;
|
||||
}
|
||||
disk_phase.complete();
|
||||
tracing::info!("{}", t!("bins.start-init.loaded-disk"));
|
||||
tracing::info!("Loaded Disk");
|
||||
|
||||
if requires_reboot.0 {
|
||||
tracing::info!("{}", t!("bins.start-init.rebooting"));
|
||||
let mut reboot_phase =
|
||||
handle.add_phase(t!("bins.start-init.rebooting").into(), Some(1));
|
||||
tracing::info!("Rebooting...");
|
||||
let mut reboot_phase = handle.add_phase("Rebooting".into(), Some(1));
|
||||
reboot_phase.start();
|
||||
return Ok(Err(Shutdown {
|
||||
disk_guid: Some(disk_guid),
|
||||
@@ -226,10 +236,11 @@ pub async fn main(
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
Some(InternedString::intern(
|
||||
Some(Arc::new(
|
||||
tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
||||
.await?
|
||||
.trim(),
|
||||
.trim()
|
||||
.to_owned(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::cmp::max;
|
||||
use std::ffi::OsString;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
use rust_i18n::t;
|
||||
use tokio::signal::unix::signal;
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -15,11 +15,11 @@ use crate::context::{DiagnosticContext, InitContext, RpcContext};
|
||||
use crate::net::gateway::{BindTcp, SelfContainedNetworkInterfaceListener, UpgradableListener};
|
||||
use crate::net::static_server::refresher;
|
||||
use crate::net::web_server::{Acceptor, WebServer};
|
||||
use crate::prelude::*;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::system::launch_metrics_task;
|
||||
use crate::util::io::append_file;
|
||||
use crate::util::logger::LOGGER;
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn inner_main(
|
||||
@@ -53,10 +53,11 @@ async fn inner_main(
|
||||
let ctx = RpcContext::init(
|
||||
&server.acceptor_setter(),
|
||||
config,
|
||||
InternedString::intern(
|
||||
Arc::new(
|
||||
tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
||||
.await?
|
||||
.trim(),
|
||||
.trim()
|
||||
.to_owned(),
|
||||
),
|
||||
None,
|
||||
rpc_ctx_phases,
|
||||
@@ -113,11 +114,11 @@ async fn inner_main(
|
||||
metrics_task
|
||||
.map_err(|e| {
|
||||
Error::new(
|
||||
eyre!("{}", e).wrap_err(t!("bins.startd.metrics-daemon-panicked").to_string()),
|
||||
eyre!("{}", e).wrap_err("Metrics daemon panicked!"),
|
||||
ErrorKind::Unknown,
|
||||
)
|
||||
})
|
||||
.map_ok(|_| tracing::debug!("{}", t!("bins.startd.metrics-daemon-shutdown")))
|
||||
.map_ok(|_| tracing::debug!("Metrics daemon Shutdown"))
|
||||
.await?;
|
||||
|
||||
let shutdown = shutdown_recv
|
||||
@@ -145,7 +146,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||
.worker_threads(max(1, num_cpus::get()))
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect(&t!("bins.startd.failed-to-initialize-runtime"));
|
||||
.expect("failed to initialize runtime");
|
||||
let res = rt.block_on(async {
|
||||
let mut server = WebServer::new(
|
||||
Acceptor::bind_upgradable(SelfContainedNetworkInterfaceListener::bind(BindTcp, 80)),
|
||||
@@ -166,10 +167,11 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
Some(InternedString::intern(
|
||||
Some(Arc::new(
|
||||
tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy
|
||||
.await?
|
||||
.trim(),
|
||||
.trim()
|
||||
.to_owned(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
@@ -6,7 +6,6 @@ use std::time::Duration;
|
||||
use clap::Parser;
|
||||
use futures::FutureExt;
|
||||
use rpc_toolkit::CliApp;
|
||||
use rust_i18n::t;
|
||||
use tokio::signal::unix::signal;
|
||||
use tracing::instrument;
|
||||
use visit_rs::Visit;
|
||||
@@ -71,7 +70,7 @@ async fn inner_main(config: &TunnelConfig) -> Result<(), Error> {
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("{}", t!("bins.tunnel.error-adding-ssl-listener", error = e.to_string()));
|
||||
tracing::error!("error adding ssl listener: {e}");
|
||||
tracing::debug!("{e:?}");
|
||||
|
||||
false
|
||||
@@ -93,7 +92,7 @@ async fn inner_main(config: &TunnelConfig) -> Result<(), Error> {
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("{}", t!("bins.tunnel.error-updating-webserver-bind", error = e.to_string()));
|
||||
tracing::error!("error updating webserver bind: {e}");
|
||||
tracing::debug!("{e:?}");
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
}
|
||||
@@ -158,7 +157,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect(&t!("bins.tunnel.failed-to-initialize-runtime"));
|
||||
.expect("failed to initialize runtime");
|
||||
rt.block_on(inner_main(&config))
|
||||
};
|
||||
|
||||
@@ -180,7 +179,6 @@ pub fn cli(args: impl IntoIterator<Item = OsString>) {
|
||||
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||
crate::tunnel::api::tunnel_api(),
|
||||
)
|
||||
.mutate_command(super::translate_cli)
|
||||
.run(args)
|
||||
{
|
||||
match e.data {
|
||||
@@ -7,7 +7,6 @@ use std::sync::Arc;
|
||||
use cookie::{Cookie, Expiration, SameSite};
|
||||
use cookie_store::CookieStore;
|
||||
use http::HeaderMap;
|
||||
use imbl::OrdMap;
|
||||
use imbl_value::InternedString;
|
||||
use josekit::jwk::Jwk;
|
||||
use once_cell::sync::OnceCell;
|
||||
@@ -23,7 +22,7 @@ use tracing::instrument;
|
||||
|
||||
use super::setup::CURRENT_SECRET;
|
||||
use crate::context::config::{ClientConfig, local_config_path};
|
||||
use crate::context::{DiagnosticContext, InitContext, RpcContext, SetupContext};
|
||||
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
||||
use crate::developer::{OS_DEVELOPER_KEY_PATH, default_developer_key_path};
|
||||
use crate::middleware::auth::local::LocalAuthContext;
|
||||
use crate::prelude::*;
|
||||
@@ -38,8 +37,6 @@ pub struct CliContextSeed {
|
||||
pub registry_url: Option<Url>,
|
||||
pub registry_hostname: Vec<InternedString>,
|
||||
pub registry_listen: Option<SocketAddr>,
|
||||
pub s9pk_s3base: Option<Url>,
|
||||
pub s9pk_s3bucket: Option<InternedString>,
|
||||
pub tunnel_addr: Option<SocketAddr>,
|
||||
pub tunnel_listen: Option<SocketAddr>,
|
||||
pub client: Client,
|
||||
@@ -131,8 +128,6 @@ impl CliContext {
|
||||
.transpose()?,
|
||||
registry_hostname: config.registry_hostname.unwrap_or_default(),
|
||||
registry_listen: config.registry_listen,
|
||||
s9pk_s3base: config.s9pk_s3base,
|
||||
s9pk_s3bucket: config.s9pk_s3bucket,
|
||||
tunnel_addr: config.tunnel,
|
||||
tunnel_listen: config.tunnel_listen,
|
||||
client: {
|
||||
@@ -164,23 +159,21 @@ impl CliContext {
|
||||
if !path.exists() {
|
||||
continue;
|
||||
}
|
||||
let pair =
|
||||
<ed25519::KeypairBytes as ed25519::pkcs8::DecodePrivateKey>::from_pkcs8_pem(
|
||||
&std::fs::read_to_string(path)?,
|
||||
let pair = <ed25519::KeypairBytes as ed25519::pkcs8::DecodePrivateKey>::from_pkcs8_pem(
|
||||
&std::fs::read_to_string(path)?,
|
||||
)
|
||||
.with_kind(crate::ErrorKind::Pem)?;
|
||||
let secret = ed25519_dalek::SecretKey::try_from(&pair.secret_key[..]).map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("pkcs8 key is of incorrect length"),
|
||||
ErrorKind::OpenSsl,
|
||||
)
|
||||
.with_kind(crate::ErrorKind::Pem)?;
|
||||
let secret =
|
||||
ed25519_dalek::SecretKey::try_from(&pair.secret_key[..]).map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("{}", t!("context.cli.pkcs8-key-incorrect-length")),
|
||||
ErrorKind::OpenSsl,
|
||||
)
|
||||
})?;
|
||||
return Ok(secret.into());
|
||||
})?;
|
||||
return Ok(secret.into())
|
||||
}
|
||||
Err(Error::new(
|
||||
eyre!("{}", t!("context.cli.developer-key-does-not-exist")),
|
||||
crate::ErrorKind::Uninitialized,
|
||||
eyre!("Developer Key does not exist! Please run `start-cli init-key` before running this command."),
|
||||
crate::ErrorKind::Uninitialized
|
||||
))
|
||||
})
|
||||
}
|
||||
@@ -195,18 +188,14 @@ impl CliContext {
|
||||
"http" => "ws",
|
||||
_ => {
|
||||
return Err(Error::new(
|
||||
eyre!("{}", t!("context.cli.cannot-parse-scheme-from-base-url")),
|
||||
eyre!("Cannot parse scheme from base URL"),
|
||||
crate::ErrorKind::ParseUrl,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
};
|
||||
url.set_scheme(ws_scheme).map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("{}", t!("context.cli.cannot-set-url-scheme")),
|
||||
crate::ErrorKind::ParseUrl,
|
||||
)
|
||||
})?;
|
||||
url.set_scheme(ws_scheme)
|
||||
.map_err(|_| Error::new(eyre!("Cannot set URL scheme"), crate::ErrorKind::ParseUrl))?;
|
||||
url.path_segments_mut()
|
||||
.map_err(|_| eyre!("Url cannot be base"))
|
||||
.with_kind(crate::ErrorKind::ParseUrl)?
|
||||
@@ -249,16 +238,10 @@ impl CliContext {
|
||||
where
|
||||
Self: CallRemote<RemoteContext>,
|
||||
{
|
||||
<Self as CallRemote<RemoteContext, Empty>>::call_remote(
|
||||
&self,
|
||||
method,
|
||||
OrdMap::new(),
|
||||
params,
|
||||
Empty {},
|
||||
)
|
||||
.await
|
||||
.map_err(Error::from)
|
||||
.with_ctx(|e| (e.kind, method))
|
||||
<Self as CallRemote<RemoteContext, Empty>>::call_remote(&self, method, params, Empty {})
|
||||
.await
|
||||
.map_err(Error::from)
|
||||
.with_ctx(|e| (e.kind, method))
|
||||
}
|
||||
pub async fn call_remote_with<RemoteContext, T>(
|
||||
&self,
|
||||
@@ -269,16 +252,10 @@ impl CliContext {
|
||||
where
|
||||
Self: CallRemote<RemoteContext, T>,
|
||||
{
|
||||
<Self as CallRemote<RemoteContext, T>>::call_remote(
|
||||
&self,
|
||||
method,
|
||||
OrdMap::new(),
|
||||
params,
|
||||
extra,
|
||||
)
|
||||
.await
|
||||
.map_err(Error::from)
|
||||
.with_ctx(|e| (e.kind, method))
|
||||
<Self as CallRemote<RemoteContext, T>>::call_remote(&self, method, params, extra)
|
||||
.await
|
||||
.map_err(Error::from)
|
||||
.with_ctx(|e| (e.kind, method))
|
||||
}
|
||||
}
|
||||
impl AsRef<Jwk> for CliContext {
|
||||
@@ -315,13 +292,7 @@ impl AsRef<Client> for CliContext {
|
||||
}
|
||||
|
||||
impl CallRemote<RpcContext> for CliContext {
|
||||
async fn call_remote(
|
||||
&self,
|
||||
method: &str,
|
||||
_: OrdMap<&'static str, Value>,
|
||||
params: Value,
|
||||
_: Empty,
|
||||
) -> Result<Value, RpcError> {
|
||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||
if let Ok(local) = read_file_to_string(RpcContext::LOCAL_AUTH_COOKIE_PATH).await {
|
||||
self.cookie_store
|
||||
.lock()
|
||||
@@ -348,13 +319,7 @@ impl CallRemote<RpcContext> for CliContext {
|
||||
}
|
||||
}
|
||||
impl CallRemote<DiagnosticContext> for CliContext {
|
||||
async fn call_remote(
|
||||
&self,
|
||||
method: &str,
|
||||
_: OrdMap<&'static str, Value>,
|
||||
params: Value,
|
||||
_: Empty,
|
||||
) -> Result<Value, RpcError> {
|
||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||
crate::middleware::auth::signature::call_remote(
|
||||
self,
|
||||
self.rpc_url.clone(),
|
||||
@@ -367,13 +332,7 @@ impl CallRemote<DiagnosticContext> for CliContext {
|
||||
}
|
||||
}
|
||||
impl CallRemote<InitContext> for CliContext {
|
||||
async fn call_remote(
|
||||
&self,
|
||||
method: &str,
|
||||
_: OrdMap<&'static str, Value>,
|
||||
params: Value,
|
||||
_: Empty,
|
||||
) -> Result<Value, RpcError> {
|
||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||
crate::middleware::auth::signature::call_remote(
|
||||
self,
|
||||
self.rpc_url.clone(),
|
||||
@@ -386,13 +345,20 @@ impl CallRemote<InitContext> for CliContext {
|
||||
}
|
||||
}
|
||||
impl CallRemote<SetupContext> for CliContext {
|
||||
async fn call_remote(
|
||||
&self,
|
||||
method: &str,
|
||||
_: OrdMap<&'static str, Value>,
|
||||
params: Value,
|
||||
_: Empty,
|
||||
) -> Result<Value, RpcError> {
|
||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||
crate::middleware::auth::signature::call_remote(
|
||||
self,
|
||||
self.rpc_url.clone(),
|
||||
HeaderMap::new(),
|
||||
self.rpc_url.host_str(),
|
||||
method,
|
||||
params,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
impl CallRemote<InstallContext> for CliContext {
|
||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||
crate::middleware::auth::signature::call_remote(
|
||||
self,
|
||||
self.rpc_url.clone(),
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user