mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
Compare commits
15 Commits
v0.4.0-alp
...
v0.4.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24eb27f005 | ||
|
|
009d76ea35 | ||
|
|
6e8a425eb1 | ||
|
|
66188d791b | ||
|
|
015ff02d71 | ||
|
|
10bfaf5415 | ||
|
|
e3e0b85e0c | ||
|
|
ad0632892e | ||
|
|
f26791ba39 | ||
|
|
2fbaaebf44 | ||
|
|
edb916338c | ||
|
|
f7e947d37d | ||
|
|
a9e3d1ed75 | ||
|
|
ce97827c42 | ||
|
|
3efec07338 |
100
.github/workflows/start-tunnel.yaml
vendored
Normal file
100
.github/workflows/start-tunnel.yaml
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
name: Start-Tunnel
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
environment:
|
||||||
|
type: choice
|
||||||
|
description: Environment
|
||||||
|
options:
|
||||||
|
- NONE
|
||||||
|
- dev
|
||||||
|
- unstable
|
||||||
|
- dev-unstable
|
||||||
|
runner:
|
||||||
|
type: choice
|
||||||
|
description: Runner
|
||||||
|
options:
|
||||||
|
- standard
|
||||||
|
- fast
|
||||||
|
arch:
|
||||||
|
type: choice
|
||||||
|
description: Architecture
|
||||||
|
options:
|
||||||
|
- ALL
|
||||||
|
- x86_64
|
||||||
|
- aarch64
|
||||||
|
- riscv64
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next/*
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next/*
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODEJS_VERSION: "24.11.0"
|
||||||
|
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
compile:
|
||||||
|
name: Build Debian Package
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
arch: >-
|
||||||
|
${{
|
||||||
|
fromJson('{
|
||||||
|
"x86_64": ["x86_64"],
|
||||||
|
"aarch64": ["aarch64"],
|
||||||
|
"riscv64": ["riscv64"],
|
||||||
|
"ALL": ["x86_64", "aarch64", "riscv64"]
|
||||||
|
}')[github.event.inputs.platform || 'ALL']
|
||||||
|
}}
|
||||||
|
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||||
|
steps:
|
||||||
|
- name: Cleaning up unnecessary files
|
||||||
|
run: |
|
||||||
|
sudo apt-get remove --purge -y google-chrome-stable firefox mono-devel
|
||||||
|
sudo apt-get autoremove -y
|
||||||
|
sudo apt-get clean
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
sudo mount -t tmpfs tmpfs .
|
||||||
|
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODEJS_VERSION }}
|
||||||
|
|
||||||
|
- name: Set up docker QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Configure sccache
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
|
||||||
|
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||||
|
|
||||||
|
- name: Make
|
||||||
|
run: make tunnel-deb
|
||||||
|
env:
|
||||||
|
PLATFORM: ${{ matrix.arch }}
|
||||||
|
SCCACHE_GHA_ENABLED: on
|
||||||
|
SCCACHE_GHA_VERSION: 0
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: start-tunnel_${{ matrix.arch }}.deb
|
||||||
|
path: results/start-tunnel-*_${{ matrix.arch }}.deb
|
||||||
17
.github/workflows/startos-iso.yaml
vendored
17
.github/workflows/startos-iso.yaml
vendored
@@ -67,8 +67,13 @@ jobs:
|
|||||||
"ALL": ["x86_64", "aarch64"]
|
"ALL": ["x86_64", "aarch64"]
|
||||||
}')[github.event.inputs.platform || 'ALL']
|
}')[github.event.inputs.platform || 'ALL']
|
||||||
}}
|
}}
|
||||||
runs-on: ${{ fromJson('["ubuntu-22.04", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Cleaning up unnecessary files
|
||||||
|
run: |
|
||||||
|
sudo apt-get remove --purge -y google-chrome-stable firefox mono-devel
|
||||||
|
sudo apt-get autoremove -y
|
||||||
|
sudo apt-get clean
|
||||||
- run: |
|
- run: |
|
||||||
sudo mount -t tmpfs tmpfs .
|
sudo mount -t tmpfs tmpfs .
|
||||||
if: ${{ github.event.inputs.runner == 'fast' }}
|
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||||
@@ -102,12 +107,6 @@ jobs:
|
|||||||
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
|
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
|
||||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||||
|
|
||||||
- name: Use Beta Toolchain
|
|
||||||
run: rustup default beta
|
|
||||||
|
|
||||||
- name: Setup Cross
|
|
||||||
run: cargo install cross --git https://github.com/cross-rs/cross
|
|
||||||
|
|
||||||
- name: Make
|
- name: Make
|
||||||
run: make ARCH=${{ matrix.arch }} compiled-${{ matrix.arch }}.tar
|
run: make ARCH=${{ matrix.arch }} compiled-${{ matrix.arch }}.tar
|
||||||
env:
|
env:
|
||||||
@@ -140,7 +139,7 @@ jobs:
|
|||||||
${{
|
${{
|
||||||
fromJson(
|
fromJson(
|
||||||
format(
|
format(
|
||||||
'["ubuntu-22.04", "{0}"]',
|
'["ubuntu-latest", "{0}"]',
|
||||||
fromJson('{
|
fromJson('{
|
||||||
"x86_64": "buildjet-8vcpu-ubuntu-2204",
|
"x86_64": "buildjet-8vcpu-ubuntu-2204",
|
||||||
"x86_64-nonfree": "buildjet-8vcpu-ubuntu-2204",
|
"x86_64-nonfree": "buildjet-8vcpu-ubuntu-2204",
|
||||||
@@ -273,7 +272,7 @@ jobs:
|
|||||||
index:
|
index:
|
||||||
if: ${{ github.event.inputs.deploy != '' && github.event.inputs.deploy != 'NONE' }}
|
if: ${{ github.event.inputs.deploy != '' && github.event.inputs.deploy != 'NONE' }}
|
||||||
needs: [image]
|
needs: [image]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- run: >-
|
- run: >-
|
||||||
curl "https://${{
|
curl "https://${{
|
||||||
|
|||||||
8
.github/workflows/test.yaml
vendored
8
.github/workflows/test.yaml
vendored
@@ -17,7 +17,7 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Run Automated Tests
|
name: Run Automated Tests
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -27,11 +27,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODEJS_VERSION }}
|
node-version: ${{ env.NODEJS_VERSION }}
|
||||||
|
|
||||||
- name: Use Beta Toolchain
|
|
||||||
run: rustup default beta
|
|
||||||
|
|
||||||
- name: Setup Cross
|
|
||||||
run: cargo install cross --git https://github.com/cross-rs/cross
|
|
||||||
|
|
||||||
- name: Build And Run Tests
|
- name: Build And Run Tests
|
||||||
run: make test
|
run: make test
|
||||||
|
|||||||
12
Makefile
12
Makefile
@@ -40,7 +40,6 @@ STARTOS_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_
|
|||||||
fi')
|
fi')
|
||||||
REGISTRY_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox core/startos/start-registryd.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
|
TUNNEL_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox core/startos/start-tunneld.service
|
||||||
REBUILD_TYPES = 1
|
|
||||||
|
|
||||||
ifeq ($(REMOTE),)
|
ifeq ($(REMOTE),)
|
||||||
mkdir = mkdir -p $1
|
mkdir = mkdir -p $1
|
||||||
@@ -63,7 +62,7 @@ endif
|
|||||||
|
|
||||||
.DELETE_ON_ERROR:
|
.DELETE_ON_ERROR:
|
||||||
|
|
||||||
.PHONY: all metadata install clean format cli uis ui reflash deb $(IMAGE_TYPE) squashfs wormhole wormhole-deb test test-core test-sdk test-container-runtime registry install-registry tunnel install-tunnel
|
.PHONY: all metadata install clean format cli uis ui reflash deb $(IMAGE_TYPE) squashfs wormhole wormhole-deb test test-core test-sdk test-container-runtime registry install-registry tunnel install-tunnel ts-bindings
|
||||||
|
|
||||||
all: $(STARTOS_TARGETS)
|
all: $(STARTOS_TARGETS)
|
||||||
|
|
||||||
@@ -160,7 +159,7 @@ results/$(REGISTRY_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-reg
|
|||||||
tunnel-deb: results/$(TUNNEL_BASENAME).deb
|
tunnel-deb: results/$(TUNNEL_BASENAME).deb
|
||||||
|
|
||||||
results/$(TUNNEL_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-tunnel) $(TUNNEL_TARGETS)
|
results/$(TUNNEL_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-tunnel) $(TUNNEL_TARGETS)
|
||||||
PROJECT=start-tunnel PLATFORM=$(ARCH) REQUIRES=debian DEPENDS=wireguard-tools,iptables ./build/os-compat/run-compat.sh ./dpkg-build.sh
|
PROJECT=start-tunnel PLATFORM=$(ARCH) REQUIRES=debian DEPENDS=wireguard-tools,iptables,conntrack ./build/os-compat/run-compat.sh ./dpkg-build.sh
|
||||||
|
|
||||||
$(IMAGE_TYPE): results/$(BASENAME).$(IMAGE_TYPE)
|
$(IMAGE_TYPE): results/$(BASENAME).$(IMAGE_TYPE)
|
||||||
|
|
||||||
@@ -226,7 +225,7 @@ wormhole-squashfs: results/$(BASENAME).squashfs
|
|||||||
$(eval SQFS_SIZE := $(shell du -s --bytes results/$(BASENAME).squashfs | awk '{print $$1}'))
|
$(eval SQFS_SIZE := $(shell du -s --bytes results/$(BASENAME).squashfs | awk '{print $$1}'))
|
||||||
@echo "Paste the following command into the shell of your StartOS server:"
|
@echo "Paste the following command into the shell of your StartOS server:"
|
||||||
@echo
|
@echo
|
||||||
@wormhole send results/$(BASENAME).squashfs 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo sh -c '"'"'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE) && /usr/lib/startos/scripts/prune-boot && cd /media/startos/images && wormhole receive --accept-file %s && CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/use-img ./$(BASENAME).squashfs'"'"'\n", $$3 }'
|
@wormhole send results/$(BASENAME).squashfs 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo sh -c '"'"'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE) && /usr/lib/startos/scripts/prune-boot && cd /media/startos/images && wormhole receive --accept-file %s && CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/upgrade ./$(BASENAME).squashfs'"'"'\n", $$3 }'
|
||||||
|
|
||||||
update: $(STARTOS_TARGETS)
|
update: $(STARTOS_TARGETS)
|
||||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||||
@@ -254,7 +253,7 @@ update-squashfs: results/$(BASENAME).squashfs
|
|||||||
$(call ssh,'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE)')
|
$(call ssh,'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE)')
|
||||||
$(call ssh,'/usr/lib/startos/scripts/prune-boot')
|
$(call ssh,'/usr/lib/startos/scripts/prune-boot')
|
||||||
$(call cp,results/$(BASENAME).squashfs,/media/startos/images/next.rootfs)
|
$(call cp,results/$(BASENAME).squashfs,/media/startos/images/next.rootfs)
|
||||||
$(call ssh,'sudo CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/use-img /media/startos/images/next.rootfs')
|
$(call ssh,'sudo CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/upgrade /media/startos/images/next.rootfs')
|
||||||
|
|
||||||
emulate-reflash: $(STARTOS_TARGETS)
|
emulate-reflash: $(STARTOS_TARGETS)
|
||||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||||
@@ -277,10 +276,9 @@ container-runtime/node_modules/.package-lock.json: container-runtime/package-loc
|
|||||||
npm --prefix container-runtime ci
|
npm --prefix container-runtime ci
|
||||||
touch container-runtime/node_modules/.package-lock.json
|
touch container-runtime/node_modules/.package-lock.json
|
||||||
|
|
||||||
sdk/base/lib/osBindings/index.ts: $(shell if [ "$(REBUILD_TYPES)" -ne 0 ]; then echo core/startos/bindings/index.ts; fi)
|
ts-bindings: core/startos/bindings/index.ts
|
||||||
mkdir -p sdk/base/lib/osBindings
|
mkdir -p sdk/base/lib/osBindings
|
||||||
rsync -ac --delete core/startos/bindings/ sdk/base/lib/osBindings/
|
rsync -ac --delete core/startos/bindings/ sdk/base/lib/osBindings/
|
||||||
touch sdk/base/lib/osBindings/index.ts
|
|
||||||
|
|
||||||
core/startos/bindings/index.ts: $(call ls-files, core) $(ENVIRONMENT_FILE)
|
core/startos/bindings/index.ts: $(call ls-files, core) $(ENVIRONMENT_FILE)
|
||||||
rm -rf core/startos/bindings
|
rm -rf core/startos/bindings
|
||||||
|
|||||||
@@ -1,26 +1,18 @@
|
|||||||
# StartTunnel
|
# StartTunnel
|
||||||
|
|
||||||
A self-hosted Wiregaurd VPN optimized for creating VLANs and reverse tunneling to personal servers.
|
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"
|
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.
|
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.
|
||||||
|
|
||||||
## Installation
|
## Features
|
||||||
|
|
||||||
1. Rent a low cost VPS. For most use cases, the cheapest option should be enough.
|
- **Create Subnets**: Each subnet creates a private, virtual local area network (VLAN), similar to the LAN created by a home router.
|
||||||
|
|
||||||
- It must have a dedicated public IP address.
|
- **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.
|
||||||
- For (CPU), memory (RAM), and storage (disk), choose the minimum spec.
|
|
||||||
- For transfer (bandwidth), it depends on (1) your use case and (2) your home Internet's _upload_ speed. Even if you intend to serve large files or stream content from your server, there is no reason to pay for speeds that exceed your home Internet's upload speed.
|
|
||||||
|
|
||||||
1. Provision the VPS with the latest version of Debian.
|
- **Forward Ports**: Forwarding a port creates a "reverse tunnel", exposing a specific port on a specific device to the public Internet.
|
||||||
|
|
||||||
1. Access the VPS via SSH.
|
|
||||||
|
|
||||||
1. Install StartTunnel:
|
|
||||||
|
|
||||||
@TODO
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -30,6 +22,32 @@ Use it for private, remote access, to self-hosted services running on a personal
|
|||||||
|
|
||||||
- **Forward Ports**: Forwarding a port creates a "reverse tunnel", exposing a specific port on a specific device to the public Internet.
|
- **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. Install StartTunnel:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
TMP_DIR=$(mktemp -d) && (cd $TMP_DIR && wget https://github.com/Start9Labs/start-os/releases/download/v0.4.0-alpha.12/start-tunnel-0.4.0-alpha.12-unknown.dev_$(uname -m).deb && apt-get install -y ./start-tunnel-0.4.0-alpha.12-unknown.dev_$(uname -m).deb) && rm -rf $TMP_DIR && systemctl start start-tunneld && echo "Installation Succeeded"
|
||||||
|
```
|
||||||
|
|
||||||
|
5. [Initialize the web interface](#web-interface) (recommended)
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
```sh
|
||||||
|
TMP_DIR=$(mktemp -d) && (cd $TMP_DIR && wget https://github.com/Start9Labs/start-os/releases/download/v0.4.0-alpha.12/start-tunnel-0.4.0-alpha.12-unknown.dev_$(uname -m).deb && apt-get install --reinstall -y ./start-tunnel-0.4.0-alpha.12-unknown.dev_$(uname -m).deb) && rm -rf $TMP_DIR && systemctl daemon-reload && systemctl restart start-tunneld && echo "Update Succeeded"
|
||||||
|
```
|
||||||
|
|
||||||
## CLI
|
## CLI
|
||||||
|
|
||||||
By default, StartTunnel is managed via the `start-tunnel` command line interface, which is self-documented.
|
By default, StartTunnel is managed via the `start-tunnel` command line interface, which is self-documented.
|
||||||
@@ -52,8 +70,8 @@ If you choose to enable the web interface (recommended in most cases), StartTunn
|
|||||||
|
|
||||||
1. Select whether to autogenerate a self-signed certificate or provide your own certificate and key. If you choose to autogenerate, you will be asked to list all IP addresses and domains for which to sign the certificate. For example, if you intend to access your StartTunnel web UI at a domain, include the domain in the list.
|
1. Select whether to autogenerate a self-signed certificate or provide your own certificate and key. If you choose to autogenerate, you will be asked to list all IP addresses and domains for which to sign the certificate. For example, if you intend to access your StartTunnel web UI at a domain, include the domain in the list.
|
||||||
|
|
||||||
1. You will receive a success message that the webserver is running at the chosen IP:port, as well as your SSL certificate and an autogenerated UI password.
|
1. You will receive a success message with 3 pieces of information:
|
||||||
|
|
||||||
1. If not already, trust the certificate in your system keychain and/or browser.
|
- <https://IP:port>: the URL where you can reach your personal web interface.
|
||||||
|
- Password: an autogenerated password for your interface. If you lose/forget it, you can reset using the CLI.
|
||||||
1. If you lose/forget your password, you can reset it using the CLI.
|
- Root Certificate Authority: the Root CA of your StartTunnel instance. If not already, trust it in your browser or system keychain.
|
||||||
|
|||||||
201
agents/VERSION_BUMP.md
Normal file
201
agents/VERSION_BUMP.md
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
# StartOS Version Bump Guide
|
||||||
|
|
||||||
|
This document explains how to bump the StartOS version across the entire codebase.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
When bumping from version `X.Y.Z-alpha.N` to `X.Y.Z-alpha.N+1`, you need to update files in multiple locations across the repository. The `// VERSION_BUMP` comment markers indicate where changes are needed.
|
||||||
|
|
||||||
|
## Files to Update
|
||||||
|
|
||||||
|
### 1. Core Rust Crate Version
|
||||||
|
|
||||||
|
**File: `core/startos/Cargo.toml`**
|
||||||
|
|
||||||
|
Update the version string (line ~18):
|
||||||
|
|
||||||
|
```toml
|
||||||
|
version = "0.4.0-alpha.15" # VERSION_BUMP
|
||||||
|
```
|
||||||
|
|
||||||
|
**File: `core/Cargo.lock`**
|
||||||
|
|
||||||
|
This file is auto-generated. After updating `Cargo.toml`, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd core
|
||||||
|
cargo check
|
||||||
|
```
|
||||||
|
|
||||||
|
This will update the version in `Cargo.lock` automatically.
|
||||||
|
|
||||||
|
### 2. Create New Version Migration Module
|
||||||
|
|
||||||
|
**File: `core/startos/src/version/vX_Y_Z_alpha_N+1.rs`**
|
||||||
|
|
||||||
|
Create a new version file by copying the previous version and updating:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use exver::{PreReleaseSegment, VersionRange};
|
||||||
|
|
||||||
|
use super::v0_3_5::V0_3_0_COMPAT;
|
||||||
|
use super::{VersionT, v0_4_0_alpha_14}; // Update to previous version
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref V0_4_0_alpha_15: exver::Version = exver::Version::new(
|
||||||
|
[0, 4, 0],
|
||||||
|
[PreReleaseSegment::String("alpha".into()), 15.into()] // Update number
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Version;
|
||||||
|
|
||||||
|
impl VersionT for Version {
|
||||||
|
type Previous = v0_4_0_alpha_14::Version; // Update to previous version
|
||||||
|
type PreUpRes = ();
|
||||||
|
|
||||||
|
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn semver(self) -> exver::Version {
|
||||||
|
V0_4_0_alpha_15.clone() // Update version name
|
||||||
|
}
|
||||||
|
fn compat(self) -> &'static VersionRange {
|
||||||
|
&V0_3_0_COMPAT
|
||||||
|
}
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||||
|
// Add migration logic here if needed
|
||||||
|
Ok(Value::Null)
|
||||||
|
}
|
||||||
|
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||||
|
// Add rollback logic here if needed
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Update Version Module Registry
|
||||||
|
|
||||||
|
**File: `core/startos/src/version/mod.rs`**
|
||||||
|
|
||||||
|
Make changes in **5 locations**:
|
||||||
|
|
||||||
|
#### Location 1: Module Declaration (~line 57)
|
||||||
|
|
||||||
|
Add the new module after the previous version:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
mod v0_4_0_alpha_14;
|
||||||
|
mod v0_4_0_alpha_15; // Add this
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Location 2: Current Type Alias (~line 59)
|
||||||
|
|
||||||
|
Update the `Current` type and move the `// VERSION_BUMP` comment:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub type Current = v0_4_0_alpha_15::Version; // VERSION_BUMP
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Location 3: Version Enum (~line 175)
|
||||||
|
|
||||||
|
Remove `// VERSION_BUMP` from the previous version, add new variant, add comment:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
V0_4_0_alpha_14(Wrapper<v0_4_0_alpha_14::Version>),
|
||||||
|
V0_4_0_alpha_15(Wrapper<v0_4_0_alpha_15::Version>), // VERSION_BUMP
|
||||||
|
Other(exver::Version),
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Location 4: as_version_t() Match (~line 233)
|
||||||
|
|
||||||
|
Remove `// VERSION_BUMP`, add new match arm, add comment:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
Self::V0_4_0_alpha_14(v) => DynVersion(Box::new(v.0)),
|
||||||
|
Self::V0_4_0_alpha_15(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||||
|
Self::Other(v) => {
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Location 5: as_exver() Match (~line 284, inside #[cfg(test)])
|
||||||
|
|
||||||
|
Remove `// VERSION_BUMP`, add new match arm, add comment:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
Version::V0_4_0_alpha_14(Wrapper(x)) => x.semver(),
|
||||||
|
Version::V0_4_0_alpha_15(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||||
|
Version::Other(x) => x.clone(),
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. SDK TypeScript Version
|
||||||
|
|
||||||
|
**File: `sdk/package/lib/StartSdk.ts`**
|
||||||
|
|
||||||
|
Update the OSVersion constant (~line 64):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const OSVersion = testTypeVersion("0.4.0-alpha.15");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Web UI Package Version
|
||||||
|
|
||||||
|
**File: `web/package.json`**
|
||||||
|
|
||||||
|
Update the version field:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "startos-ui",
|
||||||
|
"version": "0.4.0-alpha.15",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**File: `web/package-lock.json`**
|
||||||
|
|
||||||
|
This file is auto-generated, but it's faster to update manually. Find all instances of "startos-ui" and update the version field.
|
||||||
|
|
||||||
|
## Verification Step
|
||||||
|
|
||||||
|
```
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
## VERSION_BUMP Comment Pattern
|
||||||
|
|
||||||
|
The `// VERSION_BUMP` comment serves as a marker for where to make changes next time:
|
||||||
|
|
||||||
|
- Always **remove** it from the old location
|
||||||
|
- **Add** the new version entry
|
||||||
|
- **Move** the comment to mark the new location
|
||||||
|
|
||||||
|
This pattern helps you quickly find all the places that need updating in the next version bump.
|
||||||
|
|
||||||
|
## Summary Checklist
|
||||||
|
|
||||||
|
- [ ] Update `core/startos/Cargo.toml` version
|
||||||
|
- [ ] Create new `core/startos/src/version/vX_Y_Z_alpha_N+1.rs` file
|
||||||
|
- [ ] Update `core/startos/src/version/mod.rs` in 5 locations
|
||||||
|
- [ ] Run `cargo check` to update `core/Cargo.lock`
|
||||||
|
- [ ] Update `sdk/package/lib/StartSdk.ts` OSVersion
|
||||||
|
- [ ] Update `web/package.json` and `web/package-lock.json` version
|
||||||
|
- [ ] Verify all changes compile/build successfully
|
||||||
|
|
||||||
|
## Migration Logic
|
||||||
|
|
||||||
|
The `up()` and `down()` methods in the version file handle database migrations:
|
||||||
|
|
||||||
|
- **up()**: Migrates the database from the previous version to this version
|
||||||
|
- **down()**: Rolls back from this version to the previous version
|
||||||
|
- **pre_up()**: Runs before migration, useful for pre-migration checks or data gathering
|
||||||
|
|
||||||
|
If no migration is needed, return `Ok(Value::Null)` for `up()` and `Ok(())` for `down()`.
|
||||||
|
|
||||||
|
For complex migrations, you may need to:
|
||||||
|
|
||||||
|
1. Update `type PreUpRes` to pass data between `pre_up()` and `up()`
|
||||||
|
2. Implement database transformations in the `up()` method
|
||||||
|
3. Implement reverse transformations in `down()` for rollback support
|
||||||
@@ -7,6 +7,7 @@ bmon
|
|||||||
btrfs-progs
|
btrfs-progs
|
||||||
ca-certificates
|
ca-certificates
|
||||||
cifs-utils
|
cifs-utils
|
||||||
|
conntrack
|
||||||
cryptsetup
|
cryptsetup
|
||||||
curl
|
curl
|
||||||
dmidecode
|
dmidecode
|
||||||
@@ -19,7 +20,6 @@ flashrom
|
|||||||
fuse3
|
fuse3
|
||||||
grub-common
|
grub-common
|
||||||
grub-efi
|
grub-efi
|
||||||
grub2-common
|
|
||||||
htop
|
htop
|
||||||
httpdirfs
|
httpdirfs
|
||||||
iotop
|
iotop
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
- grub-common
|
|
||||||
- grub-efi
|
- grub-efi
|
||||||
- grub2-common
|
|
||||||
+ parted
|
+ parted
|
||||||
+ raspberrypi-net-mods
|
+ raspberrypi-net-mods
|
||||||
+ raspberrypi-sys-mods
|
+ raspberrypi-sys-mods
|
||||||
|
|||||||
@@ -10,24 +10,24 @@ fi
|
|||||||
POSITIONAL_ARGS=()
|
POSITIONAL_ARGS=()
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
--no-sync)
|
--no-sync)
|
||||||
NO_SYNC=1
|
NO_SYNC=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--create)
|
--create)
|
||||||
ONLY_CREATE=1
|
ONLY_CREATE=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-*|--*)
|
-*|--*)
|
||||||
echo "Unknown option $1"
|
echo "Unknown option $1"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
POSITIONAL_ARGS+=("$1") # save positional arg
|
POSITIONAL_ARGS+=("$1") # save positional arg
|
||||||
shift # past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
|
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
|
||||||
@@ -35,7 +35,7 @@ set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
|
|||||||
if [ -z "$NO_SYNC" ]; then
|
if [ -z "$NO_SYNC" ]; then
|
||||||
echo 'Syncing...'
|
echo 'Syncing...'
|
||||||
umount -R /media/startos/next 2> /dev/null
|
umount -R /media/startos/next 2> /dev/null
|
||||||
umount -R /media/startos/upper 2> /dev/null
|
umount /media/startos/upper 2> /dev/null
|
||||||
rm -rf /media/startos/upper /media/startos/next
|
rm -rf /media/startos/upper /media/startos/next
|
||||||
mkdir /media/startos/upper
|
mkdir /media/startos/upper
|
||||||
mount -t tmpfs tmpfs /media/startos/upper
|
mount -t tmpfs tmpfs /media/startos/upper
|
||||||
@@ -43,8 +43,6 @@ if [ -z "$NO_SYNC" ]; then
|
|||||||
mount -t overlay \
|
mount -t overlay \
|
||||||
-olowerdir=/media/startos/current,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \
|
-olowerdir=/media/startos/current,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \
|
||||||
overlay /media/startos/next
|
overlay /media/startos/next
|
||||||
mkdir -p /media/startos/next/media/startos/root
|
|
||||||
mount --bind /media/startos/root /media/startos/next/media/startos/root
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$ONLY_CREATE" ]; then
|
if [ -n "$ONLY_CREATE" ]; then
|
||||||
@@ -56,12 +54,18 @@ mkdir -p /media/startos/next/dev
|
|||||||
mkdir -p /media/startos/next/sys
|
mkdir -p /media/startos/next/sys
|
||||||
mkdir -p /media/startos/next/proc
|
mkdir -p /media/startos/next/proc
|
||||||
mkdir -p /media/startos/next/boot
|
mkdir -p /media/startos/next/boot
|
||||||
|
mkdir -p /media/startos/next/media/startos/root
|
||||||
mount --bind /run /media/startos/next/run
|
mount --bind /run /media/startos/next/run
|
||||||
mount --bind /tmp /media/startos/next/tmp
|
mount --bind /tmp /media/startos/next/tmp
|
||||||
mount --bind /dev /media/startos/next/dev
|
mount --bind /dev /media/startos/next/dev
|
||||||
mount --bind /sys /media/startos/next/sys
|
mount --bind /sys /media/startos/next/sys
|
||||||
mount --bind /proc /media/startos/next/proc
|
mount --bind /proc /media/startos/next/proc
|
||||||
mount --bind /boot /media/startos/next/boot
|
mount --bind /boot /media/startos/next/boot
|
||||||
|
mount --bind /media/startos/root /media/startos/next/media/startos/root
|
||||||
|
|
||||||
|
if mountpoint /sys/firmware/efi/efivars 2> /dev/null; then
|
||||||
|
mount --bind /sys/firmware/efi/efivars /media/startos/next/sys/firmware/efi/efivars
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$*" ]; then
|
if [ -z "$*" ]; then
|
||||||
chroot /media/startos/next
|
chroot /media/startos/next
|
||||||
@@ -71,6 +75,10 @@ else
|
|||||||
CHROOT_RES=$?
|
CHROOT_RES=$?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if mountpoint /media/startos/next/sys/firmware/efi/efivars 2> /dev/null; then
|
||||||
|
umount /media/startos/next/sys/firmware/efi/efivars
|
||||||
|
fi
|
||||||
|
|
||||||
umount /media/startos/next/run
|
umount /media/startos/next/run
|
||||||
umount /media/startos/next/tmp
|
umount /media/startos/next/tmp
|
||||||
umount /media/startos/next/dev
|
umount /media/startos/next/dev
|
||||||
@@ -87,11 +95,12 @@ if [ "$CHROOT_RES" -eq 0 ]; then
|
|||||||
|
|
||||||
echo 'Upgrading...'
|
echo 'Upgrading...'
|
||||||
|
|
||||||
|
rm -f /media/startos/images/next.squashfs
|
||||||
if ! time mksquashfs /media/startos/next /media/startos/images/next.squashfs -b 4096 -comp gzip; then
|
if ! time mksquashfs /media/startos/next /media/startos/images/next.squashfs -b 4096 -comp gzip; then
|
||||||
umount -R /media/startos/next
|
umount -l /media/startos/next
|
||||||
umount -R /media/startos/upper
|
umount -l /media/startos/upper
|
||||||
rm -rf /media/startos/upper /media/startos/next
|
rm -rf /media/startos/upper /media/startos/next
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
hash=$(b3sum /media/startos/images/next.squashfs | head -c 32)
|
hash=$(b3sum /media/startos/images/next.squashfs | head -c 32)
|
||||||
mv /media/startos/images/next.squashfs /media/startos/images/${hash}.rootfs
|
mv /media/startos/images/next.squashfs /media/startos/images/${hash}.rootfs
|
||||||
@@ -103,5 +112,5 @@ if [ "$CHROOT_RES" -eq 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
umount -R /media/startos/next
|
umount -R /media/startos/next
|
||||||
umount -R /media/startos/upper
|
umount /media/startos/upper
|
||||||
rm -rf /media/startos/upper /media/startos/next
|
rm -rf /media/startos/upper /media/startos/next
|
||||||
@@ -5,34 +5,25 @@ if [ -z "$sip" ] || [ -z "$dip" ] || [ -z "$sport" ] || [ -z "$dport" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Helper function to check if a rule exists
|
rule_exists() {
|
||||||
nat_rule_exists() {
|
|
||||||
iptables -t nat -C "$@" 2>/dev/null
|
iptables -t nat -C "$@" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Helper function to add or delete a rule idempotently
|
apply_rule() {
|
||||||
# Usage: apply_rule [add|del] <iptables args...>
|
if [ "$UNDO" = "1" ]; then
|
||||||
apply_nat_rule() {
|
|
||||||
local action="$1"
|
|
||||||
shift
|
|
||||||
|
|
||||||
if [ "$action" = "add" ]; then
|
|
||||||
# Only add if rule doesn't exist
|
|
||||||
if ! rule_exists "$@"; then
|
|
||||||
iptables -t nat -A "$@"
|
|
||||||
fi
|
|
||||||
elif [ "$action" = "del" ]; then
|
|
||||||
if rule_exists "$@"; then
|
if rule_exists "$@"; then
|
||||||
iptables -t nat -D "$@"
|
iptables -t nat -D "$@"
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
if ! rule_exists "$@"; then
|
||||||
|
iptables -t nat -A "$@"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ "$UNDO" = 1 ]; then
|
apply_rule PREROUTING -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport
|
||||||
action="del"
|
apply_rule OUTPUT -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport
|
||||||
else
|
|
||||||
action="add"
|
|
||||||
fi
|
|
||||||
|
|
||||||
apply_nat_rule "$action" PREROUTING -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport
|
if [ "$UNDO" = 1 ]; then
|
||||||
apply_nat_rule "$action" OUTPUT -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport
|
conntrack -D -p tcp -d $sip --dport $sport || true # conntrack returns exit 1 if no connections are active
|
||||||
|
fi
|
||||||
82
build/lib/scripts/upgrade
Executable file
82
build/lib/scripts/upgrade
Executable file
@@ -0,0 +1,82 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SOURCE_DIR="$(dirname $(realpath "${BASH_SOURCE[0]}"))"
|
||||||
|
|
||||||
|
if [ "$UID" -ne 0 ]; then
|
||||||
|
>&2 echo 'Must be run as root'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -f "$1" ]; then
|
||||||
|
>&2 echo "usage: $0 <SQUASHFS>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 'Upgrading...'
|
||||||
|
|
||||||
|
hash=$(b3sum $1 | head -c 32)
|
||||||
|
if [ -n "$2" ] && [ "$hash" != "$CHECKSUM" ]; then
|
||||||
|
>&2 echo 'Checksum mismatch'
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
unsquashfs -f -d / $1 boot
|
||||||
|
|
||||||
|
umount -R /media/startos/next 2> /dev/null || true
|
||||||
|
umount /media/startos/upper 2> /dev/null || true
|
||||||
|
umount /media/startos/lower 2> /dev/null || true
|
||||||
|
|
||||||
|
mkdir -p /media/startos/upper
|
||||||
|
mount -t tmpfs tmpfs /media/startos/upper
|
||||||
|
mkdir -p /media/startos/lower /media/startos/upper/data /media/startos/upper/work /media/startos/next
|
||||||
|
mount $1 /media/startos/lower
|
||||||
|
mount -t overlay \
|
||||||
|
-olowerdir=/media/startos/lower,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \
|
||||||
|
overlay /media/startos/next
|
||||||
|
|
||||||
|
mkdir -p /media/startos/next/run
|
||||||
|
mkdir -p /media/startos/next/dev
|
||||||
|
mkdir -p /media/startos/next/sys
|
||||||
|
mkdir -p /media/startos/next/proc
|
||||||
|
mkdir -p /media/startos/next/boot
|
||||||
|
mkdir -p /media/startos/next/media/startos/root
|
||||||
|
mount --bind /run /media/startos/next/run
|
||||||
|
mount --bind /tmp /media/startos/next/tmp
|
||||||
|
mount --bind /dev /media/startos/next/dev
|
||||||
|
mount --bind /sys /media/startos/next/sys
|
||||||
|
mount --bind /proc /media/startos/next/proc
|
||||||
|
mount --bind /boot /media/startos/next/boot
|
||||||
|
mount --bind /media/startos/root /media/startos/next/media/startos/root
|
||||||
|
|
||||||
|
if mountpoint /boot/efi 2> /dev/null; then
|
||||||
|
mkdir -p /media/startos/next/boot/efi
|
||||||
|
mount --bind /boot/efi /media/startos/next/boot/efi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if mountpoint /sys/firmware/efi/efivars 2> /dev/null; then
|
||||||
|
mount --bind /sys/firmware/efi/efivars /media/startos/next/sys/firmware/efi/efivars
|
||||||
|
fi
|
||||||
|
|
||||||
|
chroot /media/startos/next bash -e << "EOF"
|
||||||
|
|
||||||
|
if [ -f /boot/grub/grub.cfg ]; then
|
||||||
|
grub-install /dev/$(eval $(lsblk -o MOUNTPOINT,PKNAME -P | grep 'MOUNTPOINT="/media/startos/root"') && echo $PKNAME)
|
||||||
|
update-grub
|
||||||
|
fi
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sync
|
||||||
|
|
||||||
|
umount -Rl /media/startos/next
|
||||||
|
umount /media/startos/upper
|
||||||
|
umount /media/startos/lower
|
||||||
|
|
||||||
|
mv $1 /media/startos/images/${hash}.rootfs
|
||||||
|
ln -rsf /media/startos/images/${hash}.rootfs /media/startos/config/current.rootfs
|
||||||
|
|
||||||
|
sync
|
||||||
|
|
||||||
|
echo 'System upgrade complete. Reboot to apply changes...'
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
if [ "$UID" -ne 0 ]; then
|
|
||||||
>&2 echo 'Must be run as root'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$1" ]; then
|
|
||||||
>&2 echo "usage: $0 <SQUASHFS>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
VERSION=$(unsquashfs -cat $1 /usr/lib/startos/VERSION.txt)
|
|
||||||
GIT_HASH=$(unsquashfs -cat $1 /usr/lib/startos/GIT_HASH.txt)
|
|
||||||
B3SUM=$(b3sum $1 | head -c 32)
|
|
||||||
|
|
||||||
if [ -n "$CHECKSUM" ] && [ "$CHECKSUM" != "$B3SUM" ]; then
|
|
||||||
>&2 echo "CHECKSUM MISMATCH"
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv $1 /media/startos/images/${B3SUM}.rootfs
|
|
||||||
ln -rsf /media/startos/images/${B3SUM}.rootfs /media/startos/config/current.rootfs
|
|
||||||
|
|
||||||
unsquashfs -n -f -d / /media/startos/images/${B3SUM}.rootfs boot
|
|
||||||
|
|
||||||
umount -R /media/startos/next 2> /dev/null || true
|
|
||||||
umount -R /media/startos/lower 2> /dev/null || true
|
|
||||||
umount -R /media/startos/upper 2> /dev/null || true
|
|
||||||
|
|
||||||
rm -rf /media/startos/lower /media/startos/upper /media/startos/next
|
|
||||||
mkdir /media/startos/upper
|
|
||||||
mount -t tmpfs tmpfs /media/startos/upper
|
|
||||||
mkdir -p /media/startos/lower /media/startos/upper/data /media/startos/upper/work /media/startos/next
|
|
||||||
mount /media/startos/images/${B3SUM}.rootfs /media/startos/lower
|
|
||||||
mount -t overlay \
|
|
||||||
-olowerdir=/media/startos/lower,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \
|
|
||||||
overlay /media/startos/next
|
|
||||||
mkdir -p /media/startos/next/media/startos/root
|
|
||||||
mount --bind /media/startos/root /media/startos/next/media/startos/root
|
|
||||||
mkdir -p /media/startos/next/dev
|
|
||||||
mkdir -p /media/startos/next/sys
|
|
||||||
mkdir -p /media/startos/next/proc
|
|
||||||
mkdir -p /media/startos/next/boot
|
|
||||||
mount --bind /dev /media/startos/next/dev
|
|
||||||
mount --bind /sys /media/startos/next/sys
|
|
||||||
mount --bind /proc /media/startos/next/proc
|
|
||||||
mount --bind /boot /media/startos/next/boot
|
|
||||||
|
|
||||||
chroot /media/startos/next update-grub2
|
|
||||||
|
|
||||||
umount -R /media/startos/next
|
|
||||||
umount -R /media/startos/upper
|
|
||||||
umount -R /media/startos/lower
|
|
||||||
rm -rf /media/startos/lower /media/startos/upper /media/startos/next
|
|
||||||
|
|
||||||
sync
|
|
||||||
|
|
||||||
reboot
|
|
||||||
2
container-runtime/package-lock.json
generated
2
container-runtime/package-lock.json
generated
@@ -38,7 +38,7 @@
|
|||||||
},
|
},
|
||||||
"../sdk/dist": {
|
"../sdk/dist": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.42",
|
"version": "0.4.0-beta.44",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
|
|||||||
@@ -158,6 +158,8 @@ export class RpcListener {
|
|||||||
|
|
||||||
this.unixSocketServer.listen(SOCKET_PATH)
|
this.unixSocketServer.listen(SOCKET_PATH)
|
||||||
|
|
||||||
|
console.log("Listening on %s", SOCKET_PATH)
|
||||||
|
|
||||||
this.unixSocketServer.on("connection", (s) => {
|
this.unixSocketServer.on("connection", (s) => {
|
||||||
let id: IdType = null
|
let id: IdType = null
|
||||||
const captureId = <X>(x: X) => {
|
const captureId = <X>(x: X) => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ if [ "$ARCH" = "riscv64" ]; then
|
|||||||
RUST_ARCH="riscv64gc"
|
RUST_ARCH="riscv64gc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if mountpoint -q tmp/combined; then sudo umount -R tmp/combined; fi
|
if mountpoint -q tmp/combined; then sudo umount -l tmp/combined; fi
|
||||||
if mountpoint -q tmp/lower; then sudo umount tmp/lower; fi
|
if mountpoint -q tmp/lower; then sudo umount tmp/lower; fi
|
||||||
sudo rm -rf tmp
|
sudo rm -rf tmp
|
||||||
mkdir -p tmp/lower tmp/upper tmp/work tmp/combined
|
mkdir -p tmp/lower tmp/upper tmp/work tmp/combined
|
||||||
|
|||||||
37
core/Cargo.lock
generated
37
core/Cargo.lock
generated
@@ -3458,7 +3458,7 @@ dependencies = [
|
|||||||
"lazy_async_pool",
|
"lazy_async_pool",
|
||||||
"models",
|
"models",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"rpc-toolkit",
|
"rpc-toolkit 0.3.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?branch=master)",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -4835,7 +4835,7 @@ dependencies = [
|
|||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rpc-toolkit",
|
"rpc-toolkit 0.3.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?branch=master)",
|
||||||
"rustls 0.23.35",
|
"rustls 0.23.35",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -6744,6 +6744,34 @@ dependencies = [
|
|||||||
"yajrc",
|
"yajrc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rpc-toolkit"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "git+https://github.com/Start9Labs/rpc-toolkit.git?rev=068db90#068db905ee38a7da97cc4a43b806409204e73723"
|
||||||
|
dependencies = [
|
||||||
|
"async-stream",
|
||||||
|
"async-trait",
|
||||||
|
"axum 0.8.6",
|
||||||
|
"clap",
|
||||||
|
"futures",
|
||||||
|
"http",
|
||||||
|
"http-body-util",
|
||||||
|
"imbl-value 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"itertools 0.14.0",
|
||||||
|
"lazy_format",
|
||||||
|
"lazy_static",
|
||||||
|
"openssl",
|
||||||
|
"pin-project",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"url",
|
||||||
|
"yajrc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsa"
|
name = "rsa"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
@@ -7880,7 +7908,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "start-os"
|
name = "start-os"
|
||||||
version = "0.4.0-alpha.12"
|
version = "0.4.0-alpha.15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes 0.7.5",
|
"aes 0.7.5",
|
||||||
"arti-client",
|
"arti-client",
|
||||||
@@ -7888,7 +7916,6 @@ dependencies = [
|
|||||||
"async-compression",
|
"async-compression",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"aws-lc-sys",
|
|
||||||
"axum 0.8.6",
|
"axum 0.8.6",
|
||||||
"backtrace-on-stack-overflow",
|
"backtrace-on-stack-overflow",
|
||||||
"barrage",
|
"barrage",
|
||||||
@@ -7981,7 +8008,7 @@ dependencies = [
|
|||||||
"reqwest",
|
"reqwest",
|
||||||
"reqwest_cookie_store",
|
"reqwest_cookie_store",
|
||||||
"rpassword",
|
"rpassword",
|
||||||
"rpc-toolkit",
|
"rpc-toolkit 0.3.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?rev=068db90)",
|
||||||
"rust-argon2",
|
"rust-argon2",
|
||||||
"safelog",
|
"safelog",
|
||||||
"semver",
|
"semver",
|
||||||
|
|||||||
@@ -2,12 +2,19 @@
|
|||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
set -ea
|
set -ea
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
PROFILE=${PROFILE:-release}
|
PROFILE=${PROFILE:-release}
|
||||||
if [ "${PROFILE}" = "release" ]; then
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
BUILD_FLAGS="--release"
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "${ARCH:-}" ]; then
|
if [ -z "${ARCH:-}" ]; then
|
||||||
@@ -18,15 +25,20 @@ if [ "$ARCH" = "arm64" ]; then
|
|||||||
ARCH="aarch64"
|
ARCH="aarch64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
RUST_ARCH="$ARCH"
|
||||||
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
|
RUST_ARCH="riscv64gc"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "${KERNEL_NAME:-}" ]; then
|
if [ -z "${KERNEL_NAME:-}" ]; then
|
||||||
KERNEL_NAME=$(uname -s)
|
KERNEL_NAME=$(uname -s)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "${TARGET:-}" ]; then
|
if [ -z "${TARGET:-}" ]; then
|
||||||
if [ "$KERNEL_NAME" = "Linux" ]; then
|
if [ "$KERNEL_NAME" = "Linux" ]; then
|
||||||
TARGET="$ARCH-unknown-linux-musl"
|
TARGET="$RUST_ARCH-unknown-linux-musl"
|
||||||
elif [ "$KERNEL_NAME" = "Darwin" ]; then
|
elif [ "$KERNEL_NAME" = "Darwin" ]; then
|
||||||
TARGET="$ARCH-apple-darwin"
|
TARGET="$RUST_ARCH-apple-darwin"
|
||||||
else
|
else
|
||||||
>&2 echo "unknown kernel $KERNEL_NAME"
|
>&2 echo "unknown kernel $KERNEL_NAME"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -53,4 +65,7 @@ fi
|
|||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
cross build --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features $FEATURE_ARGS --locked --bin start-cli --target=$TARGET
|
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features $FEATURE_ARGS --locked --bin start-cli --target=$TARGET
|
||||||
|
if [ "$(ls -nd "core/target/$TARGET/$PROFILE/start-cli" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
|
rust-zig-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
||||||
|
fi
|
||||||
@@ -2,12 +2,19 @@
|
|||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
set -ea
|
set -ea
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
PROFILE=${PROFILE:-release}
|
PROFILE=${PROFILE:-release}
|
||||||
if [ "${PROFILE}" = "release" ]; then
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
BUILD_FLAGS="--release"
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
@@ -33,4 +40,7 @@ fi
|
|||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
cross build --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-container,$FEATURES --locked --bin containerbox --target=$RUST_ARCH-unknown-linux-musl
|
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-container,$FEATURES --locked --bin containerbox --target=$RUST_ARCH-unknown-linux-musl
|
||||||
|
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/containerbox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||||
|
fi
|
||||||
@@ -2,12 +2,19 @@
|
|||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
set -ea
|
set -ea
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
PROFILE=${PROFILE:-release}
|
PROFILE=${PROFILE:-release}
|
||||||
if [ "${PROFILE}" = "release" ]; then
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
BUILD_FLAGS="--release"
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
@@ -33,4 +40,7 @@ fi
|
|||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
cross build --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-registry,registry,$FEATURES --locked --bin registrybox --target=$RUST_ARCH-unknown-linux-musl
|
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-registry,registry,$FEATURES --locked --bin registrybox --target=$RUST_ARCH-unknown-linux-musl
|
||||||
|
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/registrybox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||||
|
fi
|
||||||
@@ -2,12 +2,19 @@
|
|||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
set -ea
|
set -ea
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
PROFILE=${PROFILE:-release}
|
PROFILE=${PROFILE:-release}
|
||||||
if [ "${PROFILE}" = "release" ]; then
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
BUILD_FLAGS="--release"
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
@@ -33,4 +40,7 @@ fi
|
|||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
cross build --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli,startd,$FEATURES --locked --bin startbox --target=$RUST_ARCH-unknown-linux-musl
|
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli,startd,$FEATURES --locked --bin startbox --target=$RUST_ARCH-unknown-linux-musl
|
||||||
|
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/startbox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||||
|
fi
|
||||||
@@ -2,12 +2,19 @@
|
|||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
set -ea
|
set -ea
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
PROFILE=${PROFILE:-release}
|
PROFILE=${PROFILE:-release}
|
||||||
if [ "${PROFILE}" = "release" ]; then
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
BUILD_FLAGS="--release"
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
@@ -31,4 +38,7 @@ if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
|||||||
fi
|
fi
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
cross test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features test,$FEATURES --locked 'export_bindings_'
|
rust-zig-builder cargo test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features test,$FEATURES --locked 'export_bindings_'
|
||||||
|
if [ "$(ls -nd "core/startos/bindings" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID core/startos/bindings && chown -R $UID:$UID /root/.cargo"
|
||||||
|
fi
|
||||||
@@ -2,12 +2,19 @@
|
|||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
set -ea
|
set -ea
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
PROFILE=${PROFILE:-release}
|
PROFILE=${PROFILE:-release}
|
||||||
if [ "${PROFILE}" = "release" ]; then
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
BUILD_FLAGS="--release"
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
@@ -33,4 +40,7 @@ fi
|
|||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
cross build --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-tunnel,tunnel,$FEATURES --locked --bin tunnelbox --target=$RUST_ARCH-unknown-linux-musl
|
rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-tunnel,tunnel,$FEATURES --locked --bin tunnelbox --target=$RUST_ARCH-unknown-linux-musl
|
||||||
|
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/tunnelbox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||||
|
fi
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -e SCCACHE_GHA_ENABLED -e SCCACHE_GHA_VERSION -e ACTIONS_RESULTS_URL -e ACTIONS_RUNTIME_TOKEN -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$HOME/.cache/sccache":/root/.cache/sccache -v "$(pwd)":/home/rust/src -w /home/rust/src -P start9/rust-musl-cross:$ARCH-musl'
|
USE_TTY=
|
||||||
|
if tty -s; then
|
||||||
|
USE_TTY="-it"
|
||||||
|
fi
|
||||||
|
|
||||||
|
alias 'rust-zig-builder'='docker run '"$USE_TTY"' --rm -e "RUSTFLAGS=$RUSTFLAGS" -e "AWS_LC_SYS_CMAKE_TOOLCHAIN_FILE_riscv64gc_unknown_linux_musl=/root/cmake-overrides/toolchain-riscv64-musl-clang.cmake" -e SCCACHE_GHA_ENABLED -e SCCACHE_GHA_VERSION -e ACTIONS_RESULTS_URL -e ACTIONS_RUNTIME_TOKEN -v "$HOME/.cargo/registry":/usr/local/cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$HOME/.cache/sccache":/root/.cache/sccache -v "$(pwd)":/workdir -w /workdir -P start9/cargo-zigbuild'
|
||||||
|
|||||||
@@ -2,12 +2,19 @@
|
|||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
set -ea
|
set -ea
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
PROFILE=${PROFILE:-release}
|
PROFILE=${PROFILE:-release}
|
||||||
if [ "${PROFILE}" = "release" ]; then
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
BUILD_FLAGS="--release"
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
@@ -31,8 +38,8 @@ if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
|||||||
RUSTFLAGS="--cfg tokio_unstable"
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
source ./core/builder-alias.sh
|
|
||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
cross test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=test,$FEATURES --workspace --locked --target=$ARCH-unknown-linux-musl -- --skip export_bindings_
|
rust-zig-builder cargo test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=test,$FEATURES --workspace --locked -- --skip export_bindings_
|
||||||
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||||
@@ -15,7 +15,7 @@ license = "MIT"
|
|||||||
name = "start-os"
|
name = "start-os"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/Start9Labs/start-os"
|
repository = "https://github.com/Start9Labs/start-os"
|
||||||
version = "0.4.0-alpha.12" # VERSION_BUMP
|
version = "0.4.0-alpha.15" # VERSION_BUMP
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "startos"
|
name = "startos"
|
||||||
@@ -93,7 +93,6 @@ async-compression = { version = "0.4.32", features = [
|
|||||||
] }
|
] }
|
||||||
async-stream = "0.3.5"
|
async-stream = "0.3.5"
|
||||||
async-trait = "0.1.74"
|
async-trait = "0.1.74"
|
||||||
aws-lc-sys = { version = "0.32", features = ["bindgen"] }
|
|
||||||
axum = { version = "0.8.4", features = ["ws"] }
|
axum = { version = "0.8.4", features = ["ws"] }
|
||||||
backtrace-on-stack-overflow = { version = "0.3.0", optional = true }
|
backtrace-on-stack-overflow = { version = "0.3.0", optional = true }
|
||||||
barrage = "0.2.3"
|
barrage = "0.2.3"
|
||||||
@@ -223,7 +222,7 @@ regex = "1.10.2"
|
|||||||
reqwest = { version = "0.12.4", features = ["json", "socks", "stream"] }
|
reqwest = { version = "0.12.4", features = ["json", "socks", "stream"] }
|
||||||
reqwest_cookie_store = "0.8.0"
|
reqwest_cookie_store = "0.8.0"
|
||||||
rpassword = "7.2.0"
|
rpassword = "7.2.0"
|
||||||
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" }
|
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", rev = "068db90" }
|
||||||
rust-argon2 = "2.0.0"
|
rust-argon2 = "2.0.0"
|
||||||
safelog = { version = "0.4.8", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
safelog = { version = "0.4.8", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
semver = { version = "1.0.20", features = ["serde"] }
|
semver = { version = "1.0.20", features = ["serde"] }
|
||||||
@@ -252,7 +251,7 @@ termion = "4.0.5"
|
|||||||
textwrap = "0.16.1"
|
textwrap = "0.16.1"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
tokio = { version = "1.38.1", features = ["full"] }
|
tokio = { version = "1.38.1", features = ["full"] }
|
||||||
tokio-rustls = "0.26.0"
|
tokio-rustls = "0.26.4"
|
||||||
tokio-stream = { version = "0.1.14", features = ["io-util", "net", "sync"] }
|
tokio-stream = { version = "0.1.14", features = ["io-util", "net", "sync"] }
|
||||||
tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" }
|
tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" }
|
||||||
tokio-tungstenite = { version = "0.26.2", features = ["native-tls", "url"] }
|
tokio-tungstenite = { version = "0.26.2", features = ["native-tls", "url"] }
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use crate::tunnel::tunnel_router;
|
|||||||
use crate::tunnel::web::TunnelCertHandler;
|
use crate::tunnel::web::TunnelCertHandler;
|
||||||
use crate::util::logger::LOGGER;
|
use crate::util::logger::LOGGER;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
enum WebserverListener {
|
enum WebserverListener {
|
||||||
Http,
|
Http,
|
||||||
Https(SocketAddr),
|
Https(SocketAddr),
|
||||||
|
|||||||
@@ -260,11 +260,7 @@ impl NetworkInterfaceInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn secure(&self) -> bool {
|
pub fn secure(&self) -> bool {
|
||||||
self.secure.unwrap_or_else(|| {
|
self.secure.unwrap_or(false)
|
||||||
self.ip_info.as_ref().map_or(false, |ip_info| {
|
|
||||||
ip_info.device_type == Some(NetworkInterfaceType::Wireguard)
|
|
||||||
}) && !self.public()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ pub async fn bind<P0: AsRef<Path>, P1: AsRef<Path>>(
|
|||||||
pub async fn unmount<P: AsRef<Path>>(mountpoint: P, lazy: bool) -> Result<(), Error> {
|
pub async fn unmount<P: AsRef<Path>>(mountpoint: P, lazy: bool) -> Result<(), Error> {
|
||||||
tracing::debug!("Unmounting {}.", mountpoint.as_ref().display());
|
tracing::debug!("Unmounting {}.", mountpoint.as_ref().display());
|
||||||
let mut cmd = tokio::process::Command::new("umount");
|
let mut cmd = tokio::process::Command::new("umount");
|
||||||
cmd.arg("-R");
|
|
||||||
if lazy {
|
if lazy {
|
||||||
cmd.arg("-l");
|
cmd.arg("-l");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,6 +280,9 @@ pub async fn list(os: &OsPartitionInfo) -> Result<Vec<DiskInfo>, Error> {
|
|||||||
.try_fold(
|
.try_fold(
|
||||||
BTreeMap::<PathBuf, DiskIndex>::new(),
|
BTreeMap::<PathBuf, DiskIndex>::new(),
|
||||||
|mut disks, dir_entry| async move {
|
|mut disks, dir_entry| async move {
|
||||||
|
if dir_entry.file_type().await?.is_dir() {
|
||||||
|
return Ok(disks);
|
||||||
|
}
|
||||||
if let Some(disk_path) = dir_entry.path().file_name().and_then(|s| s.to_str()) {
|
if let Some(disk_path) = dir_entry.path().file_name().and_then(|s| s.to_str()) {
|
||||||
let (disk_path, part_path) = if let Some(end) = PARTITION_REGEX.find(disk_path) {
|
let (disk_path, part_path) = if let Some(end) = PARTITION_REGEX.find(disk_path) {
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ use crate::registry::context::{RegistryContext, RegistryUrlParams};
|
|||||||
use crate::registry::package::get::GetPackageResponse;
|
use crate::registry::package::get::GetPackageResponse;
|
||||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
|
use crate::s9pk::v2::SIG_CONTEXT;
|
||||||
use crate::upload::upload;
|
use crate::upload::upload;
|
||||||
use crate::util::Never;
|
use crate::util::Never;
|
||||||
use crate::util::io::open_file;
|
use crate::util::io::open_file;
|
||||||
@@ -154,6 +155,8 @@ pub async fn install(
|
|||||||
})?
|
})?
|
||||||
.s9pk;
|
.s9pk;
|
||||||
|
|
||||||
|
asset.validate(SIG_CONTEXT, asset.all_signers())?;
|
||||||
|
|
||||||
let progress_tracker = FullProgressTracker::new();
|
let progress_tracker = FullProgressTracker::new();
|
||||||
let download_progress = progress_tracker.add_phase("Downloading".into(), Some(100));
|
let download_progress = progress_tracker.add_phase("Downloading".into(), Some(100));
|
||||||
let download = ctx
|
let download = ctx
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ impl LxcContainer {
|
|||||||
}
|
}
|
||||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
|
tracing::info!("Connected to socket in {:?}", started.elapsed());
|
||||||
Ok(UnixRpcClient::new(sock_path))
|
Ok(UnixRpcClient::new(sock_path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ use crate::context::{CliContext, RpcContext};
|
|||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
use crate::db::model::public::AcmeSettings;
|
use crate::db::model::public::AcmeSettings;
|
||||||
use crate::db::{DbAccess, DbAccessByKey, DbAccessMut};
|
use crate::db::{DbAccess, DbAccessByKey, DbAccessMut};
|
||||||
|
use crate::net::ssl::should_use_cert;
|
||||||
use crate::net::tls::{SingleCertResolver, TlsHandler};
|
use crate::net::tls::{SingleCertResolver, TlsHandler};
|
||||||
use crate::net::web_server::Accept;
|
use crate::net::web_server::Accept;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -63,20 +64,27 @@ where
|
|||||||
.and_then(|p| p.as_idx(JsonKey::new_ref(san_info)))
|
.and_then(|p| p.as_idx(JsonKey::new_ref(san_info)))
|
||||||
{
|
{
|
||||||
let cert = cert.de().log_err()?;
|
let cert = cert.de().log_err()?;
|
||||||
return Some(
|
if cert
|
||||||
CertifiedKey::from_der(
|
.fullchain
|
||||||
cert.fullchain
|
.get(0)
|
||||||
.into_iter()
|
.and_then(|c| should_use_cert(&c.0).log_err())
|
||||||
.map(|c| Ok(CertificateDer::from(c.to_der()?)))
|
.unwrap_or(false)
|
||||||
.collect::<Result<_, Error>>()
|
{
|
||||||
.log_err()?,
|
return Some(
|
||||||
PrivateKeyDer::from(PrivatePkcs8KeyDer::from(
|
CertifiedKey::from_der(
|
||||||
cert.key.0.private_key_to_pkcs8().log_err()?,
|
cert.fullchain
|
||||||
)),
|
.into_iter()
|
||||||
&*self.crypto_provider,
|
.map(|c| Ok(CertificateDer::from(c.to_der()?)))
|
||||||
)
|
.collect::<Result<_, Error>>()
|
||||||
.log_err()?,
|
.log_err()?,
|
||||||
);
|
PrivateKeyDer::from(PrivatePkcs8KeyDer::from(
|
||||||
|
cert.key.0.private_key_to_pkcs8().log_err()?,
|
||||||
|
)),
|
||||||
|
&*self.crypto_provider,
|
||||||
|
)
|
||||||
|
.log_err()?,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.in_progress.send_if_modified(|x| {
|
if !self.in_progress.send_if_modified(|x| {
|
||||||
@@ -307,6 +315,16 @@ where
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
let cert = cert.de()?;
|
let cert = cert.de()?;
|
||||||
|
if !cert
|
||||||
|
.fullchain
|
||||||
|
.get(0)
|
||||||
|
.map(|c| should_use_cert(&c.0))
|
||||||
|
.transpose()
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
Ok(Some((
|
Ok(Some((
|
||||||
String::from_utf8(
|
String::from_utf8(
|
||||||
cert.key
|
cert.key
|
||||||
|
|||||||
@@ -437,7 +437,8 @@ impl InterfaceForwardState {
|
|||||||
for mut entry in self.state.iter_mut() {
|
for mut entry in self.state.iter_mut() {
|
||||||
entry.gc(ip_info, &self.port_forward).await?;
|
entry.gc(ip_info, &self.port_forward).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
|
self.port_forward.gc().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,7 +538,6 @@ impl InterfacePortForwardController {
|
|||||||
_ = ip_info.changed() => {
|
_ = ip_info.changed() => {
|
||||||
interfaces = ip_info.read();
|
interfaces = ip_info.read();
|
||||||
state.sync(&interfaces).await.log_err();
|
state.sync(&interfaces).await.log_err();
|
||||||
state.port_forward.gc().await.log_err();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||||
|
use std::fmt;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV6};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV6};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
@@ -130,7 +131,6 @@ async fn list_interfaces(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||||
#[ts(export)]
|
|
||||||
struct NetworkInterfaceSetPublicParams {
|
struct NetworkInterfaceSetPublicParams {
|
||||||
gateway: GatewayId,
|
gateway: GatewayId,
|
||||||
public: Option<bool>,
|
public: Option<bool>,
|
||||||
@@ -147,7 +147,6 @@ async fn set_public(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||||
#[ts(export)]
|
|
||||||
struct UnsetPublicParams {
|
struct UnsetPublicParams {
|
||||||
gateway: GatewayId,
|
gateway: GatewayId,
|
||||||
}
|
}
|
||||||
@@ -163,7 +162,6 @@ async fn unset_public(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||||
#[ts(export)]
|
|
||||||
struct ForgetGatewayParams {
|
struct ForgetGatewayParams {
|
||||||
gateway: GatewayId,
|
gateway: GatewayId,
|
||||||
}
|
}
|
||||||
@@ -176,7 +174,6 @@ async fn forget_iface(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||||
#[ts(export)]
|
|
||||||
struct RenameGatewayParams {
|
struct RenameGatewayParams {
|
||||||
id: GatewayId,
|
id: GatewayId,
|
||||||
name: InternedString,
|
name: InternedString,
|
||||||
@@ -404,6 +401,12 @@ async fn watcher(
|
|||||||
) {
|
) {
|
||||||
loop {
|
loop {
|
||||||
let res: Result<(), Error> = async {
|
let res: Result<(), Error> = async {
|
||||||
|
Command::new("systemctl")
|
||||||
|
.arg("start")
|
||||||
|
.arg("NetworkManager")
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let connection = Connection::system().await?;
|
let connection = Connection::system().await?;
|
||||||
|
|
||||||
let netman_proxy = NetworkManagerProxy::new(&connection).await?;
|
let netman_proxy = NetworkManagerProxy::new(&connection).await?;
|
||||||
@@ -436,6 +439,11 @@ async fn watcher(
|
|||||||
until
|
until
|
||||||
.run(async {
|
.run(async {
|
||||||
let devices = netman_proxy.all_devices().await?;
|
let devices = netman_proxy.all_devices().await?;
|
||||||
|
ensure_code!(
|
||||||
|
!devices.is_empty(),
|
||||||
|
ErrorKind::Network,
|
||||||
|
"NetworkManager returned no devices. Trying again..."
|
||||||
|
);
|
||||||
let mut ifaces = BTreeSet::new();
|
let mut ifaces = BTreeSet::new();
|
||||||
let mut jobs = Vec::new();
|
let mut jobs = Vec::new();
|
||||||
for device in devices {
|
for device in devices {
|
||||||
@@ -1538,6 +1546,14 @@ pub struct NetworkInterfaceListenerAcceptMetadata<B: Bind> {
|
|||||||
pub inner: <B::Accept as Accept>::Metadata,
|
pub inner: <B::Accept as Accept>::Metadata,
|
||||||
pub info: GatewayInfo,
|
pub info: GatewayInfo,
|
||||||
}
|
}
|
||||||
|
impl<B: Bind> fmt::Debug for NetworkInterfaceListenerAcceptMetadata<B> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("NetworkInterfaceListenerAcceptMetadata")
|
||||||
|
.field("inner", &self.inner)
|
||||||
|
.field("info", &self.info)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<B: Bind> Clone for NetworkInterfaceListenerAcceptMetadata<B>
|
impl<B: Bind> Clone for NetworkInterfaceListenerAcceptMetadata<B>
|
||||||
where
|
where
|
||||||
<B::Accept as Accept>::Metadata: Clone,
|
<B::Accept as Accept>::Metadata: Clone,
|
||||||
@@ -1614,3 +1630,39 @@ where
|
|||||||
Self::new(Some(Either::Left(listener)))
|
Self::new(Some(Either::Left(listener)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filter() {
|
||||||
|
use crate::net::host::binding::NetInfo;
|
||||||
|
let wg1 = "wg1".parse::<GatewayId>().unwrap();
|
||||||
|
assert!(!InterfaceFilter::filter(
|
||||||
|
&AndFilter(
|
||||||
|
NetInfo {
|
||||||
|
private_disabled: [wg1.clone()].into_iter().collect(),
|
||||||
|
public_enabled: Default::default(),
|
||||||
|
assigned_port: None,
|
||||||
|
assigned_ssl_port: None,
|
||||||
|
},
|
||||||
|
AndFilter(IdFilter(wg1.clone()), PublicFilter { public: false }),
|
||||||
|
)
|
||||||
|
.into_dyn(),
|
||||||
|
&wg1,
|
||||||
|
&NetworkInterfaceInfo {
|
||||||
|
name: None,
|
||||||
|
public: None,
|
||||||
|
secure: None,
|
||||||
|
ip_info: Some(Arc::new(IpInfo {
|
||||||
|
name: "".into(),
|
||||||
|
scope_id: 3,
|
||||||
|
device_type: Some(NetworkInterfaceType::Wireguard),
|
||||||
|
subnets: ["10.59.0.2/24".parse::<IpNet>().unwrap()]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
lan_ip: Default::default(),
|
||||||
|
wan_ip: None,
|
||||||
|
ntp_servers: Default::default(),
|
||||||
|
dns_servers: Default::default(),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use openssl::x509::extension::{
|
|||||||
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName,
|
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName,
|
||||||
SubjectKeyIdentifier,
|
SubjectKeyIdentifier,
|
||||||
};
|
};
|
||||||
use openssl::x509::{X509, X509Builder, X509NameBuilder};
|
use openssl::x509::{X509, X509Builder, X509NameBuilder, X509Ref};
|
||||||
use openssl::*;
|
use openssl::*;
|
||||||
use patch_db::HasModel;
|
use patch_db::HasModel;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -48,6 +48,17 @@ pub fn gen_nistp256() -> Result<PKey<Private>, ErrorStack> {
|
|||||||
)?)?)
|
)?)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn should_use_cert(cert: &X509Ref) -> Result<bool, ErrorStack> {
|
||||||
|
Ok(cert
|
||||||
|
.not_before()
|
||||||
|
.compare(Asn1Time::days_from_now(0)?.as_ref())?
|
||||||
|
== Ordering::Less
|
||||||
|
&& cert
|
||||||
|
.not_after()
|
||||||
|
.compare(Asn1Time::days_from_now(30)?.as_ref())?
|
||||||
|
== Ordering::Greater)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -83,30 +94,8 @@ impl Model<CertStore> {
|
|||||||
.map(|m| m.de())
|
.map(|m| m.de())
|
||||||
.transpose()?
|
.transpose()?
|
||||||
{
|
{
|
||||||
if cert_data
|
if should_use_cert(&cert_data.certs.ed25519)?
|
||||||
.certs
|
&& should_use_cert(&cert_data.certs.nistp256)?
|
||||||
.ed25519
|
|
||||||
.not_before()
|
|
||||||
.compare(Asn1Time::days_from_now(0)?.as_ref())?
|
|
||||||
== Ordering::Less
|
|
||||||
&& cert_data
|
|
||||||
.certs
|
|
||||||
.ed25519
|
|
||||||
.not_after()
|
|
||||||
.compare(Asn1Time::days_from_now(30)?.as_ref())?
|
|
||||||
== Ordering::Greater
|
|
||||||
&& cert_data
|
|
||||||
.certs
|
|
||||||
.nistp256
|
|
||||||
.not_before()
|
|
||||||
.compare(Asn1Time::days_from_now(0)?.as_ref())?
|
|
||||||
== Ordering::Less
|
|
||||||
&& cert_data
|
|
||||||
.certs
|
|
||||||
.nistp256
|
|
||||||
.not_after()
|
|
||||||
.compare(Asn1Time::days_from_now(30)?.as_ref())?
|
|
||||||
== Ordering::Greater
|
|
||||||
{
|
{
|
||||||
return Ok(FullchainCertData {
|
return Ok(FullchainCertData {
|
||||||
root: self.as_root_cert().de()?.0,
|
root: self.as_root_cert().de()?.0,
|
||||||
@@ -251,12 +240,16 @@ impl CertPair {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn root_ca_start_time() -> Result<SystemTime, Error> {
|
pub async fn root_ca_start_time() -> SystemTime {
|
||||||
Ok(if check_time_is_synchronized().await? {
|
if check_time_is_synchronized()
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
} else {
|
} else {
|
||||||
*SOURCE_DATE
|
*SOURCE_DATE
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const EC_CURVE_NAME: nid::Nid = nid::Nid::X9_62_PRIME256V1;
|
const EC_CURVE_NAME: nid::Nid = nid::Nid::X9_62_PRIME256V1;
|
||||||
|
|||||||
@@ -217,10 +217,15 @@ where
|
|||||||
.write_all(&buffered)
|
.write_all(&buffered)
|
||||||
.await
|
.await
|
||||||
.with_kind(ErrorKind::Network)?;
|
.with_kind(ErrorKind::Network)?;
|
||||||
return Ok(Some((
|
let stream = match mid.into_stream(Arc::new(cfg)).await {
|
||||||
metadata,
|
Ok(stream) => Box::pin(stream) as AcceptStream,
|
||||||
Box::pin(mid.into_stream(Arc::new(cfg)).await?) as AcceptStream,
|
Err(e) => {
|
||||||
)));
|
tracing::trace!("Error completing TLS handshake: {e}");
|
||||||
|
tracing::trace!("{e:?}");
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Ok(Some((metadata, stream)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|||||||
@@ -649,16 +649,6 @@ async fn torctl(
|
|||||||
.invoke(ErrorKind::Tor)
|
.invoke(ErrorKind::Tor)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let logs = journalctl(
|
|
||||||
LogSource::Unit(SYSTEMD_UNIT),
|
|
||||||
Some(0),
|
|
||||||
None,
|
|
||||||
Some("0"),
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut tcp_stream = None;
|
let mut tcp_stream = None;
|
||||||
for _ in 0..60 {
|
for _ in 0..60 {
|
||||||
if let Ok(conn) = TcpStream::connect(tor_control).await {
|
if let Ok(conn) = TcpStream::connect(tor_control).await {
|
||||||
@@ -720,7 +710,7 @@ async fn torctl(
|
|||||||
ErrorKind::Tor,
|
ErrorKind::Tor,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok((connection, logs))
|
Ok(connection)
|
||||||
};
|
};
|
||||||
let pre_handler = async {
|
let pre_handler = async {
|
||||||
while let Some(command) = recv.recv().await {
|
while let Some(command) = recv.recv().await {
|
||||||
@@ -745,7 +735,7 @@ async fn torctl(
|
|||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut connection, mut logs) = tokio::select! {
|
let mut connection = tokio::select! {
|
||||||
res = bootstrap => res?,
|
res = bootstrap => res?,
|
||||||
res = pre_handler => return res,
|
res = pre_handler => return res,
|
||||||
};
|
};
|
||||||
@@ -851,45 +841,59 @@ async fn torctl(
|
|||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
let log_parser = async {
|
let log_parser = async {
|
||||||
while let Some(log) = logs.try_next().await? {
|
loop {
|
||||||
for (regex, severity) in &*LOG_REGEXES {
|
let mut logs = journalctl(
|
||||||
if regex.is_match(&log.message) {
|
LogSource::Unit(SYSTEMD_UNIT),
|
||||||
let (check, wipe_state) = match severity {
|
Some(0),
|
||||||
ErrorLogSeverity::Fatal { wipe_state } => (false, *wipe_state),
|
None,
|
||||||
ErrorLogSeverity::Unknown { wipe_state } => (true, *wipe_state),
|
Some("0"),
|
||||||
};
|
false,
|
||||||
let addr = hck_key.public().get_onion_address().to_string();
|
true,
|
||||||
if !check
|
)
|
||||||
|| TcpStream::connect(tor_socks)
|
.await?;
|
||||||
.map_err(|e| Error::new(e, ErrorKind::Tor))
|
while let Some(log) = logs.try_next().await? {
|
||||||
.and_then(|mut tor_socks| async move {
|
for (regex, severity) in &*LOG_REGEXES {
|
||||||
tokio::time::timeout(
|
if regex.is_match(&log.message) {
|
||||||
Duration::from_secs(30),
|
let (check, wipe_state) = match severity {
|
||||||
socks5_impl::client::connect(&mut tor_socks, (addr, 80), None)
|
ErrorLogSeverity::Fatal { wipe_state } => (false, *wipe_state),
|
||||||
.map_err(|e| Error::new(e, ErrorKind::Tor)),
|
ErrorLogSeverity::Unknown { wipe_state } => (true, *wipe_state),
|
||||||
)
|
};
|
||||||
|
let addr = hck_key.public().get_onion_address().to_string();
|
||||||
|
if !check
|
||||||
|
|| TcpStream::connect(tor_socks)
|
||||||
.map_err(|e| Error::new(e, ErrorKind::Tor))
|
.map_err(|e| Error::new(e, ErrorKind::Tor))
|
||||||
.await?
|
.and_then(|mut tor_socks| async move {
|
||||||
})
|
tokio::time::timeout(
|
||||||
.await
|
Duration::from_secs(30),
|
||||||
.with_ctx(|_| (ErrorKind::Tor, "Tor is confirmed to be down"))
|
socks5_impl::client::connect(
|
||||||
.log_err()
|
&mut tor_socks,
|
||||||
.is_some()
|
(addr, 80),
|
||||||
{
|
None,
|
||||||
if wipe_state {
|
)
|
||||||
Command::new("systemctl")
|
.map_err(|e| Error::new(e, ErrorKind::Tor)),
|
||||||
.arg("stop")
|
)
|
||||||
.arg("tor")
|
.map_err(|e| Error::new(e, ErrorKind::Tor))
|
||||||
.invoke(ErrorKind::Tor)
|
.await?
|
||||||
.await?;
|
})
|
||||||
tokio::fs::remove_dir_all("/var/lib/tor").await?;
|
.await
|
||||||
|
.with_ctx(|_| (ErrorKind::Tor, "Tor is confirmed to be down"))
|
||||||
|
.log_err()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
if wipe_state {
|
||||||
|
Command::new("systemctl")
|
||||||
|
.arg("stop")
|
||||||
|
.arg("tor")
|
||||||
|
.invoke(ErrorKind::Tor)
|
||||||
|
.await?;
|
||||||
|
tokio::fs::remove_dir_all("/var/lib/tor").await?;
|
||||||
|
}
|
||||||
|
return Err(Error::new(eyre!("{}", log.message), ErrorKind::Tor));
|
||||||
}
|
}
|
||||||
return Err(Error::new(eyre!("{}", log.message), ErrorKind::Tor));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(Error::new(eyre!("Log stream terminated"), ErrorKind::Tor))
|
|
||||||
};
|
};
|
||||||
let health_checker = async {
|
let health_checker = async {
|
||||||
let mut last_success = Instant::now();
|
let mut last_success = Instant::now();
|
||||||
@@ -959,20 +963,23 @@ impl TorControl {
|
|||||||
_thread: tokio::spawn(async move {
|
_thread: tokio::spawn(async move {
|
||||||
let wipe_state = AtomicBool::new(false);
|
let wipe_state = AtomicBool::new(false);
|
||||||
let mut health_timeout = Duration::from_secs(STARTING_HEALTH_TIMEOUT);
|
let mut health_timeout = Duration::from_secs(STARTING_HEALTH_TIMEOUT);
|
||||||
while let Err(e) = torctl(
|
loop {
|
||||||
tor_control,
|
if let Err(e) = torctl(
|
||||||
tor_socks,
|
tor_control,
|
||||||
&mut recv,
|
tor_socks,
|
||||||
&mut thread_services,
|
&mut recv,
|
||||||
&wipe_state,
|
&mut thread_services,
|
||||||
&mut health_timeout,
|
&wipe_state,
|
||||||
)
|
&mut health_timeout,
|
||||||
.await
|
)
|
||||||
{
|
.await
|
||||||
tracing::error!("{e}: Restarting tor");
|
{
|
||||||
tracing::debug!("{e:?}");
|
tracing::error!("TorControl : {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
}
|
||||||
|
tracing::info!("Restarting Tor");
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
tracing::info!("TorControl is shut down.")
|
|
||||||
})
|
})
|
||||||
.into(),
|
.into(),
|
||||||
send,
|
send,
|
||||||
|
|||||||
@@ -39,6 +39,23 @@ pub struct AddTunnelParams {
|
|||||||
public: bool,
|
public: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sanitize_config(config: &str) -> String {
|
||||||
|
let mut res = String::with_capacity(config.len());
|
||||||
|
for line in config.lines() {
|
||||||
|
if line
|
||||||
|
.trim()
|
||||||
|
.strip_prefix("AllowedIPs")
|
||||||
|
.map_or(false, |l| l.trim().starts_with("="))
|
||||||
|
{
|
||||||
|
res.push_str("AllowedIPs = 0.0.0.0/0, ::/0");
|
||||||
|
} else {
|
||||||
|
res.push_str(line);
|
||||||
|
}
|
||||||
|
res.push('\n');
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn add_tunnel(
|
pub async fn add_tunnel(
|
||||||
ctx: RpcContext,
|
ctx: RpcContext,
|
||||||
AddTunnelParams {
|
AddTunnelParams {
|
||||||
@@ -86,7 +103,7 @@ pub async fn add_tunnel(
|
|||||||
|
|
||||||
let tmpdir = TmpDir::new().await?;
|
let tmpdir = TmpDir::new().await?;
|
||||||
let conf = tmpdir.join(&iface).with_extension("conf");
|
let conf = tmpdir.join(&iface).with_extension("conf");
|
||||||
write_file_atomic(&conf, &config).await?;
|
write_file_atomic(&conf, &sanitize_config(&config)).await?;
|
||||||
Command::new("nmcli")
|
Command::new("nmcli")
|
||||||
.arg("connection")
|
.arg("connection")
|
||||||
.arg("import")
|
.arg("import")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::fmt;
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use std::task::{Poll, ready};
|
use std::task::{Poll, ready};
|
||||||
@@ -41,6 +42,7 @@ use crate::net::tls::{
|
|||||||
use crate::net::web_server::{Accept, AcceptStream, ExtractVisitor, TcpMetadata, extract};
|
use crate::net::web_server::{Accept, AcceptStream, ExtractVisitor, TcpMetadata, extract};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::collections::EqSet;
|
use crate::util::collections::EqSet;
|
||||||
|
use crate::util::future::WeakFuture;
|
||||||
use crate::util::serde::{HandlerExtSerde, MaybeUtf8String, display_serializable};
|
use crate::util::serde::{HandlerExtSerde, MaybeUtf8String, display_serializable};
|
||||||
use crate::util::sync::{SyncMutex, Watch};
|
use crate::util::sync::{SyncMutex, Watch};
|
||||||
|
|
||||||
@@ -134,7 +136,6 @@ impl VHostController {
|
|||||||
pub fn dump_table(
|
pub fn dump_table(
|
||||||
&self,
|
&self,
|
||||||
) -> BTreeMap<JsonKey<u16>, BTreeMap<JsonKey<Option<InternedString>>, EqSet<String>>> {
|
) -> BTreeMap<JsonKey<u16>, BTreeMap<JsonKey<Option<InternedString>>, EqSet<String>>> {
|
||||||
let ip_info = self.interfaces.watcher.ip_info();
|
|
||||||
self.servers.peek(|s| {
|
self.servers.peek(|s| {
|
||||||
s.iter()
|
s.iter()
|
||||||
.map(|(k, v)| {
|
.map(|(k, v)| {
|
||||||
@@ -187,7 +188,7 @@ pub trait VHostTarget<A: Accept>: std::fmt::Debug + Eq {
|
|||||||
hello: &'a ClientHello<'a>,
|
hello: &'a ClientHello<'a>,
|
||||||
metadata: &'a <A as Accept>::Metadata,
|
metadata: &'a <A as Accept>::Metadata,
|
||||||
) -> impl Future<Output = Option<(ServerConfig, Self::PreprocessRes)>> + Send + 'a;
|
) -> impl Future<Output = Option<(ServerConfig, Self::PreprocessRes)>> + Send + 'a;
|
||||||
fn handle_stream(&self, stream: AcceptStream, prev: Self::PreprocessRes);
|
fn handle_stream(&self, stream: AcceptStream, prev: Self::PreprocessRes, rc: Weak<()>);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DynVHostTargetT<A: Accept>: std::fmt::Debug + Any {
|
pub trait DynVHostTargetT<A: Accept>: std::fmt::Debug + Any {
|
||||||
@@ -199,7 +200,7 @@ pub trait DynVHostTargetT<A: Accept>: std::fmt::Debug + Any {
|
|||||||
hello: &'a ClientHello<'a>,
|
hello: &'a ClientHello<'a>,
|
||||||
metadata: &'a <A as Accept>::Metadata,
|
metadata: &'a <A as Accept>::Metadata,
|
||||||
) -> BoxFuture<'a, Option<(ServerConfig, Box<dyn Any + Send>)>>;
|
) -> BoxFuture<'a, Option<(ServerConfig, Box<dyn Any + Send>)>>;
|
||||||
fn handle_stream(&self, stream: AcceptStream, prev: Box<dyn Any + Send>);
|
fn handle_stream(&self, stream: AcceptStream, prev: Box<dyn Any + Send>, rc: Weak<()>);
|
||||||
fn eq(&self, other: &dyn DynVHostTargetT<A>) -> bool;
|
fn eq(&self, other: &dyn DynVHostTargetT<A>) -> bool;
|
||||||
}
|
}
|
||||||
impl<A: Accept, T: VHostTarget<A> + 'static> DynVHostTargetT<A> for T {
|
impl<A: Accept, T: VHostTarget<A> + 'static> DynVHostTargetT<A> for T {
|
||||||
@@ -219,9 +220,9 @@ impl<A: Accept, T: VHostTarget<A> + 'static> DynVHostTargetT<A> for T {
|
|||||||
.map(|o| o.map(|(cfg, res)| (cfg, Box::new(res) as Box<dyn Any + Send>)))
|
.map(|o| o.map(|(cfg, res)| (cfg, Box::new(res) as Box<dyn Any + Send>)))
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
fn handle_stream(&self, stream: AcceptStream, prev: Box<dyn Any + Send>) {
|
fn handle_stream(&self, stream: AcceptStream, prev: Box<dyn Any + Send>, rc: Weak<()>) {
|
||||||
if let Ok(prev) = prev.downcast() {
|
if let Ok(prev) = prev.downcast() {
|
||||||
VHostTarget::handle_stream(self, stream, *prev);
|
VHostTarget::handle_stream(self, stream, *prev, rc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn eq(&self, other: &dyn DynVHostTargetT<A>) -> bool {
|
fn eq(&self, other: &dyn DynVHostTargetT<A>) -> bool {
|
||||||
@@ -251,21 +252,27 @@ impl<A: Accept + 'static> PartialEq for DynVHostTarget<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<A: Accept + 'static> Eq for DynVHostTarget<A> {}
|
impl<A: Accept + 'static> Eq for DynVHostTarget<A> {}
|
||||||
struct Preprocessed<A: Accept>(DynVHostTarget<A>, Box<dyn Any + Send>);
|
struct Preprocessed<A: Accept>(DynVHostTarget<A>, Weak<()>, Box<dyn Any + Send>);
|
||||||
|
impl<A: Accept> fmt::Debug for Preprocessed<A> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
(self.0).0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<A: Accept + 'static> DynVHostTarget<A> {
|
impl<A: Accept + 'static> DynVHostTarget<A> {
|
||||||
async fn into_preprocessed(
|
async fn into_preprocessed(
|
||||||
self,
|
self,
|
||||||
|
rc: Weak<()>,
|
||||||
prev: ServerConfig,
|
prev: ServerConfig,
|
||||||
hello: &ClientHello<'_>,
|
hello: &ClientHello<'_>,
|
||||||
metadata: &<A as Accept>::Metadata,
|
metadata: &<A as Accept>::Metadata,
|
||||||
) -> Option<(ServerConfig, Preprocessed<A>)> {
|
) -> Option<(ServerConfig, Preprocessed<A>)> {
|
||||||
let (cfg, res) = self.0.preprocess(prev, hello, metadata).await?;
|
let (cfg, res) = self.0.preprocess(prev, hello, metadata).await?;
|
||||||
Some((cfg, Preprocessed(self, res)))
|
Some((cfg, Preprocessed(self, rc, res)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<A: Accept + 'static> Preprocessed<A> {
|
impl<A: Accept + 'static> Preprocessed<A> {
|
||||||
fn finish(self, stream: AcceptStream) {
|
fn finish(self, stream: AcceptStream) {
|
||||||
(self.0).0.handle_stream(stream, self.1);
|
(self.0).0.handle_stream(stream, self.2, self.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,6 +286,7 @@ pub struct ProxyTarget {
|
|||||||
impl PartialEq for ProxyTarget {
|
impl PartialEq for ProxyTarget {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.filter == other.filter
|
self.filter == other.filter
|
||||||
|
&& self.acme == other.acme
|
||||||
&& self.addr == other.addr
|
&& self.addr == other.addr
|
||||||
&& self.connect_ssl.as_ref().map(Arc::as_ptr)
|
&& self.connect_ssl.as_ref().map(Arc::as_ptr)
|
||||||
== other.connect_ssl.as_ref().map(Arc::as_ptr)
|
== other.connect_ssl.as_ref().map(Arc::as_ptr)
|
||||||
@@ -294,6 +302,9 @@ where
|
|||||||
type PreprocessRes = AcceptStream;
|
type PreprocessRes = AcceptStream;
|
||||||
fn filter(&self, metadata: &<A as Accept>::Metadata) -> bool {
|
fn filter(&self, metadata: &<A as Accept>::Metadata) -> bool {
|
||||||
let info = extract::<GatewayInfo, _>(metadata);
|
let info = extract::<GatewayInfo, _>(metadata);
|
||||||
|
if info.is_none() {
|
||||||
|
tracing::warn!("No GatewayInfo on metadata");
|
||||||
|
}
|
||||||
info.as_ref()
|
info.as_ref()
|
||||||
.map_or(true, |i| self.filter.filter(&i.id, &i.info))
|
.map_or(true, |i| self.filter.filter(&i.id, &i.info))
|
||||||
}
|
}
|
||||||
@@ -304,7 +315,7 @@ where
|
|||||||
&'a self,
|
&'a self,
|
||||||
mut prev: ServerConfig,
|
mut prev: ServerConfig,
|
||||||
hello: &'a ClientHello<'a>,
|
hello: &'a ClientHello<'a>,
|
||||||
metadata: &'a <A as Accept>::Metadata,
|
_: &'a <A as Accept>::Metadata,
|
||||||
) -> Option<(ServerConfig, Self::PreprocessRes)> {
|
) -> Option<(ServerConfig, Self::PreprocessRes)> {
|
||||||
let tcp_stream = TcpStream::connect(self.addr)
|
let tcp_stream = TcpStream::connect(self.addr)
|
||||||
.await
|
.await
|
||||||
@@ -345,8 +356,10 @@ where
|
|||||||
}
|
}
|
||||||
Some((prev, Box::pin(tcp_stream)))
|
Some((prev, Box::pin(tcp_stream)))
|
||||||
}
|
}
|
||||||
fn handle_stream(&self, mut stream: AcceptStream, mut prev: Self::PreprocessRes) {
|
fn handle_stream(&self, mut stream: AcceptStream, mut prev: Self::PreprocessRes, rc: Weak<()>) {
|
||||||
tokio::spawn(async move { tokio::io::copy_bidirectional(&mut stream, &mut prev).await });
|
tokio::spawn(async move {
|
||||||
|
WeakFuture::new(rc, tokio::io::copy_bidirectional(&mut stream, &mut prev)).await
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,16 +449,16 @@ where
|
|||||||
return Some(prev);
|
return Some(prev);
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = self.0.peek(|m| {
|
let (target, rc) = self.0.peek(|m| {
|
||||||
m.get(&hello.server_name().map(InternedString::from))
|
m.get(&hello.server_name().map(InternedString::from))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||||
.find(|(t, _)| t.0.filter(metadata))
|
.find(|(t, _)| t.0.filter(metadata))
|
||||||
.map(|(e, _)| e.clone())
|
.map(|(t, rc)| (t.clone(), rc.clone()))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let (prev, store) = target.into_preprocessed(prev, hello, metadata).await?;
|
let (prev, store) = target.into_preprocessed(rc, prev, hello, metadata).await?;
|
||||||
|
|
||||||
self.1 = Some(store);
|
self.1 = Some(store);
|
||||||
|
|
||||||
@@ -480,6 +493,14 @@ struct VHostListenerMetadata<A: Accept> {
|
|||||||
inner: TlsMetadata<A::Metadata>,
|
inner: TlsMetadata<A::Metadata>,
|
||||||
preprocessed: Preprocessed<A>,
|
preprocessed: Preprocessed<A>,
|
||||||
}
|
}
|
||||||
|
impl<A: Accept> fmt::Debug for VHostListenerMetadata<A> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("VHostListenerMetadata")
|
||||||
|
.field("inner", &self.inner)
|
||||||
|
.field("preprocessed", &self.preprocessed)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<M, A> Accept for VHostListener<M, A>
|
impl<M, A> Accept for VHostListener<M, A>
|
||||||
where
|
where
|
||||||
for<'a> M: HasModel<Model = Model<M>>
|
for<'a> M: HasModel<Model = Model<M>>
|
||||||
@@ -637,6 +658,7 @@ impl<A: Accept> VHostServer<A> {
|
|||||||
changed = true;
|
changed = true;
|
||||||
Arc::new(())
|
Arc::new(())
|
||||||
};
|
};
|
||||||
|
targets.retain(|_, rc| rc.strong_count() > 0);
|
||||||
targets.insert(target, Arc::downgrade(&rc));
|
targets.insert(target, Arc::downgrade(&rc));
|
||||||
writable.insert(hostname, targets);
|
writable.insert(hostname, targets);
|
||||||
res = Ok(rc);
|
res = Ok(rc);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use core::fmt;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
@@ -68,7 +69,7 @@ pub fn extract<
|
|||||||
metadata: &M,
|
metadata: &M,
|
||||||
) -> Option<T> {
|
) -> Option<T> {
|
||||||
let mut visitor = ExtractVisitor(None);
|
let mut visitor = ExtractVisitor(None);
|
||||||
visitor.visit(metadata);
|
metadata.visit(&mut visitor);
|
||||||
visitor.0
|
visitor.0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ impl<V: MetadataVisitor> Visit<V> for TcpMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait Accept {
|
pub trait Accept {
|
||||||
type Metadata;
|
type Metadata: fmt::Debug;
|
||||||
fn poll_accept(
|
fn poll_accept(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
@@ -144,7 +145,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, VisitFields)]
|
#[derive(Debug, Clone, VisitFields)]
|
||||||
pub struct MapListenerMetadata<K, M> {
|
pub struct MapListenerMetadata<K, M> {
|
||||||
pub inner: M,
|
pub inner: M,
|
||||||
pub key: K,
|
pub key: K,
|
||||||
@@ -162,7 +163,7 @@ where
|
|||||||
|
|
||||||
impl<K, A> Accept for BTreeMap<K, A>
|
impl<K, A> Accept for BTreeMap<K, A>
|
||||||
where
|
where
|
||||||
K: Clone,
|
K: Clone + fmt::Debug,
|
||||||
A: Accept,
|
A: Accept,
|
||||||
{
|
{
|
||||||
type Metadata = MapListenerMetadata<K, A::Metadata>;
|
type Metadata = MapListenerMetadata<K, A::Metadata>;
|
||||||
@@ -218,40 +219,38 @@ trait DynAcceptT: Send + Sync {
|
|||||||
fn poll_accept(
|
fn poll_accept(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
) -> Poll<
|
) -> Poll<Result<(DynMetadata, AcceptStream), Error>>;
|
||||||
Result<
|
|
||||||
(
|
|
||||||
Box<dyn for<'a> Visit<ExtensionVisitor<'a>> + Send + Sync>,
|
|
||||||
AcceptStream,
|
|
||||||
),
|
|
||||||
Error,
|
|
||||||
>,
|
|
||||||
>;
|
|
||||||
}
|
}
|
||||||
impl<A> DynAcceptT for A
|
impl<A> DynAcceptT for A
|
||||||
where
|
where
|
||||||
A: Accept + Send + Sync,
|
A: Accept + Send + Sync,
|
||||||
for<'a> <A as Accept>::Metadata: Visit<ExtensionVisitor<'a>> + Send + Sync + 'static,
|
<A as Accept>::Metadata: DynMetadataT + 'static,
|
||||||
{
|
{
|
||||||
fn poll_accept(
|
fn poll_accept(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
) -> Poll<
|
) -> Poll<Result<(DynMetadata, AcceptStream), Error>> {
|
||||||
Result<
|
|
||||||
(
|
|
||||||
Box<dyn for<'a> Visit<ExtensionVisitor<'a>> + Send + Sync>,
|
|
||||||
AcceptStream,
|
|
||||||
),
|
|
||||||
Error,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
let (metadata, stream) = ready!(Accept::poll_accept(self, cx)?);
|
let (metadata, stream) = ready!(Accept::poll_accept(self, cx)?);
|
||||||
Poll::Ready(Ok((Box::new(metadata), stream)))
|
Poll::Ready(Ok((DynMetadata(Box::new(metadata)), stream)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct DynAccept(Box<dyn DynAcceptT>);
|
pub struct DynAccept(Box<dyn DynAcceptT>);
|
||||||
|
trait DynMetadataT: for<'a> Visit<ExtensionVisitor<'a>> + fmt::Debug + Send + Sync {}
|
||||||
|
impl<T> DynMetadataT for T where for<'a> T: Visit<ExtensionVisitor<'a>> + fmt::Debug + Send + Sync {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DynMetadata(Box<dyn DynMetadataT>);
|
||||||
|
impl<'a> Visit<ExtensionVisitor<'a>> for DynMetadata {
|
||||||
|
fn visit(
|
||||||
|
&self,
|
||||||
|
visitor: &mut ExtensionVisitor<'a>,
|
||||||
|
) -> <ExtensionVisitor<'a> as Visitor>::Result {
|
||||||
|
self.0.visit(visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Accept for DynAccept {
|
impl Accept for DynAccept {
|
||||||
type Metadata = Box<dyn for<'a> Visit<ExtensionVisitor<'a>> + Send + Sync>;
|
type Metadata = DynMetadata;
|
||||||
fn poll_accept(
|
fn poll_accept(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
@@ -325,7 +324,7 @@ impl Acceptor<Vec<DynAccept>> {
|
|||||||
}
|
}
|
||||||
impl<K> Acceptor<BTreeMap<K, TcpListener>>
|
impl<K> Acceptor<BTreeMap<K, TcpListener>>
|
||||||
where
|
where
|
||||||
K: Ord + Clone + Send + Sync + 'static,
|
K: Ord + Clone + fmt::Debug + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
pub async fn bind_map(
|
pub async fn bind_map(
|
||||||
listen: impl IntoIterator<Item = (K, SocketAddr)>,
|
listen: impl IntoIterator<Item = (K, SocketAddr)>,
|
||||||
@@ -347,7 +346,7 @@ where
|
|||||||
}
|
}
|
||||||
impl<K> Acceptor<BTreeMap<K, DynAccept>>
|
impl<K> Acceptor<BTreeMap<K, DynAccept>>
|
||||||
where
|
where
|
||||||
K: Ord + Clone + Send + Sync + 'static,
|
K: Ord + Clone + fmt::Debug + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
pub async fn bind_map_dyn(
|
pub async fn bind_map_dyn(
|
||||||
listen: impl IntoIterator<Item = (K, SocketAddr)>,
|
listen: impl IntoIterator<Item = (K, SocketAddr)>,
|
||||||
|
|||||||
@@ -356,7 +356,10 @@ pub async fn execute<C: Context>(
|
|||||||
let mut install = Command::new("chroot");
|
let mut install = Command::new("chroot");
|
||||||
install.arg(overlay.path()).arg("grub-install");
|
install.arg(overlay.path()).arg("grub-install");
|
||||||
if tokio::fs::metadata("/sys/firmware/efi").await.is_err() {
|
if tokio::fs::metadata("/sys/firmware/efi").await.is_err() {
|
||||||
install.arg("--target=i386-pc");
|
match ARCH {
|
||||||
|
"x86_64" => install.arg("--target=i386-pc"),
|
||||||
|
_ => &mut install,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
match ARCH {
|
match ARCH {
|
||||||
"x86_64" => install.arg("--target=x86_64-efi"),
|
"x86_64" => install.arg("--target=x86_64-efi"),
|
||||||
@@ -372,7 +375,7 @@ pub async fn execute<C: Context>(
|
|||||||
|
|
||||||
Command::new("chroot")
|
Command::new("chroot")
|
||||||
.arg(overlay.path())
|
.arg(overlay.path())
|
||||||
.arg("update-grub2")
|
.arg("update-grub")
|
||||||
.invoke(crate::ErrorKind::Grub)
|
.invoke(crate::ErrorKind::Grub)
|
||||||
.await?;
|
.await?;
|
||||||
dev.unmount(false).await?;
|
dev.unmount(false).await?;
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ impl FullProgressTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn progress_bar_task(&self, name: &str) -> NonDetachingJoinHandle<()> {
|
pub fn progress_bar_task(&self, name: &str) -> NonDetachingJoinHandle<()> {
|
||||||
let mut stream = self.stream(None);
|
let mut stream = self.stream(Some(Duration::from_millis(200)));
|
||||||
let mut bar = PhasedProgressBar::new(name);
|
let mut bar = PhasedProgressBar::new(name);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(progress) = stream.next().await {
|
while let Some(progress) = stream.next().await {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
@@ -84,6 +85,26 @@ impl RegistryAsset<MerkleArchiveCommitment> {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
pub async fn download_to(
|
||||||
|
&self,
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
client: Client,
|
||||||
|
progress: PhaseProgressTrackerHandle,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
S9pk<Section<Arc<BufferedHttpSource>>>,
|
||||||
|
Arc<BufferedHttpSource>,
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
> {
|
||||||
|
let source = Arc::new(
|
||||||
|
BufferedHttpSource::with_path(path, client, self.url.clone(), progress).await?,
|
||||||
|
);
|
||||||
|
Ok((
|
||||||
|
S9pk::deserialize(&source, Some(&self.commitment)).await?,
|
||||||
|
source,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BufferedHttpSource {
|
pub struct BufferedHttpSource {
|
||||||
@@ -91,6 +112,19 @@ pub struct BufferedHttpSource {
|
|||||||
file: UploadingFile,
|
file: UploadingFile,
|
||||||
}
|
}
|
||||||
impl BufferedHttpSource {
|
impl BufferedHttpSource {
|
||||||
|
pub async fn with_path(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
client: Client,
|
||||||
|
url: Url,
|
||||||
|
progress: PhaseProgressTrackerHandle,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let (mut handle, file) = UploadingFile::with_path(path, progress).await?;
|
||||||
|
let response = client.get(url).send().await?;
|
||||||
|
Ok(Self {
|
||||||
|
_download: tokio::spawn(async move { handle.download(response).await }).into(),
|
||||||
|
file,
|
||||||
|
})
|
||||||
|
}
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
client: Client,
|
client: Client,
|
||||||
url: Url,
|
url: Url,
|
||||||
@@ -103,6 +137,9 @@ impl BufferedHttpSource {
|
|||||||
file,
|
file,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub async fn wait_for_buffered(&self) -> Result<(), Error> {
|
||||||
|
self.file.wait_for_complete().await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl ArchiveSource for BufferedHttpSource {
|
impl ArchiveSource for BufferedHttpSource {
|
||||||
type FetchReader = <UploadingFile as ArchiveSource>::FetchReader;
|
type FetchReader = <UploadingFile as ArchiveSource>::FetchReader;
|
||||||
|
|||||||
@@ -1,19 +1,27 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use exver::{ExtendedVersion, VersionRange};
|
use exver::{ExtendedVersion, VersionRange};
|
||||||
use imbl_value::InternedString;
|
use helpers::to_tmp_path;
|
||||||
|
use imbl_value::{InternedString, json};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::PackageId;
|
use models::PackageId;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
use crate::context::CliContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::progress::{FullProgressTracker, ProgressUnits};
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::device_info::DeviceInfo;
|
use crate::registry::device_info::DeviceInfo;
|
||||||
use crate::registry::package::index::{PackageIndex, PackageVersionInfo};
|
use crate::registry::package::index::{PackageIndex, PackageVersionInfo};
|
||||||
|
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
||||||
|
use crate::s9pk::v2::SIG_CONTEXT;
|
||||||
use crate::util::VersionString;
|
use crate::util::VersionString;
|
||||||
|
use crate::util::io::TrackingIO;
|
||||||
use crate::util::serde::{WithIoFormat, display_serializable};
|
use crate::util::serde::{WithIoFormat, display_serializable};
|
||||||
|
use crate::util::tui::choose;
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS, ValueEnum,
|
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS, ValueEnum,
|
||||||
@@ -352,8 +360,7 @@ pub fn display_package_info(
|
|||||||
info: Value,
|
info: Value,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
display_serializable(format, info);
|
return display_serializable(format, info);
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(_) = params.rest.id {
|
if let Some(_) = params.rest.id {
|
||||||
@@ -387,3 +394,90 @@ pub fn display_package_info(
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, TS, Parser)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CliDownloadParams {
|
||||||
|
pub id: PackageId,
|
||||||
|
#[arg(long, short = 'v')]
|
||||||
|
#[ts(type = "string | null")]
|
||||||
|
pub target_version: Option<VersionRange>,
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub dest: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cli_download(
|
||||||
|
ctx: CliContext,
|
||||||
|
CliDownloadParams {
|
||||||
|
ref id,
|
||||||
|
target_version,
|
||||||
|
dest,
|
||||||
|
}: CliDownloadParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let progress_tracker = FullProgressTracker::new();
|
||||||
|
let mut fetching_progress = progress_tracker.add_phase("Fetching".into(), Some(1));
|
||||||
|
let download_progress = progress_tracker.add_phase("Downloading".into(), Some(100));
|
||||||
|
let mut verify_progress = progress_tracker.add_phase("Verifying".into(), Some(10));
|
||||||
|
|
||||||
|
let progress = progress_tracker.progress_bar_task("Downloading S9PK...");
|
||||||
|
|
||||||
|
fetching_progress.start();
|
||||||
|
let mut res: GetPackageResponse = from_value(
|
||||||
|
ctx.call_remote::<RegistryContext>(
|
||||||
|
"package.get",
|
||||||
|
json!({
|
||||||
|
"id": &id,
|
||||||
|
"targetVersion": &target_version,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
)?;
|
||||||
|
let PackageVersionInfo { s9pk, .. } = match res.best.len() {
|
||||||
|
0 => {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!(
|
||||||
|
"Could not find a version of {id} that satisfies {}",
|
||||||
|
target_version.unwrap_or(VersionRange::Any)
|
||||||
|
),
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
1 => res.best.pop_first().unwrap().1,
|
||||||
|
_ => {
|
||||||
|
let choices = res.best.keys().cloned().collect::<Vec<_>>();
|
||||||
|
let version = choose(
|
||||||
|
&format!("Multiple flavors of {id} available. Choose a version to download:"),
|
||||||
|
&choices,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
res.best.remove(version).unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
s9pk.validate(SIG_CONTEXT, s9pk.all_signers())?;
|
||||||
|
fetching_progress.complete();
|
||||||
|
|
||||||
|
let dest = dest.unwrap_or_else(|| Path::new(".").join(id).with_extension("s9pk"));
|
||||||
|
let dest_tmp = to_tmp_path(&dest).with_kind(ErrorKind::Filesystem)?;
|
||||||
|
let (mut parsed, source) = s9pk
|
||||||
|
.download_to(&dest_tmp, ctx.client.clone(), download_progress)
|
||||||
|
.await?;
|
||||||
|
if let Some(size) = source.size().await {
|
||||||
|
verify_progress.set_total(size);
|
||||||
|
}
|
||||||
|
verify_progress.set_units(Some(ProgressUnits::Bytes));
|
||||||
|
let mut progress_sink = verify_progress.writer(tokio::io::sink());
|
||||||
|
parsed
|
||||||
|
.serialize(&mut TrackingIO::new(0, &mut progress_sink), true)
|
||||||
|
.await?;
|
||||||
|
progress_sink.into_inner().1.complete();
|
||||||
|
|
||||||
|
source.wait_for_buffered().await?;
|
||||||
|
tokio::fs::rename(dest_tmp, dest).await?;
|
||||||
|
|
||||||
|
progress_tracker.complete();
|
||||||
|
progress.await.unwrap();
|
||||||
|
|
||||||
|
println!("Download Complete");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async, from_fn_async_local};
|
||||||
|
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -54,6 +54,12 @@ pub fn package_api<C: Context>() -> ParentHandler<C> {
|
|||||||
.with_about("List installation candidate package(s)")
|
.with_about("List installation candidate package(s)")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
"download",
|
||||||
|
from_fn_async_local(get::cli_download)
|
||||||
|
.no_display()
|
||||||
|
.with_about("Download an s9pk"),
|
||||||
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"category",
|
"category",
|
||||||
category::category_api::<C>()
|
category::category_api::<C>()
|
||||||
|
|||||||
@@ -106,7 +106,9 @@ pub struct ExecParams {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pty_size: Option<TermSize>,
|
pty_size: Option<TermSize>,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
env: Option<PathBuf>,
|
env: Vec<String>,
|
||||||
|
#[arg(long)]
|
||||||
|
env_file: Option<PathBuf>,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
workdir: Option<PathBuf>,
|
workdir: Option<PathBuf>,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
@@ -119,6 +121,7 @@ impl ExecParams {
|
|||||||
fn exec(&self) -> Result<(), Error> {
|
fn exec(&self) -> Result<(), Error> {
|
||||||
let ExecParams {
|
let ExecParams {
|
||||||
env,
|
env,
|
||||||
|
env_file,
|
||||||
workdir,
|
workdir,
|
||||||
user,
|
user,
|
||||||
chroot,
|
chroot,
|
||||||
@@ -131,14 +134,15 @@ impl ExecParams {
|
|||||||
ErrorKind::InvalidRequest,
|
ErrorKind::InvalidRequest,
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let env_string = if let Some(env) = &env {
|
let env_string = if let Some(env_file) = &env_file {
|
||||||
std::fs::read_to_string(env)
|
std::fs::read_to_string(env_file)
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("read {env:?}")))?
|
.with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("read {env:?}")))?
|
||||||
} else {
|
} else {
|
||||||
Default::default()
|
Default::default()
|
||||||
};
|
};
|
||||||
let env = env_string
|
let env = env_string
|
||||||
.lines()
|
.lines()
|
||||||
|
.chain(env.iter().map(|l| l.as_str()))
|
||||||
.map(|l| l.trim())
|
.map(|l| l.trim())
|
||||||
.filter_map(|l| l.split_once("="))
|
.filter_map(|l| l.split_once("="))
|
||||||
.collect::<BTreeMap<_, _>>();
|
.collect::<BTreeMap<_, _>>();
|
||||||
@@ -150,31 +154,39 @@ impl ExecParams {
|
|||||||
cmd.env(k, v);
|
cmd.env(k, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(uid) = user.as_deref().and_then(|u| u.parse::<u32>().ok()) {
|
if let Some((uid, gid)) =
|
||||||
cmd.uid(uid);
|
if let Some(uid) = user.as_deref().and_then(|u| u.parse::<u32>().ok()) {
|
||||||
} else if let Some(user) = user {
|
Some((uid, uid))
|
||||||
let passwd = std::fs::read_to_string("/etc/passwd")
|
} else if let Some(user) = user {
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, "read /etc/passwd"));
|
let passwd = std::fs::read_to_string("/etc/passwd")
|
||||||
if passwd.is_err() && user == "root" {
|
.with_ctx(|_| (ErrorKind::Filesystem, "read /etc/passwd"));
|
||||||
cmd.uid(0);
|
Some(if passwd.is_err() && user == "root" {
|
||||||
cmd.gid(0);
|
(0, 0)
|
||||||
|
} else {
|
||||||
|
let (uid, gid) = passwd?
|
||||||
|
.lines()
|
||||||
|
.find_map(|l| {
|
||||||
|
let mut split = l.trim().split(":");
|
||||||
|
if user != split.next()? {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
split.next(); // throw away x
|
||||||
|
Some((split.next()?.parse().ok()?, split.next()?.parse().ok()?))
|
||||||
|
// uid gid
|
||||||
|
})
|
||||||
|
.or_not_found(lazy_format!("{user} in /etc/passwd"))?;
|
||||||
|
(uid, gid)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
let (uid, gid) = passwd?
|
None
|
||||||
.lines()
|
|
||||||
.find_map(|l| {
|
|
||||||
let mut split = l.trim().split(":");
|
|
||||||
if user != split.next()? {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
split.next(); // throw away x
|
|
||||||
Some((split.next()?.parse().ok()?, split.next()?.parse().ok()?))
|
|
||||||
// uid gid
|
|
||||||
})
|
|
||||||
.or_not_found(lazy_format!("{user} in /etc/passwd"))?;
|
|
||||||
cmd.uid(uid);
|
|
||||||
cmd.gid(gid);
|
|
||||||
}
|
}
|
||||||
};
|
{
|
||||||
|
std::os::unix::fs::chown("/proc/self/fd/0", Some(uid), Some(gid)).log_err();
|
||||||
|
std::os::unix::fs::chown("/proc/self/fd/1", Some(uid), Some(gid)).log_err();
|
||||||
|
std::os::unix::fs::chown("/proc/self/fd/2", Some(uid), Some(gid)).log_err();
|
||||||
|
cmd.uid(uid);
|
||||||
|
cmd.gid(gid);
|
||||||
|
}
|
||||||
if let Some(workdir) = workdir {
|
if let Some(workdir) = workdir {
|
||||||
cmd.current_dir(workdir);
|
cmd.current_dir(workdir);
|
||||||
} else {
|
} else {
|
||||||
@@ -191,6 +203,7 @@ pub fn launch(
|
|||||||
force_stderr_tty,
|
force_stderr_tty,
|
||||||
pty_size,
|
pty_size,
|
||||||
env,
|
env,
|
||||||
|
env_file,
|
||||||
workdir,
|
workdir,
|
||||||
user,
|
user,
|
||||||
chroot,
|
chroot,
|
||||||
@@ -286,8 +299,11 @@ pub fn launch(
|
|||||||
let (pty, pts) = pty_process::open().with_kind(ErrorKind::Filesystem)?;
|
let (pty, pts) = pty_process::open().with_kind(ErrorKind::Filesystem)?;
|
||||||
let mut cmd = pty_process::Command::new("/usr/bin/start-container");
|
let mut cmd = pty_process::Command::new("/usr/bin/start-container");
|
||||||
cmd = cmd.arg("subcontainer").arg("launch-init");
|
cmd = cmd.arg("subcontainer").arg("launch-init");
|
||||||
if let Some(env) = env {
|
for env in env {
|
||||||
cmd = cmd.arg("--env").arg(env);
|
cmd = cmd.arg("-e").arg(env)
|
||||||
|
}
|
||||||
|
if let Some(env_file) = env_file {
|
||||||
|
cmd = cmd.arg("--env-file").arg(env_file);
|
||||||
}
|
}
|
||||||
if let Some(workdir) = workdir {
|
if let Some(workdir) = workdir {
|
||||||
cmd = cmd.arg("--workdir").arg(workdir);
|
cmd = cmd.arg("--workdir").arg(workdir);
|
||||||
@@ -341,8 +357,11 @@ pub fn launch(
|
|||||||
} else {
|
} else {
|
||||||
let mut cmd = StdCommand::new("/usr/bin/start-container");
|
let mut cmd = StdCommand::new("/usr/bin/start-container");
|
||||||
cmd.arg("subcontainer").arg("launch-init");
|
cmd.arg("subcontainer").arg("launch-init");
|
||||||
if let Some(env) = env {
|
for env in env {
|
||||||
cmd.arg("--env").arg(env);
|
cmd.arg("-e").arg(env);
|
||||||
|
}
|
||||||
|
if let Some(env_file) = env_file {
|
||||||
|
cmd.arg("--env-file").arg(env_file);
|
||||||
}
|
}
|
||||||
if let Some(workdir) = workdir {
|
if let Some(workdir) = workdir {
|
||||||
cmd.arg("--workdir").arg(workdir);
|
cmd.arg("--workdir").arg(workdir);
|
||||||
@@ -433,6 +452,7 @@ pub fn exec(
|
|||||||
force_stderr_tty,
|
force_stderr_tty,
|
||||||
pty_size,
|
pty_size,
|
||||||
env,
|
env,
|
||||||
|
env_file,
|
||||||
workdir,
|
workdir,
|
||||||
user,
|
user,
|
||||||
chroot,
|
chroot,
|
||||||
@@ -536,8 +556,11 @@ pub fn exec(
|
|||||||
let (pty, pts) = pty_process::open().with_kind(ErrorKind::Filesystem)?;
|
let (pty, pts) = pty_process::open().with_kind(ErrorKind::Filesystem)?;
|
||||||
let mut cmd = pty_process::Command::new("/usr/bin/start-container");
|
let mut cmd = pty_process::Command::new("/usr/bin/start-container");
|
||||||
cmd = cmd.arg("subcontainer").arg("exec-command");
|
cmd = cmd.arg("subcontainer").arg("exec-command");
|
||||||
if let Some(env) = env {
|
for env in env {
|
||||||
cmd = cmd.arg("--env").arg(env);
|
cmd = cmd.arg("-e").arg(env);
|
||||||
|
}
|
||||||
|
if let Some(env_file) = env_file {
|
||||||
|
cmd = cmd.arg("--env-file").arg(env_file);
|
||||||
}
|
}
|
||||||
if let Some(workdir) = workdir {
|
if let Some(workdir) = workdir {
|
||||||
cmd = cmd.arg("--workdir").arg(workdir);
|
cmd = cmd.arg("--workdir").arg(workdir);
|
||||||
@@ -591,8 +614,11 @@ pub fn exec(
|
|||||||
} else {
|
} else {
|
||||||
let mut cmd = StdCommand::new("/usr/bin/start-container");
|
let mut cmd = StdCommand::new("/usr/bin/start-container");
|
||||||
cmd.arg("subcontainer").arg("exec-command");
|
cmd.arg("subcontainer").arg("exec-command");
|
||||||
if let Some(env) = env {
|
for env in env {
|
||||||
cmd.arg("--env").arg(env);
|
cmd.arg("-e").arg(env);
|
||||||
|
}
|
||||||
|
if let Some(env_file) = env_file {
|
||||||
|
cmd.arg("--env-file").arg(env_file);
|
||||||
}
|
}
|
||||||
if let Some(workdir) = workdir {
|
if let Some(workdir) = workdir {
|
||||||
cmd.arg("--workdir").arg(workdir);
|
cmd.arg("--workdir").arg(workdir);
|
||||||
|
|||||||
@@ -725,6 +725,8 @@ pub struct AttachParams {
|
|||||||
name: Option<InternedString>,
|
name: Option<InternedString>,
|
||||||
#[ts(type = "string | null")]
|
#[ts(type = "string | null")]
|
||||||
image_id: Option<ImageId>,
|
image_id: Option<ImageId>,
|
||||||
|
#[ts(type = "string | null")]
|
||||||
|
user: Option<InternedString>,
|
||||||
}
|
}
|
||||||
pub async fn attach(
|
pub async fn attach(
|
||||||
ctx: RpcContext,
|
ctx: RpcContext,
|
||||||
@@ -738,6 +740,7 @@ pub async fn attach(
|
|||||||
subcontainer,
|
subcontainer,
|
||||||
image_id,
|
image_id,
|
||||||
name,
|
name,
|
||||||
|
user,
|
||||||
}: AttachParams,
|
}: AttachParams,
|
||||||
) -> Result<Guid, Error> {
|
) -> Result<Guid, Error> {
|
||||||
let (container_id, subcontainer_id, image_id, workdir, root_command) = {
|
let (container_id, subcontainer_id, image_id, workdir, root_command) = {
|
||||||
@@ -814,9 +817,26 @@ pub async fn attach(
|
|||||||
.join("etc")
|
.join("etc")
|
||||||
.join("passwd");
|
.join("passwd");
|
||||||
|
|
||||||
let root_command = get_passwd_root_command(passwd).await;
|
let image_meta = serde_json::from_str::<Value>(
|
||||||
|
&tokio::fs::read_to_string(
|
||||||
|
root_dir
|
||||||
|
.join("media/startos/images/")
|
||||||
|
.join(&image_id)
|
||||||
|
.with_extension("json"),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
)
|
||||||
|
.with_kind(ErrorKind::Deserialization)?;
|
||||||
|
|
||||||
let workdir = attach_workdir(&image_id, &root_dir).await?;
|
let root_command = get_passwd_command(
|
||||||
|
passwd,
|
||||||
|
user.as_deref()
|
||||||
|
.or_else(|| image_meta["user"].as_str())
|
||||||
|
.unwrap_or("root"),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let workdir = image_meta["workdir"].as_str().map(|s| s.to_owned());
|
||||||
|
|
||||||
if subcontainer_ids.len() > 1 {
|
if subcontainer_ids.len() > 1 {
|
||||||
let subcontainer_ids = subcontainer_ids
|
let subcontainer_ids = subcontainer_ids
|
||||||
@@ -849,6 +869,7 @@ pub async fn attach(
|
|||||||
pty_size: Option<TermSize>,
|
pty_size: Option<TermSize>,
|
||||||
image_id: ImageId,
|
image_id: ImageId,
|
||||||
workdir: Option<String>,
|
workdir: Option<String>,
|
||||||
|
user: Option<InternedString>,
|
||||||
root_command: &RootCommand,
|
root_command: &RootCommand,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
use axum::extract::ws::Message;
|
use axum::extract::ws::Message;
|
||||||
@@ -864,13 +885,17 @@ pub async fn attach(
|
|||||||
.arg("start-container")
|
.arg("start-container")
|
||||||
.arg("subcontainer")
|
.arg("subcontainer")
|
||||||
.arg("exec")
|
.arg("exec")
|
||||||
.arg("--env")
|
.arg("--env-file")
|
||||||
.arg(
|
.arg(
|
||||||
Path::new("/media/startos/images")
|
Path::new("/media/startos/images")
|
||||||
.join(image_id)
|
.join(image_id)
|
||||||
.with_extension("env"),
|
.with_extension("env"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if let Some(user) = user {
|
||||||
|
cmd.arg("--user").arg(&*user);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(workdir) = workdir {
|
if let Some(workdir) = workdir {
|
||||||
cmd.arg("--workdir").arg(workdir);
|
cmd.arg("--workdir").arg(workdir);
|
||||||
}
|
}
|
||||||
@@ -1032,6 +1057,7 @@ pub async fn attach(
|
|||||||
pty_size,
|
pty_size,
|
||||||
image_id,
|
image_id,
|
||||||
workdir,
|
workdir,
|
||||||
|
user,
|
||||||
&root_command,
|
&root_command,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -1051,19 +1077,46 @@ pub async fn attach(
|
|||||||
Ok(guid)
|
Ok(guid)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn attach_workdir(image_id: &ImageId, root_dir: &Path) -> Result<Option<String>, Error> {
|
#[derive(Deserialize, Serialize, TS)]
|
||||||
let path_str = root_dir.join("media/startos/images/");
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ListSubcontainersParams {
|
||||||
let mut subcontainer_json =
|
pub id: PackageId,
|
||||||
tokio::fs::File::open(path_str.join(image_id).with_extension("json")).await?;
|
|
||||||
let mut contents = vec![];
|
|
||||||
subcontainer_json.read_to_end(&mut contents).await?;
|
|
||||||
let subcontainer_json: serde_json::Value =
|
|
||||||
serde_json::from_slice(&contents).with_kind(ErrorKind::Filesystem)?;
|
|
||||||
Ok(subcontainer_json["workdir"].as_str().map(|x| x.to_string()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_passwd_root_command(etc_passwd_path: PathBuf) -> RootCommand {
|
#[derive(Clone, Debug, Serialize, Deserialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct SubcontainerInfo {
|
||||||
|
pub name: InternedString,
|
||||||
|
pub image_id: ImageId,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_subcontainers(
|
||||||
|
ctx: RpcContext,
|
||||||
|
ListSubcontainersParams { id }: ListSubcontainersParams,
|
||||||
|
) -> Result<BTreeMap<Guid, SubcontainerInfo>, Error> {
|
||||||
|
let service = ctx.services.get(&id).await;
|
||||||
|
let service_ref = service.as_ref().or_not_found(&id)?;
|
||||||
|
let container = &service_ref.seed.persistent_container;
|
||||||
|
|
||||||
|
let subcontainers = container.subcontainers.lock().await;
|
||||||
|
|
||||||
|
let result: BTreeMap<Guid, SubcontainerInfo> = subcontainers
|
||||||
|
.iter()
|
||||||
|
.map(|(guid, subcontainer)| {
|
||||||
|
(
|
||||||
|
guid.clone(),
|
||||||
|
SubcontainerInfo {
|
||||||
|
name: subcontainer.name.clone(),
|
||||||
|
image_id: subcontainer.image_id.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_passwd_command(etc_passwd_path: PathBuf, user: &str) -> RootCommand {
|
||||||
async {
|
async {
|
||||||
let mut file = tokio::fs::File::open(etc_passwd_path).await?;
|
let mut file = tokio::fs::File::open(etc_passwd_path).await?;
|
||||||
|
|
||||||
@@ -1074,8 +1127,8 @@ async fn get_passwd_root_command(etc_passwd_path: PathBuf) -> RootCommand {
|
|||||||
|
|
||||||
for line in contents.split('\n') {
|
for line in contents.split('\n') {
|
||||||
let line_information = line.split(':').collect::<Vec<_>>();
|
let line_information = line.split(':').collect::<Vec<_>>();
|
||||||
if let (Some(&"root"), Some(shell)) =
|
if let (Some(&u), Some(shell)) = (line_information.first(), line_information.last())
|
||||||
(line_information.first(), line_information.last())
|
&& u == user
|
||||||
{
|
{
|
||||||
return Ok(shell.to_string());
|
return Ok(shell.to_string());
|
||||||
}
|
}
|
||||||
@@ -1106,6 +1159,8 @@ pub struct CliAttachParams {
|
|||||||
#[arg(long, short)]
|
#[arg(long, short)]
|
||||||
name: Option<InternedString>,
|
name: Option<InternedString>,
|
||||||
#[arg(long, short)]
|
#[arg(long, short)]
|
||||||
|
user: Option<InternedString>,
|
||||||
|
#[arg(long, short)]
|
||||||
image_id: Option<ImageId>,
|
image_id: Option<ImageId>,
|
||||||
}
|
}
|
||||||
#[instrument[skip_all]]
|
#[instrument[skip_all]]
|
||||||
@@ -1147,6 +1202,7 @@ pub async fn cli_attach(
|
|||||||
"subcontainer": params.subcontainer,
|
"subcontainer": params.subcontainer,
|
||||||
"imageId": params.image_id,
|
"imageId": params.image_id,
|
||||||
"name": params.name,
|
"name": params.name,
|
||||||
|
"user": params.user,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ use crate::util::rpc_client::UnixRpcClient;
|
|||||||
use crate::volume::data_dir;
|
use crate::volume::data_dir;
|
||||||
use crate::{ARCH, DATA_DIR, PACKAGE_DATA};
|
use crate::{ARCH, DATA_DIR, PACKAGE_DATA};
|
||||||
|
|
||||||
const RPC_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
const RPC_CONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ServiceState {
|
pub struct ServiceState {
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ impl ServiceMap {
|
|||||||
match Service::load(ctx, id, disposition).await {
|
match Service::load(ctx, id, disposition).await {
|
||||||
Ok(s) => *service = s.into(),
|
Ok(s) => *service = s.into(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
tracing::error!("Error loading service: {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
let e = ErrorData::from(e);
|
let e = ErrorData::from(e);
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
|
|||||||
@@ -499,7 +499,7 @@ async fn fresh_setup(
|
|||||||
..
|
..
|
||||||
}: SetupExecuteProgress,
|
}: SetupExecuteProgress,
|
||||||
) -> Result<(SetupResult, RpcContext), Error> {
|
) -> Result<(SetupResult, RpcContext), Error> {
|
||||||
let account = AccountInfo::new(start_os_password, root_ca_start_time().await?)?;
|
let account = AccountInfo::new(start_os_password, root_ca_start_time().await)?;
|
||||||
let db = ctx.db().await?;
|
let db = ctx.db().await?;
|
||||||
let kiosk = Some(kiosk.unwrap_or(true)).filter(|_| &*PLATFORM != "raspberrypi");
|
let kiosk = Some(kiosk.unwrap_or(true)).filter(|_| &*PLATFORM != "raspberrypi");
|
||||||
sync_kiosk(kiosk).await?;
|
sync_kiosk(kiosk).await?;
|
||||||
|
|||||||
@@ -353,6 +353,7 @@ pub async fn show_config(
|
|||||||
Ok(client
|
Ok(client
|
||||||
.client_config(
|
.client_config(
|
||||||
ip,
|
ip,
|
||||||
|
subnet,
|
||||||
wg.as_key().de()?.verifying_key(),
|
wg.as_key().de()?.verifying_key(),
|
||||||
(wan_addr, wg.as_port().de()?).into(),
|
(wan_addr, wg.as_port().de()?).into(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use imbl::HashMap;
|
|||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use patch_db::HasModel;
|
use patch_db::HasModel;
|
||||||
use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
@@ -113,27 +113,12 @@ impl AuthContext for TunnelContext {
|
|||||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS, Parser)]
|
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS, Parser)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
#[ts(export)]
|
|
||||||
pub struct SignerInfo {
|
pub struct SignerInfo {
|
||||||
pub name: InternedString,
|
pub name: InternedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auth_api<C: Context>() -> ParentHandler<C> {
|
pub fn auth_api<C: Context>() -> ParentHandler<C> {
|
||||||
ParentHandler::new()
|
crate::auth::auth::<C, TunnelContext>()
|
||||||
.subcommand(
|
|
||||||
"login",
|
|
||||||
from_fn_async(crate::auth::login_impl::<TunnelContext>)
|
|
||||||
.with_metadata("login", Value::Bool(true))
|
|
||||||
.no_cli(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"logout",
|
|
||||||
from_fn_async(crate::auth::logout::<TunnelContext>)
|
|
||||||
.with_metadata("get_session", Value::Bool(true))
|
|
||||||
.no_display()
|
|
||||||
.with_about("Log out of current auth session")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand("set-password", from_fn_async(set_password_rpc).no_cli())
|
.subcommand("set-password", from_fn_async(set_password_rpc).no_cli())
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"set-password",
|
"set-password",
|
||||||
@@ -173,19 +158,15 @@ pub fn auth_api<C: Context>() -> ParentHandler<C> {
|
|||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|HandlerArgs { params, .. }, res| {
|
.with_custom_display_fn(|HandlerArgs { params, .. }, res| {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
return display_serializable(format, res);
|
return display_serializable(format, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
table.add_row(row![bc => "NAME", "KEY"]);
|
table.add_row(row![bc => "NAME", "KEY"]);
|
||||||
for (key, info) in res {
|
for (key, info) in res {
|
||||||
table.add_row(row![info.name, key]);
|
table.add_row(row![info.name, key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
table.print_tty(false)?;
|
table.print_tty(false)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.with_about("List authorized keys")
|
.with_about("List authorized keys")
|
||||||
@@ -194,7 +175,7 @@ pub fn auth_api<C: Context>() -> ParentHandler<C> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Parser)]
|
#[derive(Debug, Deserialize, Serialize, Parser, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AddKeyParams {
|
pub struct AddKeyParams {
|
||||||
pub name: InternedString,
|
pub name: InternedString,
|
||||||
@@ -216,7 +197,7 @@ pub async fn add_key(
|
|||||||
.result
|
.result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Parser)]
|
#[derive(Debug, Deserialize, Serialize, Parser, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RemoveKeyParams {
|
pub struct RemoveKeyParams {
|
||||||
pub key: AnyVerifyingKey,
|
pub key: AnyVerifyingKey,
|
||||||
@@ -240,7 +221,7 @@ pub async fn list_keys(ctx: TunnelContext) -> Result<HashMap<AnyVerifyingKey, Si
|
|||||||
ctx.db.peek().await.into_auth_pubkeys().de()
|
ctx.db.peek().await.into_auth_pubkeys().de()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
||||||
pub struct SetPasswordParams {
|
pub struct SetPasswordParams {
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
@@ -293,14 +274,7 @@ pub async fn set_password_cli(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn reset_password(
|
pub async fn reset_password(ctx: CliContext) -> Result<(), Error> {
|
||||||
HandlerArgs {
|
|
||||||
context,
|
|
||||||
parent_method,
|
|
||||||
method,
|
|
||||||
..
|
|
||||||
}: HandlerArgs<CliContext>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
println!("Generating a random password...");
|
println!("Generating a random password...");
|
||||||
let params = SetPasswordParams {
|
let params = SetPasswordParams {
|
||||||
password: base32::encode(
|
password: base32::encode(
|
||||||
@@ -309,11 +283,7 @@ pub async fn reset_password(
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
context
|
ctx.call_remote::<TunnelContext>("auth.set-password", to_value(¶ms)?)
|
||||||
.call_remote::<TunnelContext>(
|
|
||||||
&parent_method.iter().chain(method.iter()).join("."),
|
|
||||||
to_value(¶ms)?,
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
println!("Your new password is:");
|
println!("Your new password is:");
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ PrivateKey = {privkey}
|
|||||||
[Peer]
|
[Peer]
|
||||||
PublicKey = {server_pubkey}
|
PublicKey = {server_pubkey}
|
||||||
PresharedKey = {psk}
|
PresharedKey = {psk}
|
||||||
AllowedIPs = 0.0.0.0/0,::/0
|
AllowedIPs = {subnet}
|
||||||
Endpoint = {server_addr}
|
Endpoint = {server_addr}
|
||||||
PersistentKeepalive = 25
|
PersistentKeepalive = 25
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use hickory_client::proto::rr::rdata::cert;
|
|
||||||
use imbl_value::{InternedString, json};
|
use imbl_value::{InternedString, json};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
@@ -12,7 +11,6 @@ use rpc_toolkit::{
|
|||||||
Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async, from_fn_async_local,
|
Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async, from_fn_async_local,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
|
||||||
use tokio_rustls::rustls::ServerConfig;
|
use tokio_rustls::rustls::ServerConfig;
|
||||||
use tokio_rustls::rustls::crypto::CryptoProvider;
|
use tokio_rustls::rustls::crypto::CryptoProvider;
|
||||||
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
||||||
@@ -20,7 +18,8 @@ use tokio_rustls::rustls::server::ClientHello;
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::net::ssl::SANInfo;
|
use crate::hostname::Hostname;
|
||||||
|
use crate::net::ssl::{SANInfo, root_ca_start_time};
|
||||||
use crate::net::tls::TlsHandler;
|
use crate::net::tls::TlsHandler;
|
||||||
use crate::net::web_server::Accept;
|
use crate::net::web_server::Accept;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -134,7 +133,7 @@ pub fn web_api<C: Context>() -> ParentHandler<C> {
|
|||||||
.subcommand(
|
.subcommand(
|
||||||
"generate-certificate",
|
"generate-certificate",
|
||||||
from_fn_async(generate_certificate)
|
from_fn_async(generate_certificate)
|
||||||
.with_about("Generate a self signed certificaet to use for the webserver")
|
.with_about("Generate a certificate to use for the webserver")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
@@ -286,11 +285,21 @@ pub struct GenerateCertParams {
|
|||||||
pub async fn generate_certificate(
|
pub async fn generate_certificate(
|
||||||
ctx: TunnelContext,
|
ctx: TunnelContext,
|
||||||
GenerateCertParams { subject }: GenerateCertParams,
|
GenerateCertParams { subject }: GenerateCertParams,
|
||||||
) -> Result<Pem<X509>, Error> {
|
) -> Result<Pem<Vec<X509>>, Error> {
|
||||||
let saninfo = SANInfo::new(&subject.into_iter().collect());
|
let saninfo = SANInfo::new(&subject.into_iter().collect());
|
||||||
|
|
||||||
|
let root_key = crate::net::ssl::generate_key()?;
|
||||||
|
let root_cert = crate::net::ssl::make_root_cert(
|
||||||
|
&root_key,
|
||||||
|
&Hostname("start-tunnel".into()),
|
||||||
|
root_ca_start_time().await,
|
||||||
|
)?;
|
||||||
|
let int_key = crate::net::ssl::generate_key()?;
|
||||||
|
let int_cert = crate::net::ssl::make_int_cert((&root_key, &root_cert), &int_key)?;
|
||||||
|
|
||||||
let key = crate::net::ssl::generate_key()?;
|
let key = crate::net::ssl::generate_key()?;
|
||||||
let cert = crate::net::ssl::make_self_signed((&key, &saninfo))?;
|
let cert = crate::net::ssl::make_leaf_cert((&int_key, &int_cert), (&key, &saninfo))?;
|
||||||
|
let chain = Pem(vec![cert, int_cert, root_cert]);
|
||||||
|
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
@@ -298,13 +307,13 @@ pub async fn generate_certificate(
|
|||||||
.as_certificate_mut()
|
.as_certificate_mut()
|
||||||
.ser(&Some(TunnelCertData {
|
.ser(&Some(TunnelCertData {
|
||||||
key: Pem(key),
|
key: Pem(key),
|
||||||
cert: Pem(vec![cert.clone()]),
|
cert: chain.clone(),
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.result?;
|
.result?;
|
||||||
|
|
||||||
Ok(Pem(cert))
|
Ok(chain)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_certificate(ctx: TunnelContext) -> Result<Option<Pem<Vec<X509>>>, Error> {
|
pub async fn get_certificate(ctx: TunnelContext) -> Result<Option<Pem<Vec<X509>>>, Error> {
|
||||||
@@ -501,8 +510,12 @@ pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
|
|||||||
let cert = from_value::<Pem<Vec<X509>>>(
|
let cert = from_value::<Pem<Vec<X509>>>(
|
||||||
ctx.call_remote::<TunnelContext>("web.get-certificate", json!({}))
|
ctx.call_remote::<TunnelContext>("web.get-certificate", json!({}))
|
||||||
.await?,
|
.await?,
|
||||||
)?;
|
)?
|
||||||
println!("📝 SSL Certificate:");
|
.0
|
||||||
|
.pop()
|
||||||
|
.map(Pem)
|
||||||
|
.or_not_found("certificate in chain")?;
|
||||||
|
println!("📝 Root SSL Certificate:");
|
||||||
print!("{cert}");
|
print!("{cert}");
|
||||||
|
|
||||||
println!(concat!(
|
println!(concat!(
|
||||||
@@ -594,7 +607,7 @@ pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
|
|||||||
impl std::fmt::Display for Choice {
|
impl std::fmt::Display for Choice {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Generate => write!(f, "Generate a Self Signed Certificate"),
|
Self::Generate => write!(f, "Generate an SSL certificate"),
|
||||||
Self::Provide => write!(f, "Provide your own certificate and key"),
|
Self::Provide => write!(f, "Provide your own certificate and key"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -602,7 +615,7 @@ pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
|
|||||||
let options = vec![Choice::Generate, Choice::Provide];
|
let options = vec![Choice::Generate, Choice::Provide];
|
||||||
let choice = choose(
|
let choice = choose(
|
||||||
concat!(
|
concat!(
|
||||||
"Select whether to autogenerate a self-signed SSL certificate ",
|
"Select whether to generate an SSL certificate ",
|
||||||
"or provide your own certificate and key:"
|
"or provide your own certificate and key:"
|
||||||
),
|
),
|
||||||
&options,
|
&options,
|
||||||
|
|||||||
@@ -170,12 +170,14 @@ impl WgConfig {
|
|||||||
pub fn client_config(
|
pub fn client_config(
|
||||||
self,
|
self,
|
||||||
addr: Ipv4Addr,
|
addr: Ipv4Addr,
|
||||||
|
subnet: Ipv4Net,
|
||||||
server_pubkey: Base64<PublicKey>,
|
server_pubkey: Base64<PublicKey>,
|
||||||
server_addr: SocketAddr,
|
server_addr: SocketAddr,
|
||||||
) -> ClientConfig {
|
) -> ClientConfig {
|
||||||
ClientConfig {
|
ClientConfig {
|
||||||
client_config: self,
|
client_config: self,
|
||||||
client_addr: addr,
|
client_addr: addr,
|
||||||
|
subnet,
|
||||||
server_pubkey,
|
server_pubkey,
|
||||||
server_addr,
|
server_addr,
|
||||||
}
|
}
|
||||||
@@ -213,6 +215,7 @@ where
|
|||||||
pub struct ClientConfig {
|
pub struct ClientConfig {
|
||||||
client_config: WgConfig,
|
client_config: WgConfig,
|
||||||
client_addr: Ipv4Addr,
|
client_addr: Ipv4Addr,
|
||||||
|
subnet: Ipv4Net,
|
||||||
#[serde(deserialize_with = "deserialize_verifying_key")]
|
#[serde(deserialize_with = "deserialize_verifying_key")]
|
||||||
server_pubkey: Base64<PublicKey>,
|
server_pubkey: Base64<PublicKey>,
|
||||||
server_addr: SocketAddr,
|
server_addr: SocketAddr,
|
||||||
@@ -226,6 +229,7 @@ impl std::fmt::Display for ClientConfig {
|
|||||||
privkey = self.client_config.key.to_padded_string(),
|
privkey = self.client_config.key.to_padded_string(),
|
||||||
psk = self.client_config.psk.to_padded_string(),
|
psk = self.client_config.psk.to_padded_string(),
|
||||||
addr = self.client_addr,
|
addr = self.client_addr,
|
||||||
|
subnet = self.subnet,
|
||||||
server_pubkey = self.server_pubkey.to_padded_string(),
|
server_pubkey = self.server_pubkey.to_padded_string(),
|
||||||
server_addr = self.server_addr,
|
server_addr = self.server_addr,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,12 +19,6 @@ use ts_rs::TS;
|
|||||||
|
|
||||||
use crate::PLATFORM;
|
use crate::PLATFORM;
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::disk::mount::filesystem::MountType;
|
|
||||||
use crate::disk::mount::filesystem::bind::Bind;
|
|
||||||
use crate::disk::mount::filesystem::block_dev::BlockDev;
|
|
||||||
use crate::disk::mount::filesystem::efivarfs::EfiVarFs;
|
|
||||||
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
|
|
||||||
use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard};
|
|
||||||
use crate::notifications::{NotificationLevel, notify};
|
use crate::notifications::{NotificationLevel, notify};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{
|
use crate::progress::{
|
||||||
@@ -275,7 +269,6 @@ async fn maybe_do_update(
|
|||||||
download_phase.set_total(asset.commitment.size);
|
download_phase.set_total(asset.commitment.size);
|
||||||
download_phase.set_units(Some(ProgressUnits::Bytes));
|
download_phase.set_units(Some(ProgressUnits::Bytes));
|
||||||
let reverify_phase = progress.add_phase("Reverifying File".into(), Some(10));
|
let reverify_phase = progress.add_phase("Reverifying File".into(), Some(10));
|
||||||
let sync_boot_phase = progress.add_phase("Syncing Boot Files".into(), Some(1));
|
|
||||||
let finalize_phase = progress.add_phase("Finalizing Update".into(), Some(1));
|
let finalize_phase = progress.add_phase("Finalizing Update".into(), Some(1));
|
||||||
|
|
||||||
let start_progress = progress.snapshot();
|
let start_progress = progress.snapshot();
|
||||||
@@ -331,7 +324,6 @@ async fn maybe_do_update(
|
|||||||
prune_phase,
|
prune_phase,
|
||||||
download_phase,
|
download_phase,
|
||||||
reverify_phase,
|
reverify_phase,
|
||||||
sync_boot_phase,
|
|
||||||
finalize_phase,
|
finalize_phase,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -388,7 +380,6 @@ struct UpdateProgressHandles {
|
|||||||
prune_phase: PhaseProgressTrackerHandle,
|
prune_phase: PhaseProgressTrackerHandle,
|
||||||
download_phase: PhaseProgressTrackerHandle,
|
download_phase: PhaseProgressTrackerHandle,
|
||||||
reverify_phase: PhaseProgressTrackerHandle,
|
reverify_phase: PhaseProgressTrackerHandle,
|
||||||
sync_boot_phase: PhaseProgressTrackerHandle,
|
|
||||||
finalize_phase: PhaseProgressTrackerHandle,
|
finalize_phase: PhaseProgressTrackerHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +392,6 @@ async fn do_update(
|
|||||||
mut prune_phase,
|
mut prune_phase,
|
||||||
mut download_phase,
|
mut download_phase,
|
||||||
mut reverify_phase,
|
mut reverify_phase,
|
||||||
mut sync_boot_phase,
|
|
||||||
mut finalize_phase,
|
mut finalize_phase,
|
||||||
}: UpdateProgressHandles,
|
}: UpdateProgressHandles,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
@@ -416,9 +406,7 @@ async fn do_update(
|
|||||||
prune_phase.complete();
|
prune_phase.complete();
|
||||||
|
|
||||||
download_phase.start();
|
download_phase.start();
|
||||||
let path = Path::new("/media/startos/images")
|
let path = Path::new("/media/startos/images/next.squashfs");
|
||||||
.join(hex::encode(&asset.commitment.hash[..16]))
|
|
||||||
.with_extension("rootfs");
|
|
||||||
let mut dst = AtomicFile::new(&path, None::<&Path>)
|
let mut dst = AtomicFile::new(&path, None::<&Path>)
|
||||||
.await
|
.await
|
||||||
.with_kind(ErrorKind::Filesystem)?;
|
.with_kind(ErrorKind::Filesystem)?;
|
||||||
@@ -438,92 +426,24 @@ async fn do_update(
|
|||||||
dst.save().await.with_kind(ErrorKind::Filesystem)?;
|
dst.save().await.with_kind(ErrorKind::Filesystem)?;
|
||||||
reverify_phase.complete();
|
reverify_phase.complete();
|
||||||
|
|
||||||
sync_boot_phase.start();
|
finalize_phase.start();
|
||||||
Command::new("unsquashfs")
|
Command::new("unsquashfs")
|
||||||
.arg("-n")
|
.arg("-n")
|
||||||
.arg("-f")
|
.arg("-f")
|
||||||
.arg("-d")
|
.arg("-d")
|
||||||
.arg("/")
|
.arg("/")
|
||||||
.arg(&path)
|
.arg(&path)
|
||||||
.arg("boot")
|
.arg("/usr/lib/startos/scripts/upgrade")
|
||||||
.invoke(crate::ErrorKind::Filesystem)
|
.invoke(crate::ErrorKind::Filesystem)
|
||||||
.await?;
|
.await?;
|
||||||
if &*PLATFORM != "raspberrypi" {
|
|
||||||
let mountpoint = "/media/startos/next";
|
|
||||||
let root_guard = OverlayGuard::mount(
|
|
||||||
TmpMountGuard::mount(&BlockDev::new(&path), MountType::ReadOnly).await?,
|
|
||||||
mountpoint,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let startos = MountGuard::mount(
|
|
||||||
&Bind::new("/media/startos/root"),
|
|
||||||
root_guard.path().join("media/startos/root"),
|
|
||||||
MountType::ReadOnly,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let boot_guard = MountGuard::mount(
|
|
||||||
&Bind::new("/boot"),
|
|
||||||
root_guard.path().join("boot"),
|
|
||||||
MountType::ReadWrite,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let dev = MountGuard::mount(
|
|
||||||
&Bind::new("/dev"),
|
|
||||||
root_guard.path().join("dev"),
|
|
||||||
MountType::ReadWrite,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let proc = MountGuard::mount(
|
|
||||||
&Bind::new("/proc"),
|
|
||||||
root_guard.path().join("proc"),
|
|
||||||
MountType::ReadWrite,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let sys = MountGuard::mount(
|
|
||||||
&Bind::new("/sys"),
|
|
||||||
root_guard.path().join("sys"),
|
|
||||||
MountType::ReadWrite,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let efivarfs = if tokio::fs::metadata("/sys/firmware/efi").await.is_ok() {
|
|
||||||
Some(
|
|
||||||
MountGuard::mount(
|
|
||||||
&EfiVarFs,
|
|
||||||
root_guard.path().join("sys/firmware/efi/efivars"),
|
|
||||||
MountType::ReadWrite,
|
|
||||||
)
|
|
||||||
.await?,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Command::new("chroot")
|
let checksum = hex::encode(&asset.commitment.hash[..16]);
|
||||||
.arg(root_guard.path())
|
|
||||||
.arg("update-grub2")
|
|
||||||
.invoke(ErrorKind::Grub)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(efivarfs) = efivarfs {
|
Command::new("/usr/lib/startos/scripts/upgrade")
|
||||||
efivarfs.unmount(false).await?;
|
.env("CHECKSUM", &checksum)
|
||||||
}
|
|
||||||
sys.unmount(false).await?;
|
|
||||||
proc.unmount(false).await?;
|
|
||||||
dev.unmount(false).await?;
|
|
||||||
boot_guard.unmount(false).await?;
|
|
||||||
startos.unmount(false).await?;
|
|
||||||
root_guard.unmount(false).await?;
|
|
||||||
}
|
|
||||||
sync_boot_phase.complete();
|
|
||||||
|
|
||||||
finalize_phase.start();
|
|
||||||
Command::new("ln")
|
|
||||||
.arg("-rsf")
|
|
||||||
.arg(&path)
|
.arg(&path)
|
||||||
.arg("/media/startos/config/current.rootfs")
|
.invoke(ErrorKind::Grub)
|
||||||
.invoke(crate::ErrorKind::Filesystem)
|
|
||||||
.await?;
|
.await?;
|
||||||
Command::new("sync").invoke(ErrorKind::Filesystem).await?;
|
|
||||||
finalize_phase.complete();
|
finalize_phase.complete();
|
||||||
|
|
||||||
progress.complete();
|
progress.complete();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::io::SeekFrom;
|
use std::io::SeekFrom;
|
||||||
|
use std::path::Path;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::task::Poll;
|
use std::task::Poll;
|
||||||
@@ -56,6 +57,7 @@ struct Progress {
|
|||||||
tracker: PhaseProgressTrackerHandle,
|
tracker: PhaseProgressTrackerHandle,
|
||||||
expected_size: Option<u64>,
|
expected_size: Option<u64>,
|
||||||
written: u64,
|
written: u64,
|
||||||
|
complete: bool,
|
||||||
error: Option<Error>,
|
error: Option<Error>,
|
||||||
}
|
}
|
||||||
impl Progress {
|
impl Progress {
|
||||||
@@ -111,9 +113,7 @@ impl Progress {
|
|||||||
}
|
}
|
||||||
async fn ready(watch: &mut watch::Receiver<Self>) -> Result<(), Error> {
|
async fn ready(watch: &mut watch::Receiver<Self>) -> Result<(), Error> {
|
||||||
match &*watch
|
match &*watch
|
||||||
.wait_for(|progress| {
|
.wait_for(|progress| progress.error.is_some() || progress.complete)
|
||||||
progress.error.is_some() || Some(progress.written) == progress.expected_size
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
Error::new(
|
Error::new(
|
||||||
@@ -126,8 +126,9 @@ impl Progress {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn complete(&mut self) -> bool {
|
fn complete(&mut self) -> bool {
|
||||||
|
let mut changed = !self.complete;
|
||||||
self.tracker.complete();
|
self.tracker.complete();
|
||||||
match self {
|
changed |= match self {
|
||||||
Self {
|
Self {
|
||||||
expected_size: Some(size),
|
expected_size: Some(size),
|
||||||
written,
|
written,
|
||||||
@@ -165,18 +166,21 @@ impl Progress {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
};
|
||||||
|
self.complete = true;
|
||||||
|
changed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct UploadingFile {
|
pub struct UploadingFile {
|
||||||
tmp_dir: Arc<TmpDir>,
|
tmp_dir: Option<Arc<TmpDir>>,
|
||||||
file: MultiCursorFile,
|
file: MultiCursorFile,
|
||||||
progress: watch::Receiver<Progress>,
|
progress: watch::Receiver<Progress>,
|
||||||
}
|
}
|
||||||
impl UploadingFile {
|
impl UploadingFile {
|
||||||
pub async fn new(
|
pub async fn with_path(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
mut progress: PhaseProgressTrackerHandle,
|
mut progress: PhaseProgressTrackerHandle,
|
||||||
) -> Result<(UploadHandle, Self), Error> {
|
) -> Result<(UploadHandle, Self), Error> {
|
||||||
progress.set_units(Some(ProgressUnits::Bytes));
|
progress.set_units(Some(ProgressUnits::Bytes));
|
||||||
@@ -185,25 +189,35 @@ impl UploadingFile {
|
|||||||
expected_size: None,
|
expected_size: None,
|
||||||
written: 0,
|
written: 0,
|
||||||
error: None,
|
error: None,
|
||||||
|
complete: false,
|
||||||
});
|
});
|
||||||
let tmp_dir = Arc::new(TmpDir::new().await?);
|
let file = create_file(path).await?;
|
||||||
let file = create_file(tmp_dir.join("upload.tmp")).await?;
|
|
||||||
let uploading = Self {
|
let uploading = Self {
|
||||||
tmp_dir: tmp_dir.clone(),
|
tmp_dir: None,
|
||||||
file: MultiCursorFile::open(&file).await?,
|
file: MultiCursorFile::open(&file).await?,
|
||||||
progress: progress.1,
|
progress: progress.1,
|
||||||
};
|
};
|
||||||
Ok((
|
Ok((
|
||||||
UploadHandle {
|
UploadHandle {
|
||||||
tmp_dir,
|
tmp_dir: None,
|
||||||
file,
|
file,
|
||||||
progress: progress.0,
|
progress: progress.0,
|
||||||
},
|
},
|
||||||
uploading,
|
uploading,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
pub async fn new(progress: PhaseProgressTrackerHandle) -> Result<(UploadHandle, Self), Error> {
|
||||||
|
let tmp_dir = Arc::new(TmpDir::new().await?);
|
||||||
|
let (mut handle, mut file) = Self::with_path(tmp_dir.join("upload.tmp"), progress).await?;
|
||||||
|
handle.tmp_dir = Some(tmp_dir.clone());
|
||||||
|
file.tmp_dir = Some(tmp_dir);
|
||||||
|
Ok((handle, file))
|
||||||
|
}
|
||||||
|
pub async fn wait_for_complete(&self) -> Result<(), Error> {
|
||||||
|
Progress::ready(&mut self.progress.clone()).await
|
||||||
|
}
|
||||||
pub async fn delete(self) -> Result<(), Error> {
|
pub async fn delete(self) -> Result<(), Error> {
|
||||||
if let Ok(tmp_dir) = Arc::try_unwrap(self.tmp_dir) {
|
if let Some(Ok(tmp_dir)) = self.tmp_dir.map(Arc::try_unwrap) {
|
||||||
tmp_dir.delete().await?;
|
tmp_dir.delete().await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -234,7 +248,7 @@ impl ArchiveSource for UploadingFile {
|
|||||||
|
|
||||||
#[pin_project::pin_project(project = UploadingFileReaderProjection)]
|
#[pin_project::pin_project(project = UploadingFileReaderProjection)]
|
||||||
pub struct UploadingFileReader {
|
pub struct UploadingFileReader {
|
||||||
tmp_dir: Arc<TmpDir>,
|
tmp_dir: Option<Arc<TmpDir>>,
|
||||||
position: u64,
|
position: u64,
|
||||||
to_seek: Option<SeekFrom>,
|
to_seek: Option<SeekFrom>,
|
||||||
#[pin]
|
#[pin]
|
||||||
@@ -330,7 +344,7 @@ impl AsyncSeek for UploadingFileReader {
|
|||||||
|
|
||||||
#[pin_project::pin_project(PinnedDrop)]
|
#[pin_project::pin_project(PinnedDrop)]
|
||||||
pub struct UploadHandle {
|
pub struct UploadHandle {
|
||||||
tmp_dir: Arc<TmpDir>,
|
tmp_dir: Option<Arc<TmpDir>>,
|
||||||
#[pin]
|
#[pin]
|
||||||
file: File,
|
file: File,
|
||||||
progress: watch::Sender<Progress>,
|
progress: watch::Sender<Progress>,
|
||||||
@@ -377,6 +391,9 @@ impl UploadHandle {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Err(e) = self.file.sync_all().await {
|
||||||
|
self.progress.send_if_modified(|p| p.handle_error(&e));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[pin_project::pinned_drop]
|
#[pin_project::pinned_drop]
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
use std::sync::Weak;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use axum::middleware::FromFn;
|
|
||||||
use futures::future::{BoxFuture, FusedFuture, abortable, pending};
|
use futures::future::{BoxFuture, FusedFuture, abortable, pending};
|
||||||
use futures::stream::{AbortHandle, Abortable, BoxStream};
|
use futures::stream::{AbortHandle, Abortable, BoxStream};
|
||||||
use futures::{Future, FutureExt, Stream, StreamExt};
|
use futures::{Future, FutureExt, Stream, StreamExt};
|
||||||
use rpc_toolkit::from_fn_blocking;
|
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
use tokio::task::LocalSet;
|
use tokio::task::LocalSet;
|
||||||
|
|
||||||
@@ -201,3 +200,26 @@ async fn test_cancellable() {
|
|||||||
handle.cancel_and_wait().await;
|
handle.cancel_and_wait().await;
|
||||||
assert!(weak.strong_count() == 0);
|
assert!(weak.strong_count() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pin_project::pin_project]
|
||||||
|
pub struct WeakFuture<Fut> {
|
||||||
|
rc: Weak<()>,
|
||||||
|
#[pin]
|
||||||
|
fut: Fut,
|
||||||
|
}
|
||||||
|
impl<Fut> WeakFuture<Fut> {
|
||||||
|
pub fn new(rc: Weak<()>, fut: Fut) -> Self {
|
||||||
|
Self { rc, fut }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<Fut: Future> Future for WeakFuture<Fut> {
|
||||||
|
type Output = Option<Fut::Output>;
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = self.project();
|
||||||
|
if this.rc.strong_count() > 0 {
|
||||||
|
this.fut.poll(cx).map(Some)
|
||||||
|
} else {
|
||||||
|
Poll::Ready(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ pub mod net;
|
|||||||
pub mod rpc;
|
pub mod rpc;
|
||||||
pub mod rpc_client;
|
pub mod rpc_client;
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
//pub mod squashfs;
|
// pub mod squashfs;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
pub mod tui;
|
pub mod tui;
|
||||||
|
|
||||||
|
|||||||
@@ -98,8 +98,7 @@ impl<W: Write> Visit<SquashfsSerializer<W>> for Superblock {
|
|||||||
|
|
||||||
#[pin_project::pin_project]
|
#[pin_project::pin_project]
|
||||||
pub struct MetadataBlocksWriter<W> {
|
pub struct MetadataBlocksWriter<W> {
|
||||||
input: [u8; 8192],
|
input: PartialBuffer<[u8; 8192]>,
|
||||||
size: usize,
|
|
||||||
size_addr: Option<u64>,
|
size_addr: Option<u64>,
|
||||||
output: PartialBuffer<[u8; 8192]>,
|
output: PartialBuffer<[u8; 8192]>,
|
||||||
output_flushed: usize,
|
output_flushed: usize,
|
||||||
@@ -123,25 +122,29 @@ enum WriteState {
|
|||||||
WritingSizeHeader(u16),
|
WritingSizeHeader(u16),
|
||||||
WritingOutput(Box<Self>),
|
WritingOutput(Box<Self>),
|
||||||
EncodingInput,
|
EncodingInput,
|
||||||
FinishingCompression,
|
|
||||||
WritingFinalSizeHeader(u64, u64),
|
|
||||||
SeekingToEnd(u64),
|
SeekingToEnd(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_seek_helper<W: AsyncSeek>(
|
fn poll_seek_helper<W: AsyncSeek>(
|
||||||
writer: std::pin::Pin<&mut W>,
|
mut writer: std::pin::Pin<&mut W>,
|
||||||
seek_state: &mut SeekState,
|
seek_state: &mut SeekState,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
pos: u64,
|
pos: u64,
|
||||||
) -> std::task::Poll<std::io::Result<u64>> {
|
) -> std::task::Poll<std::io::Result<u64>> {
|
||||||
match *seek_state {
|
match *seek_state {
|
||||||
SeekState::Idle => {
|
SeekState::Idle => {
|
||||||
writer.start_seek(std::io::SeekFrom::Start(pos))?;
|
writer.as_mut().start_seek(std::io::SeekFrom::Start(pos))?;
|
||||||
*seek_state = SeekState::Seeking(pos);
|
*seek_state = SeekState::Seeking(pos);
|
||||||
Poll::Pending
|
match writer.as_mut().poll_complete(cx)? {
|
||||||
|
Poll::Ready(result) => {
|
||||||
|
*seek_state = SeekState::Idle;
|
||||||
|
Poll::Ready(Ok(result))
|
||||||
|
}
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SeekState::Seeking(target) if target == pos => {
|
SeekState::Seeking(target) if target == pos => {
|
||||||
let result = ready!(writer.poll_complete(cx))?;
|
let result = ready!(writer.as_mut().poll_complete(cx))?;
|
||||||
*seek_state = SeekState::Idle;
|
*seek_state = SeekState::Idle;
|
||||||
Poll::Ready(Ok(result))
|
Poll::Ready(Ok(result))
|
||||||
}
|
}
|
||||||
@@ -151,35 +154,53 @@ fn poll_seek_helper<W: AsyncSeek>(
|
|||||||
pos,
|
pos,
|
||||||
old_target
|
old_target
|
||||||
);
|
);
|
||||||
writer.start_seek(std::io::SeekFrom::Start(pos))?;
|
writer.as_mut().start_seek(std::io::SeekFrom::Start(pos))?;
|
||||||
*seek_state = SeekState::Seeking(pos);
|
*seek_state = SeekState::Seeking(pos);
|
||||||
Poll::Pending
|
match writer.as_mut().poll_complete(cx)? {
|
||||||
|
Poll::Ready(result) => {
|
||||||
|
*seek_state = SeekState::Idle;
|
||||||
|
Poll::Ready(Ok(result))
|
||||||
|
}
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SeekState::GettingPosition => {
|
SeekState::GettingPosition => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"poll_seek({}) called while getting stream position, canceling",
|
"poll_seek({}) called while getting stream position, canceling",
|
||||||
pos
|
pos
|
||||||
);
|
);
|
||||||
writer.start_seek(std::io::SeekFrom::Start(pos))?;
|
writer.as_mut().start_seek(std::io::SeekFrom::Start(pos))?;
|
||||||
*seek_state = SeekState::Seeking(pos);
|
*seek_state = SeekState::Seeking(pos);
|
||||||
Poll::Pending
|
match writer.as_mut().poll_complete(cx)? {
|
||||||
|
Poll::Ready(result) => {
|
||||||
|
*seek_state = SeekState::Idle;
|
||||||
|
Poll::Ready(Ok(result))
|
||||||
|
}
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_stream_position_helper<W: AsyncSeek>(
|
fn poll_stream_position_helper<W: AsyncSeek>(
|
||||||
writer: std::pin::Pin<&mut W>,
|
mut writer: std::pin::Pin<&mut W>,
|
||||||
seek_state: &mut SeekState,
|
seek_state: &mut SeekState,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
) -> std::task::Poll<std::io::Result<u64>> {
|
) -> std::task::Poll<std::io::Result<u64>> {
|
||||||
match *seek_state {
|
match *seek_state {
|
||||||
SeekState::Idle => {
|
SeekState::Idle => {
|
||||||
writer.start_seek(std::io::SeekFrom::Current(0))?;
|
writer.as_mut().start_seek(std::io::SeekFrom::Current(0))?;
|
||||||
*seek_state = SeekState::GettingPosition;
|
*seek_state = SeekState::GettingPosition;
|
||||||
Poll::Pending
|
match writer.as_mut().poll_complete(cx)? {
|
||||||
|
Poll::Ready(result) => {
|
||||||
|
*seek_state = SeekState::Idle;
|
||||||
|
Poll::Ready(Ok(result))
|
||||||
|
}
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SeekState::GettingPosition => {
|
SeekState::GettingPosition => {
|
||||||
let result = ready!(writer.poll_complete(cx))?;
|
let result = ready!(writer.as_mut().poll_complete(cx))?;
|
||||||
*seek_state = SeekState::Idle;
|
*seek_state = SeekState::Idle;
|
||||||
Poll::Ready(Ok(result))
|
Poll::Ready(Ok(result))
|
||||||
}
|
}
|
||||||
@@ -188,18 +209,22 @@ fn poll_stream_position_helper<W: AsyncSeek>(
|
|||||||
"poll_stream_position called while seeking to {}, canceling",
|
"poll_stream_position called while seeking to {}, canceling",
|
||||||
target
|
target
|
||||||
);
|
);
|
||||||
writer.start_seek(std::io::SeekFrom::Current(0))?;
|
writer.as_mut().start_seek(std::io::SeekFrom::Current(0))?;
|
||||||
*seek_state = SeekState::GettingPosition;
|
*seek_state = SeekState::GettingPosition;
|
||||||
Poll::Pending
|
match writer.as_mut().poll_complete(cx)? {
|
||||||
|
Poll::Ready(result) => {
|
||||||
|
*seek_state = SeekState::Idle;
|
||||||
|
Poll::Ready(Ok(result))
|
||||||
|
}
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Write + Seek> Write for MetadataBlocksWriter<W> {
|
impl<W: Write + Seek> Write for MetadataBlocksWriter<W> {
|
||||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
let n = buf.len().min(self.input.len() - self.size);
|
let n = self.input.copy_unwritten_from(&mut PartialBuffer::new(buf));
|
||||||
self.input[self.size..self.size + n].copy_from_slice(&buf[..n]);
|
|
||||||
self.size += n;
|
|
||||||
if n < buf.len() {
|
if n < buf.len() {
|
||||||
self.flush()?;
|
self.flush()?;
|
||||||
}
|
}
|
||||||
@@ -207,9 +232,9 @@ impl<W: Write + Seek> Write for MetadataBlocksWriter<W> {
|
|||||||
}
|
}
|
||||||
fn flush(&mut self) -> std::io::Result<()> {
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
loop {
|
loop {
|
||||||
match self.write_state {
|
match &self.write_state {
|
||||||
WriteState::Idle => {
|
WriteState::Idle => {
|
||||||
if self.size == 0 {
|
if self.input.written().is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
self.write_state = WriteState::WritingSizeHeader(0);
|
self.write_state = WriteState::WritingSizeHeader(0);
|
||||||
@@ -218,12 +243,12 @@ impl<W: Write + Seek> Write for MetadataBlocksWriter<W> {
|
|||||||
WriteState::WritingSizeHeader(size) => {
|
WriteState::WritingSizeHeader(size) => {
|
||||||
let done = if let Some(size_addr) = self.size_addr {
|
let done = if let Some(size_addr) = self.size_addr {
|
||||||
self.writer.seek(SeekFrom::Start(size_addr))?;
|
self.writer.seek(SeekFrom::Start(size_addr))?;
|
||||||
Some(size_addr + size as u64)
|
Some(size_addr + 2 + *size as u64)
|
||||||
} else {
|
} else {
|
||||||
self.size_addr = Some(self.writer.stream_position()?);
|
self.size_addr = Some(self.writer.stream_position()?);
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
self.output.unwritten_mut()[..2].copy_from_slice(&u16::to_le_bytes(size)[..]);
|
self.output.unwritten_mut()[..2].copy_from_slice(&u16::to_le_bytes(*size)[..]);
|
||||||
self.output.advance(2);
|
self.output.advance(2);
|
||||||
self.write_state =
|
self.write_state =
|
||||||
WriteState::WritingOutput(Box::new(if let Some(end) = done {
|
WriteState::WritingOutput(Box::new(if let Some(end) = done {
|
||||||
@@ -242,80 +267,33 @@ impl<W: Write + Seek> Write for MetadataBlocksWriter<W> {
|
|||||||
} else {
|
} else {
|
||||||
self.output.reset();
|
self.output.reset();
|
||||||
self.output_flushed = 0;
|
self.output_flushed = 0;
|
||||||
self.write_state = *next;
|
self.write_state = *next.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteState::EncodingInput => {
|
WriteState::EncodingInput => {
|
||||||
let encoder = self.zstd.get_or_insert_with(|| ZstdEncoder::new(22));
|
let encoder = self.zstd.get_or_insert_with(|| ZstdEncoder::new(22));
|
||||||
let mut input = PartialBuffer::new(&self.input[..self.size]);
|
encoder.encode(
|
||||||
while !self.output.unwritten().is_empty() && !input.unwritten().is_empty() {
|
&mut PartialBuffer::new(&self.input.written()),
|
||||||
encoder.encode(&mut input, &mut self.output)?;
|
&mut self.output,
|
||||||
}
|
)?;
|
||||||
while !encoder.flush(&mut self.output)? {}
|
let compressed = if !encoder.finish(&mut self.output)? {
|
||||||
while !encoder.finish(&mut self.output)? {}
|
std::mem::swap(&mut self.output, &mut self.input);
|
||||||
if !self.output.unwritten().is_empty() {
|
false
|
||||||
let mut input =
|
} else {
|
||||||
PartialBuffer::new(&self.input[self.input_flushed..self.size]);
|
true
|
||||||
encoder.encode(&mut input, &mut self.output)?;
|
};
|
||||||
self.input_flushed += input.written().len();
|
self.zstd = None;
|
||||||
}
|
self.input.reset();
|
||||||
self.write_state = WriteState::WritingOutput(Box::new());
|
self.write_state =
|
||||||
continue;
|
WriteState::WritingOutput(Box::new(WriteState::WritingSizeHeader(
|
||||||
}
|
self.output.written().len() as u16
|
||||||
|
| if compressed { 0 } else { 0x8000 },
|
||||||
WriteState::FinishingCompression => {
|
)));
|
||||||
if !self.output.unwritten().is_empty() {
|
|
||||||
if self.zstd.as_mut().unwrap().finish(&mut self.output)? {
|
|
||||||
self.zstd = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.output.written().len() > self.output_flushed {
|
|
||||||
self.write_state = WriteState::WritingOutput;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if self.zstd.is_none() && self.output.written().len() == self.output_flushed {
|
|
||||||
self.output_flushed = 0;
|
|
||||||
self.output.reset();
|
|
||||||
let end_addr = self.writer.stream_position()?;
|
|
||||||
let size_addr = self.size_addr.ok_or_else(|| {
|
|
||||||
std::io::Error::new(
|
|
||||||
std::io::ErrorKind::InvalidData,
|
|
||||||
"size_addr not set when finishing compression",
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
self.write_state = WriteState::WritingFinalSizeHeader(size_addr, end_addr);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteState::WritingFinalSizeHeader(size_addr, end_addr) => {
|
|
||||||
if self.output.written().len() > self.output_flushed {
|
|
||||||
let n = self
|
|
||||||
.writer
|
|
||||||
.write(&self.output.written()[self.output_flushed..])?;
|
|
||||||
self.output_flushed += n;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
self.writer.seek(std::io::SeekFrom::Start(size_addr))?;
|
|
||||||
self.output.unwritten_mut()[..2]
|
|
||||||
.copy_from_slice(&((end_addr - size_addr - 2) as u16).to_le_bytes());
|
|
||||||
self.output.advance(2);
|
|
||||||
let n = self.writer.write(&self.output.written())?;
|
|
||||||
self.output_flushed = n;
|
|
||||||
if n == 2 {
|
|
||||||
self.output_flushed = 0;
|
|
||||||
self.output.reset();
|
|
||||||
self.write_state = WriteState::SeekingToEnd(end_addr);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteState::SeekingToEnd(end_addr) => {
|
WriteState::SeekingToEnd(end_addr) => {
|
||||||
self.writer.seek(std::io::SeekFrom::Start(end_addr))?;
|
self.writer.seek(std::io::SeekFrom::Start(*end_addr))?;
|
||||||
self.input_flushed = 0;
|
|
||||||
self.size = 0;
|
|
||||||
self.size_addr = None;
|
self.size_addr = None;
|
||||||
self.write_state = WriteState::Idle;
|
self.write_state = WriteState::Idle;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -332,11 +310,9 @@ impl<W: AsyncWrite + AsyncSeek> AsyncWrite for MetadataBlocksWriter<W> {
|
|||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
) -> std::task::Poll<std::io::Result<usize>> {
|
) -> std::task::Poll<std::io::Result<usize>> {
|
||||||
let this = self.as_mut().project();
|
let this = self.as_mut().project();
|
||||||
let n = buf.len().min(this.input.len() - *this.size);
|
let n = this.input.copy_unwritten_from(&mut PartialBuffer::new(buf));
|
||||||
this.input[*this.size..*this.size + n].copy_from_slice(&buf[..n]);
|
|
||||||
*this.size += n;
|
|
||||||
if n < buf.len() {
|
if n < buf.len() {
|
||||||
ready!(self.poll_flush(cx)?);
|
ready!(self.poll_flush(cx))?;
|
||||||
}
|
}
|
||||||
Poll::Ready(Ok(n))
|
Poll::Ready(Ok(n))
|
||||||
}
|
}
|
||||||
@@ -347,115 +323,76 @@ impl<W: AsyncWrite + AsyncSeek> AsyncWrite for MetadataBlocksWriter<W> {
|
|||||||
) -> std::task::Poll<std::io::Result<()>> {
|
) -> std::task::Poll<std::io::Result<()>> {
|
||||||
loop {
|
loop {
|
||||||
let mut this = self.as_mut().project();
|
let mut this = self.as_mut().project();
|
||||||
match *this.write_state {
|
match this.write_state.clone() {
|
||||||
WriteState::Idle => {
|
WriteState::Idle => {
|
||||||
if *this.size == 0 {
|
if this.input.written().is_empty() {
|
||||||
return Poll::Ready(Ok(()));
|
return Poll::Ready(Ok(()));
|
||||||
}
|
}
|
||||||
if this.size_addr.is_none() {
|
*this.write_state = WriteState::WritingSizeHeader(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteState::WritingSizeHeader(size) => {
|
||||||
|
let done = if let Some(size_addr) = *this.size_addr {
|
||||||
|
ready!(poll_seek_helper(
|
||||||
|
this.writer.as_mut(),
|
||||||
|
this.seek_state,
|
||||||
|
cx,
|
||||||
|
size_addr
|
||||||
|
))?;
|
||||||
|
Some(size_addr + 2 + size as u64)
|
||||||
|
} else {
|
||||||
let pos = ready!(poll_stream_position_helper(
|
let pos = ready!(poll_stream_position_helper(
|
||||||
this.writer.as_mut(),
|
this.writer.as_mut(),
|
||||||
this.seek_state,
|
this.seek_state,
|
||||||
cx
|
cx
|
||||||
))?;
|
))?;
|
||||||
*this.size_addr = Some(pos);
|
*this.size_addr = Some(pos);
|
||||||
this.output.unwritten_mut()[..2].copy_from_slice(&[0; 2]);
|
None
|
||||||
this.output.advance(2);
|
};
|
||||||
}
|
this.output.unwritten_mut()[..2]
|
||||||
*this.write_state = WriteState::WritingOutput;
|
.copy_from_slice(&u16::to_le_bytes(size)[..]);
|
||||||
continue;
|
this.output.advance(2);
|
||||||
}
|
*this.write_state = WriteState::WritingOutput(Box::new(if let Some(end) = done {
|
||||||
|
WriteState::SeekingToEnd(end)
|
||||||
WriteState::WritingOutput => {
|
|
||||||
if this.output.written().len() > *this.output_flushed {
|
|
||||||
let n = ready!(
|
|
||||||
this.writer
|
|
||||||
.as_mut()
|
|
||||||
.poll_write(cx, &this.output.written()[*this.output_flushed..])
|
|
||||||
)?;
|
|
||||||
*this.output_flushed += n;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if this.output.written().len() == *this.output_flushed {
|
|
||||||
*this.output_flushed = 0;
|
|
||||||
this.output.reset();
|
|
||||||
}
|
|
||||||
if *this.input_flushed < *this.size {
|
|
||||||
if !this.output.unwritten().is_empty() {
|
|
||||||
let mut input =
|
|
||||||
PartialBuffer::new(&this.input[*this.input_flushed..*this.size]);
|
|
||||||
this.zstd
|
|
||||||
.get_or_insert_with(|| ZstdEncoder::new(22))
|
|
||||||
.encode(&mut input, this.output)?;
|
|
||||||
*this.input_flushed += input.written().len();
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
} else {
|
} else {
|
||||||
if !this.output.unwritten().is_empty() {
|
WriteState::EncodingInput
|
||||||
if this.zstd.as_mut().unwrap().finish(this.output)? {
|
}));
|
||||||
*this.zstd = None;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if this.zstd.is_none()
|
|
||||||
&& this.output.written().len() == *this.output_flushed
|
|
||||||
{
|
|
||||||
*this.output_flushed = 0;
|
|
||||||
this.output.reset();
|
|
||||||
if let Some(size_addr) = *this.size_addr {
|
|
||||||
let end_addr = ready!(poll_stream_position_helper(
|
|
||||||
this.writer.as_mut(),
|
|
||||||
this.seek_state,
|
|
||||||
cx
|
|
||||||
))?;
|
|
||||||
*this.write_state =
|
|
||||||
WriteState::WritingFinalSizeHeader(size_addr, end_addr);
|
|
||||||
ready!(poll_seek_helper(
|
|
||||||
this.writer.as_mut(),
|
|
||||||
this.seek_state,
|
|
||||||
cx,
|
|
||||||
size_addr
|
|
||||||
))?;
|
|
||||||
this.output.unwritten_mut()[..2].copy_from_slice(
|
|
||||||
&((end_addr - size_addr - 2) as u16).to_le_bytes(),
|
|
||||||
);
|
|
||||||
this.output.advance(2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Poll::Ready(Ok(()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteState::WritingSizeHeader(_size_addr) => {
|
WriteState::WritingOutput(next) => {
|
||||||
*this.write_state = WriteState::WritingOutput;
|
if this.output.written().len() > *this.output_flushed {
|
||||||
continue;
|
let n = ready!(this
|
||||||
|
.writer
|
||||||
|
.as_mut()
|
||||||
|
.poll_write(cx, &this.output.written()[*this.output_flushed..]))?;
|
||||||
|
*this.output_flushed += n;
|
||||||
|
} else {
|
||||||
|
this.output.reset();
|
||||||
|
*this.output_flushed = 0;
|
||||||
|
*this.write_state = *next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteState::EncodingInput => {
|
WriteState::EncodingInput => {
|
||||||
*this.write_state = WriteState::WritingOutput;
|
let encoder = this.zstd.get_or_insert_with(|| ZstdEncoder::new(22));
|
||||||
continue;
|
encoder.encode(
|
||||||
}
|
&mut PartialBuffer::new(this.input.written()),
|
||||||
|
this.output,
|
||||||
WriteState::FinishingCompression => {
|
)?;
|
||||||
*this.write_state = WriteState::WritingOutput;
|
let compressed = if !encoder.finish(this.output)? {
|
||||||
continue;
|
std::mem::swap(this.output, this.input);
|
||||||
}
|
false
|
||||||
|
} else {
|
||||||
WriteState::WritingFinalSizeHeader(_size_addr, end_addr) => {
|
true
|
||||||
if this.output.written().len() > *this.output_flushed {
|
};
|
||||||
let n = ready!(
|
*this.zstd = None;
|
||||||
this.writer
|
this.input.reset();
|
||||||
.as_mut()
|
*this.write_state = WriteState::WritingOutput(Box::new(
|
||||||
.poll_write(cx, &this.output.written()[*this.output_flushed..])
|
WriteState::WritingSizeHeader(
|
||||||
)?;
|
this.output.written().len() as u16
|
||||||
*this.output_flushed += n;
|
| if compressed { 0 } else { 0x8000 },
|
||||||
continue;
|
),
|
||||||
}
|
));
|
||||||
*this.output_flushed = 0;
|
|
||||||
this.output.reset();
|
|
||||||
*this.write_state = WriteState::SeekingToEnd(end_addr);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteState::SeekingToEnd(end_addr) => {
|
WriteState::SeekingToEnd(end_addr) => {
|
||||||
@@ -466,8 +403,6 @@ impl<W: AsyncWrite + AsyncSeek> AsyncWrite for MetadataBlocksWriter<W> {
|
|||||||
end_addr
|
end_addr
|
||||||
))?;
|
))?;
|
||||||
*this.size_addr = None;
|
*this.size_addr = None;
|
||||||
*this.input_flushed = 0;
|
|
||||||
*this.size = 0;
|
|
||||||
*this.write_state = WriteState::Idle;
|
*this.write_state = WriteState::Idle;
|
||||||
return Poll::Ready(Ok(()));
|
return Poll::Ready(Ok(()));
|
||||||
}
|
}
|
||||||
@@ -486,11 +421,9 @@ impl<W: AsyncWrite + AsyncSeek> AsyncWrite for MetadataBlocksWriter<W> {
|
|||||||
impl<W> MetadataBlocksWriter<W> {
|
impl<W> MetadataBlocksWriter<W> {
|
||||||
pub fn new(writer: W) -> Self {
|
pub fn new(writer: W) -> Self {
|
||||||
Self {
|
Self {
|
||||||
input: [0; 8192],
|
input: PartialBuffer::new([0; 8192]),
|
||||||
input_flushed: 0,
|
|
||||||
size: 0,
|
|
||||||
size_addr: None,
|
size_addr: None,
|
||||||
output: PartialBuffer::new([0; 4096]),
|
output: PartialBuffer::new([0; 8192]),
|
||||||
output_flushed: 0,
|
output_flushed: 0,
|
||||||
zstd: None,
|
zstd: None,
|
||||||
seek_state: SeekState::Idle,
|
seek_state: SeekState::Idle,
|
||||||
@@ -507,11 +440,10 @@ use tokio::io::AsyncRead;
|
|||||||
pub struct MetadataBlocksReader<R> {
|
pub struct MetadataBlocksReader<R> {
|
||||||
#[pin]
|
#[pin]
|
||||||
reader: R,
|
reader: R,
|
||||||
size_buf: [u8; 2],
|
size_buf: PartialBuffer<[u8; 2]>,
|
||||||
size_bytes_read: usize,
|
compressed: PartialBuffer<[u8; 8192]>,
|
||||||
compressed: [u8; 8192],
|
|
||||||
compressed_size: usize,
|
compressed_size: usize,
|
||||||
compressed_pos: usize,
|
is_compressed: bool,
|
||||||
output: PartialBuffer<[u8; 8192]>,
|
output: PartialBuffer<[u8; 8192]>,
|
||||||
output_pos: usize,
|
output_pos: usize,
|
||||||
zstd: Option<ZstdDecoder>,
|
zstd: Option<ZstdDecoder>,
|
||||||
@@ -531,11 +463,10 @@ impl<R> MetadataBlocksReader<R> {
|
|||||||
pub fn new(reader: R) -> Self {
|
pub fn new(reader: R) -> Self {
|
||||||
Self {
|
Self {
|
||||||
reader,
|
reader,
|
||||||
size_buf: [0; 2],
|
size_buf: PartialBuffer::new([0; 2]),
|
||||||
size_bytes_read: 0,
|
compressed: PartialBuffer::new([0; 8192]),
|
||||||
compressed: [0; 8192],
|
|
||||||
compressed_size: 0,
|
compressed_size: 0,
|
||||||
compressed_pos: 0,
|
is_compressed: false,
|
||||||
output: PartialBuffer::new([0; 8192]),
|
output: PartialBuffer::new([0; 8192]),
|
||||||
output_pos: 0,
|
output_pos: 0,
|
||||||
zstd: None,
|
zstd: None,
|
||||||
@@ -551,11 +482,9 @@ impl<R: Read> Read for MetadataBlocksReader<R> {
|
|||||||
loop {
|
loop {
|
||||||
match self.state {
|
match self.state {
|
||||||
ReadState::ReadingSize => {
|
ReadState::ReadingSize => {
|
||||||
let n = self
|
let n = self.reader.read(self.size_buf.unwritten_mut())?;
|
||||||
.reader
|
|
||||||
.read(&mut self.size_buf[self.size_bytes_read..])?;
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
if self.size_bytes_read == 0 {
|
if self.size_buf.written().is_empty() {
|
||||||
self.state = ReadState::Eof;
|
self.state = ReadState::Eof;
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
} else {
|
} else {
|
||||||
@@ -566,56 +495,57 @@ impl<R: Read> Read for MetadataBlocksReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.size_bytes_read += n;
|
self.size_buf.advance(n);
|
||||||
if self.size_bytes_read < 2 {
|
|
||||||
|
if self.size_buf.written().len() < 2 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let size_header = u16::from_le_bytes(self.size_buf);
|
let size_header = u16::from_le_bytes([
|
||||||
let is_compressed = (size_header & 0x8000) == 0;
|
self.size_buf.written()[0],
|
||||||
let size = (size_header & 0x7FFF) as usize;
|
self.size_buf.written()[1],
|
||||||
|
]);
|
||||||
|
self.is_compressed = (size_header & 0x8000) == 0;
|
||||||
|
self.compressed_size = (size_header & 0x7FFF) as usize;
|
||||||
|
|
||||||
if !is_compressed {
|
if self.compressed_size == 0 || self.compressed_size > 8192 {
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::new(
|
||||||
std::io::ErrorKind::InvalidData,
|
std::io::ErrorKind::InvalidData,
|
||||||
"Uncompressed metadata blocks not supported",
|
format!("Invalid metadata block size: {}", self.compressed_size),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if size == 0 || size > 8192 {
|
self.compressed.reset();
|
||||||
return Err(std::io::Error::new(
|
self.size_buf.reset();
|
||||||
std::io::ErrorKind::InvalidData,
|
|
||||||
format!("Invalid metadata block size: {}", size),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.compressed_size = size;
|
|
||||||
self.compressed_pos = 0;
|
|
||||||
self.size_bytes_read = 0;
|
|
||||||
self.state = ReadState::ReadingData;
|
self.state = ReadState::ReadingData;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadState::ReadingData => {
|
ReadState::ReadingData => {
|
||||||
let n = self
|
let n = self.reader.read(self.compressed.unwritten_mut())?;
|
||||||
.reader
|
|
||||||
.read(&mut self.compressed[self.compressed_pos..self.compressed_size])?;
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::new(
|
||||||
std::io::ErrorKind::UnexpectedEof,
|
std::io::ErrorKind::UnexpectedEof,
|
||||||
"Unexpected EOF reading compressed data",
|
"Unexpected EOF reading data",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.compressed_pos += n;
|
self.compressed.advance(n);
|
||||||
if self.compressed_pos < self.compressed_size {
|
|
||||||
|
if !self.compressed.unwritten().is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.zstd = Some(ZstdDecoder::new());
|
|
||||||
self.output_pos = 0;
|
self.output_pos = 0;
|
||||||
self.output.reset();
|
self.output.reset();
|
||||||
self.state = ReadState::Decompressing;
|
if self.is_compressed {
|
||||||
|
self.zstd = Some(ZstdDecoder::new());
|
||||||
|
self.state = ReadState::Decompressing;
|
||||||
|
} else {
|
||||||
|
self.output
|
||||||
|
.copy_unwritten_from(&mut PartialBuffer::new(self.compressed.written()));
|
||||||
|
self.state = ReadState::Outputting;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,7 +555,7 @@ impl<R: Read> Read for MetadataBlocksReader<R> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut input = PartialBuffer::new(&self.compressed[..self.compressed_size]);
|
let mut input = PartialBuffer::new(self.compressed.written());
|
||||||
let decoder = self.zstd.as_mut().unwrap();
|
let decoder = self.zstd.as_mut().unwrap();
|
||||||
|
|
||||||
if decoder.decode(&mut input, &mut self.output)? {
|
if decoder.decode(&mut input, &mut self.output)? {
|
||||||
@@ -676,13 +606,13 @@ impl<R: AsyncRead> AsyncRead for MetadataBlocksReader<R> {
|
|||||||
|
|
||||||
match *this.state {
|
match *this.state {
|
||||||
ReadState::ReadingSize => {
|
ReadState::ReadingSize => {
|
||||||
let mut read_buf =
|
let mut read_buf = tokio::io::ReadBuf::new(this.size_buf.unwritten_mut());
|
||||||
tokio::io::ReadBuf::new(&mut this.size_buf[*this.size_bytes_read..]);
|
let before = read_buf.filled().len();
|
||||||
ready!(this.reader.as_mut().poll_read(cx, &mut read_buf))?;
|
ready!(this.reader.as_mut().poll_read(cx, &mut read_buf))?;
|
||||||
|
let n = read_buf.filled().len() - before;
|
||||||
|
|
||||||
let n = read_buf.filled().len();
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
if *this.size_bytes_read == 0 {
|
if this.size_buf.written().is_empty() {
|
||||||
*this.state = ReadState::Eof;
|
*this.state = ReadState::Eof;
|
||||||
return Poll::Ready(Ok(()));
|
return Poll::Ready(Ok(()));
|
||||||
} else {
|
} else {
|
||||||
@@ -693,22 +623,16 @@ impl<R: AsyncRead> AsyncRead for MetadataBlocksReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*this.size_bytes_read += n;
|
this.size_buf.advance(n);
|
||||||
if *this.size_bytes_read < 2 {
|
|
||||||
|
if this.size_buf.written().len() < 2 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let size_header = u16::from_le_bytes(*this.size_buf);
|
let size_header = u16::from_le_bytes(*this.size_buf.written());
|
||||||
let is_compressed = (size_header & 0x8000) == 0;
|
*this.is_compressed = (size_header & 0x8000) == 0;
|
||||||
let size = (size_header & 0x7FFF) as usize;
|
let size = (size_header & 0x7FFF) as usize;
|
||||||
|
|
||||||
if !is_compressed {
|
|
||||||
return Poll::Ready(Err(std::io::Error::new(
|
|
||||||
std::io::ErrorKind::InvalidData,
|
|
||||||
"Uncompressed metadata blocks not supported",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if size == 0 || size > 8192 {
|
if size == 0 || size > 8192 {
|
||||||
return Poll::Ready(Err(std::io::Error::new(
|
return Poll::Ready(Err(std::io::Error::new(
|
||||||
std::io::ErrorKind::InvalidData,
|
std::io::ErrorKind::InvalidData,
|
||||||
@@ -716,36 +640,42 @@ impl<R: AsyncRead> AsyncRead for MetadataBlocksReader<R> {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
*this.compressed_size = size;
|
this.compressed.reset();
|
||||||
*this.compressed_pos = 0;
|
this.compressed.reserve(size);
|
||||||
*this.size_bytes_read = 0;
|
this.size_buf.reset();
|
||||||
*this.state = ReadState::ReadingData;
|
*this.state = ReadState::ReadingData;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadState::ReadingData => {
|
ReadState::ReadingData => {
|
||||||
let mut read_buf = tokio::io::ReadBuf::new(
|
let mut read_buf = tokio::io::ReadBuf::new(this.compressed.unwritten_mut());
|
||||||
&mut this.compressed[*this.compressed_pos..*this.compressed_size],
|
let before = read_buf.filled().len();
|
||||||
);
|
|
||||||
ready!(this.reader.as_mut().poll_read(cx, &mut read_buf))?;
|
ready!(this.reader.as_mut().poll_read(cx, &mut read_buf))?;
|
||||||
|
let n = read_buf.filled().len() - before;
|
||||||
|
|
||||||
let n = read_buf.filled().len();
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return Poll::Ready(Err(std::io::Error::new(
|
return Poll::Ready(Err(std::io::Error::new(
|
||||||
std::io::ErrorKind::UnexpectedEof,
|
std::io::ErrorKind::UnexpectedEof,
|
||||||
"Unexpected EOF reading compressed data",
|
"Unexpected EOF reading data",
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
*this.compressed_pos += n;
|
this.compressed.advance(n);
|
||||||
if *this.compressed_pos < *this.compressed_size {
|
|
||||||
|
if !this.compressed.unwritten().is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
*this.zstd = Some(ZstdDecoder::new());
|
|
||||||
*this.output_pos = 0;
|
*this.output_pos = 0;
|
||||||
this.output.reset();
|
this.output.reset();
|
||||||
*this.state = ReadState::Decompressing;
|
if *this.is_compressed {
|
||||||
|
*this.zstd = Some(ZstdDecoder::new());
|
||||||
|
*this.state = ReadState::Decompressing;
|
||||||
|
} else {
|
||||||
|
this.output
|
||||||
|
.copy_unwritten_from(&mut PartialBuffer::new(this.compressed.written()));
|
||||||
|
*this.state = ReadState::Outputting;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -755,7 +685,7 @@ impl<R: AsyncRead> AsyncRead for MetadataBlocksReader<R> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut input = PartialBuffer::new(&this.compressed[..*this.compressed_size]);
|
let mut input = PartialBuffer::new(this.compressed.written());
|
||||||
let decoder = this.zstd.as_mut().unwrap();
|
let decoder = this.zstd.as_mut().unwrap();
|
||||||
|
|
||||||
if decoder.decode(&mut input, this.output)? {
|
if decoder.decode(&mut input, this.output)? {
|
||||||
|
|||||||
@@ -52,8 +52,11 @@ mod v0_4_0_alpha_9;
|
|||||||
mod v0_4_0_alpha_10;
|
mod v0_4_0_alpha_10;
|
||||||
mod v0_4_0_alpha_11;
|
mod v0_4_0_alpha_11;
|
||||||
mod v0_4_0_alpha_12;
|
mod v0_4_0_alpha_12;
|
||||||
|
mod v0_4_0_alpha_13;
|
||||||
|
mod v0_4_0_alpha_14;
|
||||||
|
mod v0_4_0_alpha_15;
|
||||||
|
|
||||||
pub type Current = v0_4_0_alpha_12::Version; // VERSION_BUMP
|
pub type Current = v0_4_0_alpha_15::Version; // VERSION_BUMP
|
||||||
|
|
||||||
impl Current {
|
impl Current {
|
||||||
#[instrument(skip(self, db))]
|
#[instrument(skip(self, db))]
|
||||||
@@ -167,7 +170,10 @@ enum Version {
|
|||||||
V0_4_0_alpha_9(Wrapper<v0_4_0_alpha_9::Version>),
|
V0_4_0_alpha_9(Wrapper<v0_4_0_alpha_9::Version>),
|
||||||
V0_4_0_alpha_10(Wrapper<v0_4_0_alpha_10::Version>),
|
V0_4_0_alpha_10(Wrapper<v0_4_0_alpha_10::Version>),
|
||||||
V0_4_0_alpha_11(Wrapper<v0_4_0_alpha_11::Version>),
|
V0_4_0_alpha_11(Wrapper<v0_4_0_alpha_11::Version>),
|
||||||
V0_4_0_alpha_12(Wrapper<v0_4_0_alpha_12::Version>), // VERSION_BUMP
|
V0_4_0_alpha_12(Wrapper<v0_4_0_alpha_12::Version>),
|
||||||
|
V0_4_0_alpha_13(Wrapper<v0_4_0_alpha_13::Version>),
|
||||||
|
V0_4_0_alpha_14(Wrapper<v0_4_0_alpha_14::Version>),
|
||||||
|
V0_4_0_alpha_15(Wrapper<v0_4_0_alpha_15::Version>), // VERSION_BUMP
|
||||||
Other(exver::Version),
|
Other(exver::Version),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +228,10 @@ impl Version {
|
|||||||
Self::V0_4_0_alpha_9(v) => DynVersion(Box::new(v.0)),
|
Self::V0_4_0_alpha_9(v) => DynVersion(Box::new(v.0)),
|
||||||
Self::V0_4_0_alpha_10(v) => DynVersion(Box::new(v.0)),
|
Self::V0_4_0_alpha_10(v) => DynVersion(Box::new(v.0)),
|
||||||
Self::V0_4_0_alpha_11(v) => DynVersion(Box::new(v.0)),
|
Self::V0_4_0_alpha_11(v) => DynVersion(Box::new(v.0)),
|
||||||
Self::V0_4_0_alpha_12(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
Self::V0_4_0_alpha_12(v) => DynVersion(Box::new(v.0)),
|
||||||
|
Self::V0_4_0_alpha_13(v) => DynVersion(Box::new(v.0)),
|
||||||
|
Self::V0_4_0_alpha_14(v) => DynVersion(Box::new(v.0)),
|
||||||
|
Self::V0_4_0_alpha_15(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||||
Self::Other(v) => {
|
Self::Other(v) => {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("unknown version {v}"),
|
eyre!("unknown version {v}"),
|
||||||
@@ -269,7 +278,10 @@ impl Version {
|
|||||||
Version::V0_4_0_alpha_9(Wrapper(x)) => x.semver(),
|
Version::V0_4_0_alpha_9(Wrapper(x)) => x.semver(),
|
||||||
Version::V0_4_0_alpha_10(Wrapper(x)) => x.semver(),
|
Version::V0_4_0_alpha_10(Wrapper(x)) => x.semver(),
|
||||||
Version::V0_4_0_alpha_11(Wrapper(x)) => x.semver(),
|
Version::V0_4_0_alpha_11(Wrapper(x)) => x.semver(),
|
||||||
Version::V0_4_0_alpha_12(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
Version::V0_4_0_alpha_12(Wrapper(x)) => x.semver(),
|
||||||
|
Version::V0_4_0_alpha_13(Wrapper(x)) => x.semver(),
|
||||||
|
Version::V0_4_0_alpha_14(Wrapper(x)) => x.semver(),
|
||||||
|
Version::V0_4_0_alpha_15(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||||
Version::Other(x) => x.clone(),
|
Version::Other(x) => x.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use exver::{PreReleaseSegment, VersionRange};
|
|||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
|
|
||||||
use super::v0_3_5::V0_3_0_COMPAT;
|
use super::v0_3_5::V0_3_0_COMPAT;
|
||||||
use super::{VersionT, v0_4_0_alpha_11};
|
use super::{v0_4_0_alpha_11, VersionT};
|
||||||
use crate::net::tor::TorSecretKey;
|
use crate::net::tor::TorSecretKey;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
@@ -75,7 +75,10 @@ impl VersionT for Version {
|
|||||||
}
|
}
|
||||||
fix_host(&mut db["public"]["serverInfo"]["network"]["host"])?;
|
fix_host(&mut db["public"]["serverInfo"]["network"]["host"])?;
|
||||||
|
|
||||||
db["private"]["keyStore"]["localCerts"] = db["private"]["keyStore"]["local_certs"].clone();
|
if db["private"]["keyStore"]["localCerts"].is_null() {
|
||||||
|
db["private"]["keyStore"]["localCerts"] =
|
||||||
|
db["private"]["keyStore"]["local_certs"].clone();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Value::Null)
|
Ok(Value::Null)
|
||||||
}
|
}
|
||||||
|
|||||||
37
core/startos/src/version/v0_4_0_alpha_13.rs
Normal file
37
core/startos/src/version/v0_4_0_alpha_13.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use exver::{PreReleaseSegment, VersionRange};
|
||||||
|
|
||||||
|
use super::v0_3_5::V0_3_0_COMPAT;
|
||||||
|
use super::{VersionT, v0_4_0_alpha_12};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref V0_4_0_alpha_13: exver::Version = exver::Version::new(
|
||||||
|
[0, 4, 0],
|
||||||
|
[PreReleaseSegment::String("alpha".into()), 13.into()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Version;
|
||||||
|
|
||||||
|
impl VersionT for Version {
|
||||||
|
type Previous = v0_4_0_alpha_12::Version;
|
||||||
|
type PreUpRes = ();
|
||||||
|
|
||||||
|
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn semver(self) -> exver::Version {
|
||||||
|
V0_4_0_alpha_13.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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
37
core/startos/src/version/v0_4_0_alpha_14.rs
Normal file
37
core/startos/src/version/v0_4_0_alpha_14.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use exver::{PreReleaseSegment, VersionRange};
|
||||||
|
|
||||||
|
use super::v0_3_5::V0_3_0_COMPAT;
|
||||||
|
use super::{VersionT, v0_4_0_alpha_13};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref V0_4_0_alpha_14: exver::Version = exver::Version::new(
|
||||||
|
[0, 4, 0],
|
||||||
|
[PreReleaseSegment::String("alpha".into()), 14.into()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Version;
|
||||||
|
|
||||||
|
impl VersionT for Version {
|
||||||
|
type Previous = v0_4_0_alpha_13::Version;
|
||||||
|
type PreUpRes = ();
|
||||||
|
|
||||||
|
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn semver(self) -> exver::Version {
|
||||||
|
V0_4_0_alpha_14.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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
37
core/startos/src/version/v0_4_0_alpha_15.rs
Normal file
37
core/startos/src/version/v0_4_0_alpha_15.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use exver::{PreReleaseSegment, VersionRange};
|
||||||
|
|
||||||
|
use super::v0_3_5::V0_3_0_COMPAT;
|
||||||
|
use super::{VersionT, v0_4_0_alpha_14};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref V0_4_0_alpha_15: exver::Version = exver::Version::new(
|
||||||
|
[0, 4, 0],
|
||||||
|
[PreReleaseSegment::String("alpha".into()), 15.into()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Version;
|
||||||
|
|
||||||
|
impl VersionT for Version {
|
||||||
|
type Previous = v0_4_0_alpha_14::Version;
|
||||||
|
type PreUpRes = ();
|
||||||
|
|
||||||
|
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn semver(self) -> exver::Version {
|
||||||
|
V0_4_0_alpha_15.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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
1
debian/startos/postinst
vendored
1
debian/startos/postinst
vendored
@@ -122,7 +122,6 @@ ln -sf /usr/lib/startos/scripts/wireguard-vps-proxy-setup /usr/bin/wireguard-vps
|
|||||||
|
|
||||||
echo "fs.inotify.max_user_watches=1048576" > /etc/sysctl.d/97-startos.conf
|
echo "fs.inotify.max_user_watches=1048576" > /etc/sysctl.d/97-startos.conf
|
||||||
|
|
||||||
locale-gen en_US.UTF-8
|
|
||||||
dpkg-reconfigure --frontend noninteractive locales
|
dpkg-reconfigure --frontend noninteractive locales
|
||||||
|
|
||||||
if ! getent group | grep '^startos:'; then
|
if ! getent group | grep '^startos:'; then
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ find . -type f -not -path "./DEBIAN/*" -exec md5sum {} \; | sort -k 2 | sed 's/\
|
|||||||
cd ../..
|
cd ../..
|
||||||
|
|
||||||
cd dpkg-workdir
|
cd dpkg-workdir
|
||||||
dpkg-deb --root-owner-group -b $BASENAME
|
dpkg-deb --root-owner-group -Zzstd -b $BASENAME
|
||||||
mkdir -p ../results
|
mkdir -p ../results
|
||||||
mv $BASENAME.deb ../results/$BASENAME.deb
|
mv $BASENAME.deb ../results/$BASENAME.deb
|
||||||
rm -rf $BASENAME
|
rm -rf $BASENAME
|
||||||
@@ -205,6 +205,7 @@ fi
|
|||||||
useradd --shell /bin/bash -G startos -m start9
|
useradd --shell /bin/bash -G startos -m start9
|
||||||
echo start9:embassy | chpasswd
|
echo start9:embassy | chpasswd
|
||||||
usermod -aG sudo start9
|
usermod -aG sudo start9
|
||||||
|
usermod -aG systemd-journal start9
|
||||||
|
|
||||||
echo "start9 ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee "/etc/sudoers.d/010_start9-nopasswd"
|
echo "start9 ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee "/etc/sudoers.d/010_start9-nopasswd"
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ BASEDIR="$(pwd -P)"
|
|||||||
|
|
||||||
SUITE=trixie
|
SUITE=trixie
|
||||||
|
|
||||||
|
USE_TTY=
|
||||||
|
if tty -s; then
|
||||||
|
USE_TTY="-it"
|
||||||
|
fi
|
||||||
|
|
||||||
dockerfile_hash=$(sha256sum ${BASEDIR}/image-recipe/Dockerfile | head -c 7)
|
dockerfile_hash=$(sha256sum ${BASEDIR}/image-recipe/Dockerfile | head -c 7)
|
||||||
|
|
||||||
docker_img_name="startos_build:${SUITE}-${dockerfile_hash}"
|
docker_img_name="startos_build:${SUITE}-${dockerfile_hash}"
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { GatewayId } from "./GatewayId"
|
|
||||||
|
|
||||||
export type ForgetGatewayParams = { gateway: GatewayId }
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { GatewayId } from "./GatewayId"
|
|
||||||
|
|
||||||
export type NetworkInterfaceSetPublicParams = {
|
|
||||||
gateway: GatewayId
|
|
||||||
public: boolean | null
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { GatewayId } from "./GatewayId"
|
|
||||||
|
|
||||||
export type RenameGatewayParams = { id: GatewayId; name: string }
|
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { AnyVerifyingKey } from "./AnyVerifyingKey"
|
||||||
|
import type { ContactInfo } from "./ContactInfo"
|
||||||
|
|
||||||
export type SignerInfo = { name: string }
|
export type SignerInfo = {
|
||||||
|
name: string
|
||||||
|
contact: Array<ContactInfo>
|
||||||
|
keys: Array<AnyVerifyingKey>
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { GatewayId } from "./GatewayId"
|
|
||||||
|
|
||||||
export type UnsetPublicParams = { gateway: GatewayId }
|
|
||||||
@@ -76,7 +76,6 @@ export { EventId } from "./EventId"
|
|||||||
export { ExportActionParams } from "./ExportActionParams"
|
export { ExportActionParams } from "./ExportActionParams"
|
||||||
export { ExportServiceInterfaceParams } from "./ExportServiceInterfaceParams"
|
export { ExportServiceInterfaceParams } from "./ExportServiceInterfaceParams"
|
||||||
export { FileType } from "./FileType"
|
export { FileType } from "./FileType"
|
||||||
export { ForgetGatewayParams } from "./ForgetGatewayParams"
|
|
||||||
export { FullIndex } from "./FullIndex"
|
export { FullIndex } from "./FullIndex"
|
||||||
export { FullProgress } from "./FullProgress"
|
export { FullProgress } from "./FullProgress"
|
||||||
export { GatewayId } from "./GatewayId"
|
export { GatewayId } from "./GatewayId"
|
||||||
@@ -143,7 +142,6 @@ export { NamedProgress } from "./NamedProgress"
|
|||||||
export { NetInfo } from "./NetInfo"
|
export { NetInfo } from "./NetInfo"
|
||||||
export { NetworkInfo } from "./NetworkInfo"
|
export { NetworkInfo } from "./NetworkInfo"
|
||||||
export { NetworkInterfaceInfo } from "./NetworkInterfaceInfo"
|
export { NetworkInterfaceInfo } from "./NetworkInterfaceInfo"
|
||||||
export { NetworkInterfaceSetPublicParams } from "./NetworkInterfaceSetPublicParams"
|
|
||||||
export { NetworkInterfaceType } from "./NetworkInterfaceType"
|
export { NetworkInterfaceType } from "./NetworkInterfaceType"
|
||||||
export { OnionHostname } from "./OnionHostname"
|
export { OnionHostname } from "./OnionHostname"
|
||||||
export { OsIndex } from "./OsIndex"
|
export { OsIndex } from "./OsIndex"
|
||||||
@@ -175,7 +173,6 @@ export { RemovePackageFromCategoryParams } from "./RemovePackageFromCategoryPara
|
|||||||
export { RemovePackageParams } from "./RemovePackageParams"
|
export { RemovePackageParams } from "./RemovePackageParams"
|
||||||
export { RemoveTunnelParams } from "./RemoveTunnelParams"
|
export { RemoveTunnelParams } from "./RemoveTunnelParams"
|
||||||
export { RemoveVersionParams } from "./RemoveVersionParams"
|
export { RemoveVersionParams } from "./RemoveVersionParams"
|
||||||
export { RenameGatewayParams } from "./RenameGatewayParams"
|
|
||||||
export { ReplayId } from "./ReplayId"
|
export { ReplayId } from "./ReplayId"
|
||||||
export { RequestCommitment } from "./RequestCommitment"
|
export { RequestCommitment } from "./RequestCommitment"
|
||||||
export { RunActionParams } from "./RunActionParams"
|
export { RunActionParams } from "./RunActionParams"
|
||||||
@@ -211,7 +208,6 @@ export { TaskSeverity } from "./TaskSeverity"
|
|||||||
export { TaskTrigger } from "./TaskTrigger"
|
export { TaskTrigger } from "./TaskTrigger"
|
||||||
export { Task } from "./Task"
|
export { Task } from "./Task"
|
||||||
export { TestSmtpParams } from "./TestSmtpParams"
|
export { TestSmtpParams } from "./TestSmtpParams"
|
||||||
export { UnsetPublicParams } from "./UnsetPublicParams"
|
|
||||||
export { UpdatingState } from "./UpdatingState"
|
export { UpdatingState } from "./UpdatingState"
|
||||||
export { VerifyCifsParams } from "./VerifyCifsParams"
|
export { VerifyCifsParams } from "./VerifyCifsParams"
|
||||||
export { VersionSignerParams } from "./VersionSignerParams"
|
export { VersionSignerParams } from "./VersionSignerParams"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { knownProtocols } from "../interfaces/Host"
|
|||||||
import { AddressInfo, Host, Hostname, HostnameInfo } from "../types"
|
import { AddressInfo, Host, Hostname, HostnameInfo } from "../types"
|
||||||
import { Effects } from "../Effects"
|
import { Effects } from "../Effects"
|
||||||
import { DropGenerator, DropPromise } from "./Drop"
|
import { DropGenerator, DropPromise } from "./Drop"
|
||||||
import { IPV6_LINK_LOCAL } from "./ip"
|
import { IpAddress, IPV6_LINK_LOCAL } from "./ip"
|
||||||
|
|
||||||
export type UrlString = string
|
export type UrlString = string
|
||||||
export type HostId = string
|
export type HostId = string
|
||||||
@@ -17,7 +17,15 @@ export const getHostname = (url: string): Hostname | null => {
|
|||||||
return last
|
return last
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterKinds = "onion" | "local" | "domain" | "ip" | "ipv4" | "ipv6"
|
type FilterKinds =
|
||||||
|
| "onion"
|
||||||
|
| "local"
|
||||||
|
| "domain"
|
||||||
|
| "ip"
|
||||||
|
| "ipv4"
|
||||||
|
| "ipv6"
|
||||||
|
| "localhost"
|
||||||
|
| "link-local"
|
||||||
export type Filter = {
|
export type Filter = {
|
||||||
visibility?: "public" | "private"
|
visibility?: "public" | "private"
|
||||||
kind?: FilterKinds | FilterKinds[]
|
kind?: FilterKinds | FilterKinds[]
|
||||||
@@ -72,6 +80,12 @@ type FilterReturnTy<F extends Filter> = F extends {
|
|||||||
: Exclude<HostnameInfo, FilterReturnTy<E>>
|
: Exclude<HostnameInfo, FilterReturnTy<E>>
|
||||||
: HostnameInfo
|
: HostnameInfo
|
||||||
|
|
||||||
|
const defaultFilter = {
|
||||||
|
exclude: {
|
||||||
|
kind: ["localhost", "link-local"] as ("localhost" | "link-local")[],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
type Formats = "hostname-info" | "urlstring" | "url"
|
type Formats = "hostname-info" | "urlstring" | "url"
|
||||||
type FormatReturnTy<
|
type FormatReturnTy<
|
||||||
F extends Filter,
|
F extends Filter,
|
||||||
@@ -92,8 +106,11 @@ export type Filled = {
|
|||||||
sslUrl: UrlString | null
|
sslUrl: UrlString | null
|
||||||
}
|
}
|
||||||
|
|
||||||
filter: <F extends Filter, Format extends Formats = "urlstring">(
|
filter: <
|
||||||
filter: F,
|
F extends Filter = typeof defaultFilter,
|
||||||
|
Format extends Formats = "urlstring",
|
||||||
|
>(
|
||||||
|
filter?: F,
|
||||||
format?: Format,
|
format?: Format,
|
||||||
) => FormatReturnTy<F, Format>[]
|
) => FormatReturnTy<F, Format>[]
|
||||||
|
|
||||||
@@ -215,7 +232,13 @@ function filterRec(
|
|||||||
h.kind === "ip" &&
|
h.kind === "ip" &&
|
||||||
h.hostname.kind === "domain") ||
|
h.hostname.kind === "domain") ||
|
||||||
(kind.has("ipv4") && h.kind === "ip" && h.hostname.kind === "ipv4") ||
|
(kind.has("ipv4") && h.kind === "ip" && h.hostname.kind === "ipv4") ||
|
||||||
(kind.has("ipv6") && h.kind === "ip" && h.hostname.kind === "ipv6")),
|
(kind.has("ipv6") && h.kind === "ip" && h.hostname.kind === "ipv6") ||
|
||||||
|
(kind.has("localhost") &&
|
||||||
|
["localhost", "127.0.0.1", "[::1]"].includes(h.hostname.value)) ||
|
||||||
|
(kind.has("link-local") &&
|
||||||
|
h.kind === "ip" &&
|
||||||
|
h.hostname.kind === "ipv6" &&
|
||||||
|
IPV6_LINK_LOCAL.contains(IpAddress.parse(h.hostname.value)))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,11 +262,14 @@ export const filledAddress = (
|
|||||||
...addressInfo,
|
...addressInfo,
|
||||||
hostnames,
|
hostnames,
|
||||||
toUrls,
|
toUrls,
|
||||||
filter: <F extends Filter, Format extends Formats = "urlstring">(
|
filter: <
|
||||||
filter: F,
|
F extends Filter = typeof defaultFilter,
|
||||||
|
Format extends Formats = "urlstring",
|
||||||
|
>(
|
||||||
|
filter?: F,
|
||||||
format?: Format,
|
format?: Format,
|
||||||
) => {
|
) => {
|
||||||
const filtered = filterRec(hostnames, filter, false)
|
const filtered = filterRec(hostnames, filter ?? defaultFilter, false)
|
||||||
let res: FormatReturnTy<F, Format>[] = filtered as any
|
let res: FormatReturnTy<F, Format>[] = filtered as any
|
||||||
if (format === "hostname-info") return res
|
if (format === "hostname-info") return res
|
||||||
const urls = filtered.flatMap(toUrlArray)
|
const urls = filtered.flatMap(toUrlArray)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ import {
|
|||||||
} from "../../base/lib/inits"
|
} from "../../base/lib/inits"
|
||||||
import { DropGenerator } from "../../base/lib/util/Drop"
|
import { DropGenerator } from "../../base/lib/util/Drop"
|
||||||
|
|
||||||
export const OSVersion = testTypeVersion("0.4.0-alpha.12")
|
export const OSVersion = testTypeVersion("0.4.0-alpha.15")
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
type AnyNeverCond<T extends any[], Then, Else> =
|
type AnyNeverCond<T extends any[], Then, Else> =
|
||||||
|
|||||||
@@ -77,10 +77,14 @@ export class CommandController<
|
|||||||
if (exec.runAsInit) {
|
if (exec.runAsInit) {
|
||||||
childProcess = await subcontainer!.launch(commands, {
|
childProcess = await subcontainer!.launch(commands, {
|
||||||
env: exec.env,
|
env: exec.env,
|
||||||
|
user: exec.user,
|
||||||
|
cwd: exec.cwd,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
childProcess = await subcontainer!.spawn(commands, {
|
childProcess = await subcontainer!.spawn(commands, {
|
||||||
env: exec.env,
|
env: exec.env,
|
||||||
|
user: exec.user,
|
||||||
|
cwd: exec.cwd,
|
||||||
stdio: exec.onStdout || exec.onStderr ? "pipe" : "inherit",
|
stdio: exec.onStdout || exec.onStderr ? "pipe" : "inherit",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -410,12 +410,17 @@ export class SubContainerOwned<
|
|||||||
workdir = options.cwd
|
workdir = options.cwd
|
||||||
delete options.cwd
|
delete options.cwd
|
||||||
}
|
}
|
||||||
|
if (options?.env) {
|
||||||
|
for (let [k, v] of Object.entries(options.env)) {
|
||||||
|
extra.push(`--env=${k}=${v}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
const child = cp.spawn(
|
const child = cp.spawn(
|
||||||
"start-container",
|
"start-container",
|
||||||
[
|
[
|
||||||
"subcontainer",
|
"subcontainer",
|
||||||
"exec",
|
"exec",
|
||||||
`--env=/media/startos/images/${this.imageId}.env`,
|
`--env-file=/media/startos/images/${this.imageId}.env`,
|
||||||
`--user=${user}`,
|
`--user=${user}`,
|
||||||
`--workdir=${workdir}`,
|
`--workdir=${workdir}`,
|
||||||
...extra,
|
...extra,
|
||||||
@@ -530,6 +535,11 @@ export class SubContainerOwned<
|
|||||||
workdir = options.cwd
|
workdir = options.cwd
|
||||||
delete options.cwd
|
delete options.cwd
|
||||||
}
|
}
|
||||||
|
if (options?.env) {
|
||||||
|
for (let [k, v] of Object.entries(options.env)) {
|
||||||
|
extra.push(`--env=${k}=${v}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
await this.killLeader()
|
await this.killLeader()
|
||||||
this.leaderExited = false
|
this.leaderExited = false
|
||||||
this.leader = cp.spawn(
|
this.leader = cp.spawn(
|
||||||
@@ -537,7 +547,7 @@ export class SubContainerOwned<
|
|||||||
[
|
[
|
||||||
"subcontainer",
|
"subcontainer",
|
||||||
"launch",
|
"launch",
|
||||||
`--env=/media/startos/images/${this.imageId}.env`,
|
`--env-file=/media/startos/images/${this.imageId}.env`,
|
||||||
`--user=${user}`,
|
`--user=${user}`,
|
||||||
`--workdir=${workdir}`,
|
`--workdir=${workdir}`,
|
||||||
...extra,
|
...extra,
|
||||||
@@ -574,12 +584,17 @@ export class SubContainerOwned<
|
|||||||
workdir = options.cwd
|
workdir = options.cwd
|
||||||
delete options.cwd
|
delete options.cwd
|
||||||
}
|
}
|
||||||
|
if (options?.env) {
|
||||||
|
for (let [k, v] of Object.entries(options.env)) {
|
||||||
|
extra.push(`--env=${k}=${v}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
return cp.spawn(
|
return cp.spawn(
|
||||||
"start-container",
|
"start-container",
|
||||||
[
|
[
|
||||||
"subcontainer",
|
"subcontainer",
|
||||||
"exec",
|
"exec",
|
||||||
`--env=/media/startos/images/${this.imageId}.env`,
|
`--env-file=/media/startos/images/${this.imageId}.env`,
|
||||||
`--user=${user}`,
|
`--user=${user}`,
|
||||||
`--workdir=${workdir}`,
|
`--workdir=${workdir}`,
|
||||||
...extra,
|
...extra,
|
||||||
|
|||||||
11
sdk/package/package-lock.json
generated
11
sdk/package/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.42",
|
"version": "0.4.0-beta.44",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.42",
|
"version": "0.4.0-beta.44",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
@@ -98,6 +98,7 @@
|
|||||||
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.26.0",
|
"@babel/code-frame": "^7.26.0",
|
||||||
@@ -1643,6 +1644,7 @@
|
|||||||
"integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==",
|
"integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.20.0"
|
"undici-types": "~6.20.0"
|
||||||
}
|
}
|
||||||
@@ -1944,6 +1946,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001669",
|
"caniuse-lite": "^1.0.30001669",
|
||||||
"electron-to-chromium": "^1.5.41",
|
"electron-to-chromium": "^1.5.41",
|
||||||
@@ -3053,6 +3056,7 @@
|
|||||||
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/core": "^29.7.0",
|
"@jest/core": "^29.7.0",
|
||||||
"@jest/types": "^29.6.3",
|
"@jest/types": "^29.6.3",
|
||||||
@@ -4157,6 +4161,7 @@
|
|||||||
"integrity": "sha512-n7chtCbEoGYRwZZ0i/O3t1cPr6o+d9Xx4Zwy2LYfzv0vjchMBU0tO+qYYyvZloBPcgRgzYvALzGWHe609JjEpg==",
|
"integrity": "sha512-n7chtCbEoGYRwZZ0i/O3t1cPr6o+d9Xx4Zwy2LYfzv0vjchMBU0tO+qYYyvZloBPcgRgzYvALzGWHe609JjEpg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^10.0.0",
|
"commander": "^10.0.0",
|
||||||
"source-map-generator": "0.8.0"
|
"source-map-generator": "0.8.0"
|
||||||
@@ -4833,6 +4838,7 @@
|
|||||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cspotcode/source-map-support": "^0.8.0",
|
"@cspotcode/source-map-support": "^0.8.0",
|
||||||
"@tsconfig/node10": "^1.0.7",
|
"@tsconfig/node10": "^1.0.7",
|
||||||
@@ -4953,6 +4959,7 @@
|
|||||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.42",
|
"version": "0.4.0-beta.44",
|
||||||
"description": "Software development kit to facilitate packaging services for StartOS",
|
"description": "Software development kit to facilitate packaging services for StartOS",
|
||||||
"main": "./package/lib/index.js",
|
"main": "./package/lib/index.js",
|
||||||
"types": "./package/lib/index.d.ts",
|
"types": "./package/lib/index.d.ts",
|
||||||
|
|||||||
158
web/package-lock.json
generated
158
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "startos-ui",
|
"name": "startos-ui",
|
||||||
"version": "0.4.0-alpha.12",
|
"version": "0.4.0-alpha.15",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "startos-ui",
|
"name": "startos-ui",
|
||||||
"version": "0.4.0-alpha.12",
|
"version": "0.4.0-alpha.15",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^20.3.0",
|
"@angular/animations": "^20.3.0",
|
||||||
@@ -25,18 +25,18 @@
|
|||||||
"@noble/hashes": "^1.4.0",
|
"@noble/hashes": "^1.4.0",
|
||||||
"@start9labs/argon2": "^0.3.0",
|
"@start9labs/argon2": "^0.3.0",
|
||||||
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
||||||
"@taiga-ui/addon-charts": "4.55.0",
|
"@taiga-ui/addon-charts": "4.62.0",
|
||||||
"@taiga-ui/addon-commerce": "4.55.0",
|
"@taiga-ui/addon-commerce": "4.62.0",
|
||||||
"@taiga-ui/addon-mobile": "4.55.0",
|
"@taiga-ui/addon-mobile": "4.62.0",
|
||||||
"@taiga-ui/addon-table": "4.55.0",
|
"@taiga-ui/addon-table": "4.62.0",
|
||||||
"@taiga-ui/cdk": "4.55.0",
|
"@taiga-ui/cdk": "4.62.0",
|
||||||
"@taiga-ui/core": "4.55.0",
|
"@taiga-ui/core": "4.62.0",
|
||||||
"@taiga-ui/dompurify": "4.1.11",
|
"@taiga-ui/dompurify": "4.1.11",
|
||||||
"@taiga-ui/event-plugins": "4.7.0",
|
"@taiga-ui/event-plugins": "4.7.0",
|
||||||
"@taiga-ui/experimental": "4.55.0",
|
"@taiga-ui/experimental": "4.62.0",
|
||||||
"@taiga-ui/icons": "4.55.0",
|
"@taiga-ui/icons": "4.62.0",
|
||||||
"@taiga-ui/kit": "4.55.0",
|
"@taiga-ui/kit": "4.62.0",
|
||||||
"@taiga-ui/layout": "4.55.0",
|
"@taiga-ui/layout": "4.62.0",
|
||||||
"@taiga-ui/polymorpheus": "4.9.0",
|
"@taiga-ui/polymorpheus": "4.9.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
@@ -3950,9 +3950,9 @@
|
|||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-charts": {
|
"node_modules/@taiga-ui/addon-charts": {
|
||||||
"version": "4.55.0",
|
"version": "4.62.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.62.0.tgz",
|
||||||
"integrity": "sha512-Rwgcc7NaLm75GsHYhqbO4jgNhD1bGbA7Kk0sNFE+Tgz9+V3ARXMuBw7C3cU9UxLSFnOsXz9RYLosmZ3jAVlyuQ==",
|
"integrity": "sha512-tCysUpzEHwRhK/p9hopkt0Jw4jcgA2cF8CYK8mDntghC+fNLnqCVUcrqFIC5plGabAo00WMEz+X+KyGvwvKaVg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
@@ -3961,15 +3961,15 @@
|
|||||||
"@angular/common": ">=16.0.0",
|
"@angular/common": ">=16.0.0",
|
||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@ng-web-apis/common": "^4.12.0",
|
"@ng-web-apis/common": "^4.12.0",
|
||||||
"@taiga-ui/cdk": "^4.55.0",
|
"@taiga-ui/cdk": "^4.62.0",
|
||||||
"@taiga-ui/core": "^4.55.0",
|
"@taiga-ui/core": "^4.62.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0"
|
"@taiga-ui/polymorpheus": "^4.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-commerce": {
|
"node_modules/@taiga-ui/addon-commerce": {
|
||||||
"version": "4.55.0",
|
"version": "4.62.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.62.0.tgz",
|
||||||
"integrity": "sha512-eOOBkIJSsagtRkpRZ04xlL8ePIP01d4Xo264zSTg2SRxD6vwR/7/QJlf9108BvIJv/jfTpmFukLwSB9LazqmCw==",
|
"integrity": "sha512-J4+bdHeDe2d7Uh8NNObLl4LzBhWLCdzxNHXPac1bMGB+3gX751Htc9px37FkVZlQGnxQATCbxAVXj7Zjveq/QQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3979,22 +3979,22 @@
|
|||||||
"@angular/common": ">=16.0.0",
|
"@angular/common": ">=16.0.0",
|
||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@angular/forms": ">=16.0.0",
|
"@angular/forms": ">=16.0.0",
|
||||||
"@maskito/angular": "^3.10.3",
|
"@maskito/angular": "^3.11.1",
|
||||||
"@maskito/core": "^3.10.3",
|
"@maskito/core": "^3.11.1",
|
||||||
"@maskito/kit": "^3.10.3",
|
"@maskito/kit": "^3.11.1",
|
||||||
"@ng-web-apis/common": "^4.12.0",
|
"@ng-web-apis/common": "^4.12.0",
|
||||||
"@taiga-ui/cdk": "^4.55.0",
|
"@taiga-ui/cdk": "^4.62.0",
|
||||||
"@taiga-ui/core": "^4.55.0",
|
"@taiga-ui/core": "^4.62.0",
|
||||||
"@taiga-ui/i18n": "^4.55.0",
|
"@taiga-ui/i18n": "^4.62.0",
|
||||||
"@taiga-ui/kit": "^4.55.0",
|
"@taiga-ui/kit": "^4.62.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-mobile": {
|
"node_modules/@taiga-ui/addon-mobile": {
|
||||||
"version": "4.55.0",
|
"version": "4.62.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.62.0.tgz",
|
||||||
"integrity": "sha512-NQRozVKcXLs9/rd/s1yI7T5rIokzwHQ5IN/c3NLBUEka1iKUr1ZTch+g9CHJf8GTVB0uAwWKNCgX5LxtiSI5zg==",
|
"integrity": "sha512-seIBG4utgLq2xDJu+YDzksOsVi/V6vsTbm2bljgM1fIBZInbhqk95YOIFZDU9JXT1/vIShcqetavg1vHD1wdkQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
@@ -4004,18 +4004,18 @@
|
|||||||
"@angular/common": ">=16.0.0",
|
"@angular/common": ">=16.0.0",
|
||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@ng-web-apis/common": "^4.12.0",
|
"@ng-web-apis/common": "^4.12.0",
|
||||||
"@taiga-ui/cdk": "^4.55.0",
|
"@taiga-ui/cdk": "^4.62.0",
|
||||||
"@taiga-ui/core": "^4.55.0",
|
"@taiga-ui/core": "^4.62.0",
|
||||||
"@taiga-ui/kit": "^4.55.0",
|
"@taiga-ui/kit": "^4.62.0",
|
||||||
"@taiga-ui/layout": "^4.55.0",
|
"@taiga-ui/layout": "^4.62.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-table": {
|
"node_modules/@taiga-ui/addon-table": {
|
||||||
"version": "4.55.0",
|
"version": "4.62.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.62.0.tgz",
|
||||||
"integrity": "sha512-K5qpOS0UQLDqruJXPNDic8scCRvO3oAN/ZCPQ9XOGDrcvydvo4AKUwjKRPj+pZ/z0ulxbwAAruFFFCvRNnbzaA==",
|
"integrity": "sha512-0rolnsO1puYwUK17si5OOpzFxiziS6/OSbpLOSKrVrMkCgsWCoNDvpgPIwtwS5Mq3iF5cwLfUPbDQM8saG7wxQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
@@ -4024,18 +4024,18 @@
|
|||||||
"@angular/common": ">=16.0.0",
|
"@angular/common": ">=16.0.0",
|
||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@ng-web-apis/intersection-observer": "^4.12.0",
|
"@ng-web-apis/intersection-observer": "^4.12.0",
|
||||||
"@taiga-ui/cdk": "^4.55.0",
|
"@taiga-ui/cdk": "^4.62.0",
|
||||||
"@taiga-ui/core": "^4.55.0",
|
"@taiga-ui/core": "^4.62.0",
|
||||||
"@taiga-ui/i18n": "^4.55.0",
|
"@taiga-ui/i18n": "^4.62.0",
|
||||||
"@taiga-ui/kit": "^4.55.0",
|
"@taiga-ui/kit": "^4.62.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/cdk": {
|
"node_modules/@taiga-ui/cdk": {
|
||||||
"version": "4.55.0",
|
"version": "4.62.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.62.0.tgz",
|
||||||
"integrity": "sha512-vA5nGyx+YIHR1xZeq5D9gSqTRQg74qhe1AOt5FlqFOC0P4LvmLkNg3De7AeahXALNSeRz/DYcqI7WuGo6xpcLQ==",
|
"integrity": "sha512-KWPXEbCHtRp7aIet1L3PySdXpo5Aay4L/36jDzjiFZ/bcbuD2cY/3S2l68zpgv6ZksZA94DuCuaamSEwQIAtPw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4065,9 +4065,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/core": {
|
"node_modules/@taiga-ui/core": {
|
||||||
"version": "4.55.0",
|
"version": "4.62.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.62.0.tgz",
|
||||||
"integrity": "sha512-Z2ATVNmEAlHEk2cgs/tnS6qZML87IchkPDeRl6HQfBT2fjYVjh1oCzXL07t86Lv6tpvkllyUVqoBCTSvDXs9kA==",
|
"integrity": "sha512-PQW10hFH50g8PgnJpPa/ZrGMWljhIsBHad/utvalmlv8wXQY24i8T1BjrGIOFPOjzs20NEwLOICHf7KdZUtiuA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4082,9 +4082,9 @@
|
|||||||
"@angular/router": ">=16.0.0",
|
"@angular/router": ">=16.0.0",
|
||||||
"@ng-web-apis/common": "^4.12.0",
|
"@ng-web-apis/common": "^4.12.0",
|
||||||
"@ng-web-apis/mutation-observer": "^4.12.0",
|
"@ng-web-apis/mutation-observer": "^4.12.0",
|
||||||
"@taiga-ui/cdk": "^4.55.0",
|
"@taiga-ui/cdk": "^4.62.0",
|
||||||
"@taiga-ui/event-plugins": "^4.7.0",
|
"@taiga-ui/event-plugins": "^4.7.0",
|
||||||
"@taiga-ui/i18n": "^4.55.0",
|
"@taiga-ui/i18n": "^4.62.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
@@ -4120,9 +4120,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/experimental": {
|
"node_modules/@taiga-ui/experimental": {
|
||||||
"version": "4.55.0",
|
"version": "4.62.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-4.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-4.62.0.tgz",
|
||||||
"integrity": "sha512-3zq2BTl+fE/N/tEr74TzWbyzT5OoS9YEApKobJehKivW5XxZmF/MDRWp45kSe4jDtVbJ2ueI0Jn8h0BDNykkcg==",
|
"integrity": "sha512-EiL5wJ+9LSf0BfZcFX6ioCavLfx26v0BCOUXh52Rtczp85Uh2qTDt2feM0oBDB+0Kj74/+wqqiKi+s3B8ZV3WA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": ">=2.8.1"
|
"tslib": ">=2.8.1"
|
||||||
@@ -4130,19 +4130,19 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": ">=16.0.0",
|
"@angular/common": ">=16.0.0",
|
||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@taiga-ui/addon-commerce": "^4.55.0",
|
"@taiga-ui/addon-commerce": "^4.62.0",
|
||||||
"@taiga-ui/cdk": "^4.55.0",
|
"@taiga-ui/cdk": "^4.62.0",
|
||||||
"@taiga-ui/core": "^4.55.0",
|
"@taiga-ui/core": "^4.62.0",
|
||||||
"@taiga-ui/kit": "^4.55.0",
|
"@taiga-ui/kit": "^4.62.0",
|
||||||
"@taiga-ui/layout": "^4.55.0",
|
"@taiga-ui/layout": "^4.62.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/i18n": {
|
"node_modules/@taiga-ui/i18n": {
|
||||||
"version": "4.59.0",
|
"version": "4.62.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.62.0.tgz",
|
||||||
"integrity": "sha512-IxPzqkORJlSqagvUdmqBL9fuvfRm/Ca/W/bCDEK2GN/4QtSZ0yFzAyQdWduoIJubqyEPMXRbXZGc7WBtDgAMIQ==",
|
"integrity": "sha512-84hD1nI26EAYd5RUhFKxbg+8WKYhc0GBHyf8wfi15xuwaT6oh2gbJx7pNTlGN3klH4CeDB9HF998tkhieevqQw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4155,18 +4155,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/icons": {
|
"node_modules/@taiga-ui/icons": {
|
||||||
"version": "4.55.0",
|
"version": "4.62.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.62.0.tgz",
|
||||||
"integrity": "sha512-sYqSG9wgUcwHBrDRnMhLCMEkvAAw/SZrvvq0jdY2oWGmKwAj/6WBt+wA+jnFkDDKEZ7mjzdPIQffpVaUjSwsiw==",
|
"integrity": "sha512-vD+bJk3Wot/+NcbdPwAJGBnqXG6T1OJVeg2IkaEE6DBixwdwDpukZWiV9asXyXiJkyEpG2Ar7SASvdCYZEVlxw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/kit": {
|
"node_modules/@taiga-ui/kit": {
|
||||||
"version": "4.55.0",
|
"version": "4.62.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.62.0.tgz",
|
||||||
"integrity": "sha512-xTvi7viI+wI2ifPv2bsf8prhYWWS4g1lbx059jXV3f5Cttc0Xg6DEb6xpaQOf4loBkcrP2FzkA4njACUuiouzw==",
|
"integrity": "sha512-tdEaXJTks1PZQJAwMiVQTZrtCpaLIYV6T9VdVPZUKAJXq7K6J2kcD0oIISjwE9rqgLVwqytMZrwHx1nSRzkb/A==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4177,25 +4177,25 @@
|
|||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@angular/forms": ">=16.0.0",
|
"@angular/forms": ">=16.0.0",
|
||||||
"@angular/router": ">=16.0.0",
|
"@angular/router": ">=16.0.0",
|
||||||
"@maskito/angular": "^3.10.3",
|
"@maskito/angular": "^3.11.1",
|
||||||
"@maskito/core": "^3.10.3",
|
"@maskito/core": "^3.11.1",
|
||||||
"@maskito/kit": "^3.10.3",
|
"@maskito/kit": "^3.11.1",
|
||||||
"@maskito/phone": "^3.10.3",
|
"@maskito/phone": "^3.11.1",
|
||||||
"@ng-web-apis/common": "^4.12.0",
|
"@ng-web-apis/common": "^4.12.0",
|
||||||
"@ng-web-apis/intersection-observer": "^4.12.0",
|
"@ng-web-apis/intersection-observer": "^4.12.0",
|
||||||
"@ng-web-apis/mutation-observer": "^4.12.0",
|
"@ng-web-apis/mutation-observer": "^4.12.0",
|
||||||
"@ng-web-apis/resize-observer": "^4.12.0",
|
"@ng-web-apis/resize-observer": "^4.12.0",
|
||||||
"@taiga-ui/cdk": "^4.55.0",
|
"@taiga-ui/cdk": "^4.62.0",
|
||||||
"@taiga-ui/core": "^4.55.0",
|
"@taiga-ui/core": "^4.62.0",
|
||||||
"@taiga-ui/i18n": "^4.55.0",
|
"@taiga-ui/i18n": "^4.62.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/layout": {
|
"node_modules/@taiga-ui/layout": {
|
||||||
"version": "4.55.0",
|
"version": "4.62.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.62.0.tgz",
|
||||||
"integrity": "sha512-C+e4gudZwjIc46VITil5vySas1FPpfe+D4uwLRggJOTuUosZlZHBuc51v91wCCc0pL0Xfu+TD0s8W9kRd1sQHA==",
|
"integrity": "sha512-xd8eLLeR5FE3RhnVMGl1QlC3JXXJLsLAAASpBf9DQsTt+YBBl8BQt/cXGbBcJecC2mJLZlS6zytSkMTHY7VAhw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4204,9 +4204,9 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": ">=16.0.0",
|
"@angular/common": ">=16.0.0",
|
||||||
"@angular/core": ">=16.0.0",
|
"@angular/core": ">=16.0.0",
|
||||||
"@taiga-ui/cdk": "^4.55.0",
|
"@taiga-ui/cdk": "^4.62.0",
|
||||||
"@taiga-ui/core": "^4.55.0",
|
"@taiga-ui/core": "^4.62.0",
|
||||||
"@taiga-ui/kit": "^4.55.0",
|
"@taiga-ui/kit": "^4.62.0",
|
||||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "startos-ui",
|
"name": "startos-ui",
|
||||||
"version": "0.4.0-alpha.12",
|
"version": "0.4.0-alpha.15",
|
||||||
"author": "Start9 Labs, Inc",
|
"author": "Start9 Labs, Inc",
|
||||||
"homepage": "https://start9.com/",
|
"homepage": "https://start9.com/",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -49,18 +49,18 @@
|
|||||||
"@noble/hashes": "^1.4.0",
|
"@noble/hashes": "^1.4.0",
|
||||||
"@start9labs/argon2": "^0.3.0",
|
"@start9labs/argon2": "^0.3.0",
|
||||||
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
||||||
"@taiga-ui/addon-charts": "4.55.0",
|
"@taiga-ui/addon-charts": "4.62.0",
|
||||||
"@taiga-ui/addon-commerce": "4.55.0",
|
"@taiga-ui/addon-commerce": "4.62.0",
|
||||||
"@taiga-ui/addon-mobile": "4.55.0",
|
"@taiga-ui/addon-mobile": "4.62.0",
|
||||||
"@taiga-ui/addon-table": "4.55.0",
|
"@taiga-ui/addon-table": "4.62.0",
|
||||||
"@taiga-ui/cdk": "4.55.0",
|
"@taiga-ui/cdk": "4.62.0",
|
||||||
"@taiga-ui/core": "4.55.0",
|
"@taiga-ui/core": "4.62.0",
|
||||||
"@taiga-ui/dompurify": "4.1.11",
|
"@taiga-ui/dompurify": "4.1.11",
|
||||||
"@taiga-ui/event-plugins": "4.7.0",
|
"@taiga-ui/event-plugins": "4.7.0",
|
||||||
"@taiga-ui/experimental": "4.55.0",
|
"@taiga-ui/experimental": "4.62.0",
|
||||||
"@taiga-ui/icons": "4.55.0",
|
"@taiga-ui/icons": "4.62.0",
|
||||||
"@taiga-ui/kit": "4.55.0",
|
"@taiga-ui/kit": "4.62.0",
|
||||||
"@taiga-ui/layout": "4.55.0",
|
"@taiga-ui/layout": "4.62.0",
|
||||||
"@taiga-ui/polymorpheus": "4.9.0",
|
"@taiga-ui/polymorpheus": "4.9.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ export class AppComponent {
|
|||||||
await this.api.reboot()
|
await this.api.reboot()
|
||||||
this.dialogs
|
this.dialogs
|
||||||
.open(
|
.open(
|
||||||
'Please wait 1-2 minutes, then refresh this page to access the StartOS setup wizard.',
|
window.location.host === 'localhost'
|
||||||
|
? 'Please wait 1-2 minutes for your server to restart'
|
||||||
|
: 'Please wait 1-2 minutes, then refresh this page to access the StartOS setup wizard.',
|
||||||
{
|
{
|
||||||
label: 'Rebooting',
|
label: 'Rebooting',
|
||||||
size: 's',
|
size: 's',
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ import { MarketplaceItemComponent } from './item.component'
|
|||||||
|
|
||||||
<div class="background-border box-shadow-lg shadow-color-light">
|
<div class="background-border box-shadow-lg shadow-color-light">
|
||||||
<div class="box-container">
|
<div class="box-container">
|
||||||
|
<h2 class="additional-detail-title">{{ 'Description' | i18n }}</h2>
|
||||||
<p [innerHTML]="pkg().description.long"></p>
|
<p [innerHTML]="pkg().description.long"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ import { MarketplacePkgBase } from '../../../types'
|
|||||||
selector: 'marketplace-dep-item',
|
selector: 'marketplace-dep-item',
|
||||||
template: `
|
template: `
|
||||||
<div class="outer-container">
|
<div class="outer-container">
|
||||||
<tui-avatar class="dep-img" size="l" [src]="getImage(dep.key)" />
|
<tui-avatar
|
||||||
|
appearance="action-grayscale"
|
||||||
|
class="dep-img"
|
||||||
|
size="l"
|
||||||
|
[src]="getImage(dep.key)"
|
||||||
|
/>
|
||||||
<div>
|
<div>
|
||||||
<tui-line-clamp [linesLimit]="2" [content]="titleContent" />
|
<tui-line-clamp [linesLimit]="2" [content]="titleContent" />
|
||||||
<ng-template #titleContent>
|
<ng-template #titleContent>
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ import { MarketplacePkg } from '../../types'
|
|||||||
[queryParams]="{ id: pkg.id, flavor: pkg.flavor }"
|
[queryParams]="{ id: pkg.id, flavor: pkg.flavor }"
|
||||||
queryParamsHandling="merge"
|
queryParamsHandling="merge"
|
||||||
>
|
>
|
||||||
<tui-avatar [src]="pkg.icon | trustUrl" />
|
<tui-avatar
|
||||||
|
appearance="action-grayscale"
|
||||||
|
[src]="pkg.icon | trustUrl"
|
||||||
|
/>
|
||||||
<span tuiTitle>
|
<span tuiTitle>
|
||||||
{{ pkg.title }}
|
{{ pkg.title }}
|
||||||
<span tuiSubtitle>{{ pkg.version }}</span>
|
<span tuiSubtitle>{{ pkg.version }}</span>
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ import { DocsLinkDirective } from '@start9labs/shared'
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h3 style="color: #f8546a; font-weight: bold">Important!</h3>
|
<h2 style="font-variant-caps: all-small-caps">
|
||||||
|
Root Certificate Authority
|
||||||
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
Download your server's Root CA and
|
Download your server's Root CA and
|
||||||
<a
|
<a
|
||||||
@@ -47,7 +49,7 @@ import { DocsLinkDirective } from '@start9labs/shared'
|
|||||||
path="/user-manual/trust-ca.html"
|
path="/user-manual/trust-ca.html"
|
||||||
style="color: #6866cc; font-weight: bold; text-decoration: none"
|
style="color: #6866cc; font-weight: bold; text-decoration: none"
|
||||||
>
|
>
|
||||||
follow the instructions
|
follow instructions
|
||||||
</a>
|
</a>
|
||||||
to establish a secure connection with your server.
|
to establish a secure connection with your server.
|
||||||
</p>
|
</p>
|
||||||
@@ -84,15 +86,15 @@ import { DocsLinkDirective } from '@start9labs/shared'
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<h2 style="font-variant-caps: all-small-caps">
|
<h2 style="font-variant-caps: all-small-caps">
|
||||||
Access from home (LAN)
|
Permanent Local Address
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
Visit the address below when you are connected to the same WiFi or
|
You must be connected to the same Local Area Network (LAN) as your
|
||||||
Local Area Network (LAN) as your server.
|
server to access this address.
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
style="
|
style="
|
||||||
padding: 16px;
|
padding: 16px 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@@ -100,33 +102,6 @@ import { DocsLinkDirective } from '@start9labs/shared'
|
|||||||
>
|
>
|
||||||
<code id="lan-addr"></code>
|
<code id="lan-addr"></code>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 style="font-variant-caps: all-small-caps">
|
|
||||||
Access on the go (Tor)
|
|
||||||
</h2>
|
|
||||||
<p>Visit the address below when you are away from home.</p>
|
|
||||||
<p>
|
|
||||||
<span style="font-weight: bold">Note:</span>
|
|
||||||
This address will only work from a Tor-enabled browser.
|
|
||||||
<a
|
|
||||||
docsLink
|
|
||||||
path="/user-manual/connecting-remotely/tor.html"
|
|
||||||
style="color: #6866cc; font-weight: bold; text-decoration: none"
|
|
||||||
>
|
|
||||||
Follow the instructions
|
|
||||||
</a>
|
|
||||||
to get setup.
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
style="
|
|
||||||
padding: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
overflow: auto;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<code id="tor-addr"></code>
|
|
||||||
</p>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
DOCUMENT,
|
DOCUMENT,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { DownloadHTMLService, ErrorService } from '@start9labs/shared'
|
import { DownloadHTMLService, ErrorService } from '@start9labs/shared'
|
||||||
import { TuiButton, TuiIcon, TuiSurface } from '@taiga-ui/core'
|
import { TuiButton, TuiIcon, TuiLoader, TuiSurface } from '@taiga-ui/core'
|
||||||
import { TuiCardLarge } from '@taiga-ui/layout'
|
import { TuiCardLarge } from '@taiga-ui/layout'
|
||||||
import { DocumentationComponent } from 'src/app/components/documentation.component'
|
import { DocumentationComponent } from 'src/app/components/documentation.component'
|
||||||
import { MatrixComponent } from 'src/app/components/matrix.component'
|
import { MatrixComponent } from 'src/app/components/matrix.component'
|
||||||
@@ -31,10 +31,16 @@ import { StateService } from 'src/app/services/state.service'
|
|||||||
<h3>You can now safely unplug your old StartOS data drive</h3>
|
<h3>You can now safely unplug your old StartOS data drive</h3>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
http://start.local was for setup purposes only. It will no longer
|
||||||
|
work.
|
||||||
|
</h3>
|
||||||
|
|
||||||
<button tuiCardLarge tuiSurface="floating" (click)="download()">
|
<button tuiCardLarge tuiSurface="floating" (click)="download()">
|
||||||
<strong class="caps">Download address info</strong>
|
<strong class="caps">Download address info</strong>
|
||||||
<span>
|
<span>
|
||||||
start.local was for setup purposes only. It will no longer work.
|
For future reference, this file contains your server's permanent
|
||||||
|
local address, as well as its Root Certificate Authority (Root CA).
|
||||||
</span>
|
</span>
|
||||||
<strong class="caps">
|
<strong class="caps">
|
||||||
Download
|
Download
|
||||||
@@ -48,17 +54,18 @@ import { StateService } from 'src/app/services/state.service'
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
[attr.href]="disableLogin ? null : lanAddress"
|
[attr.href]="disableLogin ? null : lanAddress"
|
||||||
>
|
>
|
||||||
<strong class="caps">Trust your Root CA</strong>
|
|
||||||
<span>
|
<span>
|
||||||
In the new tab, follow instructions to trust your server's Root CA
|
In the new tab, follow instructions to trust your server's Root CA
|
||||||
and log in.
|
and log in.
|
||||||
</span>
|
</span>
|
||||||
<strong class="caps">
|
<strong class="caps">
|
||||||
Open
|
Open Local Address
|
||||||
<tui-icon icon="@tui.external-link" />
|
<tui-icon icon="@tui.external-link" />
|
||||||
</strong>
|
</strong>
|
||||||
</a>
|
</a>
|
||||||
<app-documentation hidden [lanAddress]="lanAddress" />
|
<app-documentation hidden [lanAddress]="lanAddress" />
|
||||||
|
} @else {
|
||||||
|
<tui-loader />
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
`,
|
`,
|
||||||
@@ -97,6 +104,10 @@ import { StateService } from 'src/app/services/state.service'
|
|||||||
opacity: var(--tui-disabled-opacity);
|
opacity: var(--tui-disabled-opacity);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
imports: [
|
imports: [
|
||||||
TuiCardLarge,
|
TuiCardLarge,
|
||||||
@@ -105,6 +116,7 @@ import { StateService } from 'src/app/services/state.service'
|
|||||||
TuiSurface,
|
TuiSurface,
|
||||||
MatrixComponent,
|
MatrixComponent,
|
||||||
DocumentationComponent,
|
DocumentationComponent,
|
||||||
|
TuiLoader,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export default class SuccessPage implements AfterViewInit {
|
export default class SuccessPage implements AfterViewInit {
|
||||||
@@ -117,7 +129,6 @@ export default class SuccessPage implements AfterViewInit {
|
|||||||
|
|
||||||
readonly stateService = inject(StateService)
|
readonly stateService = inject(StateService)
|
||||||
|
|
||||||
torAddresses?: string[]
|
|
||||||
lanAddress?: string
|
lanAddress?: string
|
||||||
cert?: string
|
cert?: string
|
||||||
disableLogin = this.stateService.setupType === 'fresh'
|
disableLogin = this.stateService.setupType === 'fresh'
|
||||||
@@ -127,10 +138,8 @@ export default class SuccessPage implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
download() {
|
download() {
|
||||||
const torElem = this.document.getElementById('tor-addr')
|
|
||||||
const lanElem = this.document.getElementById('lan-addr')
|
const lanElem = this.document.getElementById('lan-addr')
|
||||||
|
|
||||||
if (torElem) torElem.innerHTML = this.torAddresses?.join('\n') || ''
|
|
||||||
if (lanElem) lanElem.innerHTML = this.lanAddress || ''
|
if (lanElem) lanElem.innerHTML = this.lanAddress || ''
|
||||||
|
|
||||||
this.document
|
this.document
|
||||||
@@ -155,9 +164,6 @@ export default class SuccessPage implements AfterViewInit {
|
|||||||
try {
|
try {
|
||||||
const ret = await this.api.complete()
|
const ret = await this.api.complete()
|
||||||
if (!this.stateService.kiosk) {
|
if (!this.stateService.kiosk) {
|
||||||
this.torAddresses = ret.torAddresses.map(a =>
|
|
||||||
a.replace(/^https:/, 'http:'),
|
|
||||||
)
|
|
||||||
this.lanAddress = ret.lanAddress.replace(/^https:/, 'http:')
|
this.lanAddress = ret.lanAddress.replace(/^https:/, 'http:')
|
||||||
this.cert = ret.rootCa
|
this.cert = ret.rootCa
|
||||||
|
|
||||||
|
|||||||
@@ -484,7 +484,7 @@ export default {
|
|||||||
512: 'Der Kiosk-Modus ist auf diesem Gerät nicht verfügbar',
|
512: 'Der Kiosk-Modus ist auf diesem Gerät nicht verfügbar',
|
||||||
513: 'Aktivieren',
|
513: 'Aktivieren',
|
||||||
514: 'Deaktivieren',
|
514: 'Deaktivieren',
|
||||||
515: 'Du verwendest derzeit einen Kiosk. Wenn du den Kiosk-Modus deaktivierst, wird die Verbindung zum Kiosk getrennt.',
|
515: 'Diese Änderung wird nach dem nächsten Neustart wirksam',
|
||||||
516: 'Empfohlen',
|
516: 'Empfohlen',
|
||||||
517: 'Möchten Sie diese Aufgabe wirklich verwerfen?',
|
517: 'Möchten Sie diese Aufgabe wirklich verwerfen?',
|
||||||
518: 'Verwerfen',
|
518: 'Verwerfen',
|
||||||
|
|||||||
@@ -483,7 +483,7 @@ export const ENGLISH = {
|
|||||||
'Kiosk Mode is unavailable on this device': 512,
|
'Kiosk Mode is unavailable on this device': 512,
|
||||||
'Enable': 513,
|
'Enable': 513,
|
||||||
'Disable': 514,
|
'Disable': 514,
|
||||||
'You are currently using a kiosk. Disabling Kiosk Mode will result in the kiosk disconnecting.': 515,
|
'This change will take effect after the next boot': 515,
|
||||||
'Recommended': 516, // as in, we recommend this
|
'Recommended': 516, // as in, we recommend this
|
||||||
'Are you sure you want to dismiss this task?': 517,
|
'Are you sure you want to dismiss this task?': 517,
|
||||||
'Dismiss': 518, // as in, dismiss or delete a task
|
'Dismiss': 518, // as in, dismiss or delete a task
|
||||||
|
|||||||
@@ -484,7 +484,7 @@ export default {
|
|||||||
512: 'El modo quiosco no está disponible en este dispositivo',
|
512: 'El modo quiosco no está disponible en este dispositivo',
|
||||||
513: 'Activar',
|
513: 'Activar',
|
||||||
514: 'Desactivar',
|
514: 'Desactivar',
|
||||||
515: 'Actualmente estás utilizando un quiosco. Desactivar el modo quiosco provocará su desconexión.',
|
515: 'Este cambio tendrá efecto después del próximo inicio',
|
||||||
516: 'Recomendado',
|
516: 'Recomendado',
|
||||||
517: '¿Estás seguro de que deseas descartar esta tarea?',
|
517: '¿Estás seguro de que deseas descartar esta tarea?',
|
||||||
518: 'Descartar',
|
518: 'Descartar',
|
||||||
|
|||||||
@@ -484,7 +484,7 @@ export default {
|
|||||||
512: 'Le mode kiosque n’est pas disponible sur cet appareil',
|
512: 'Le mode kiosque n’est pas disponible sur cet appareil',
|
||||||
513: 'Activer',
|
513: 'Activer',
|
||||||
514: 'Désactiver',
|
514: 'Désactiver',
|
||||||
515: 'Vous utilisez actuellement un kiosque. Désactiver le mode kiosque entraînera sa déconnexion.',
|
515: 'Ce changement va prendre effet après le prochain démarrage',
|
||||||
516: 'Recommandé',
|
516: 'Recommandé',
|
||||||
517: 'Êtes-vous sûr de vouloir ignorer cette tâche ?',
|
517: 'Êtes-vous sûr de vouloir ignorer cette tâche ?',
|
||||||
518: 'Ignorer',
|
518: 'Ignorer',
|
||||||
|
|||||||
@@ -484,7 +484,7 @@ export default {
|
|||||||
512: 'Tryb kiosku jest niedostępny na tym urządzeniu',
|
512: 'Tryb kiosku jest niedostępny na tym urządzeniu',
|
||||||
513: 'Włącz',
|
513: 'Włącz',
|
||||||
514: 'Wyłącz',
|
514: 'Wyłącz',
|
||||||
515: 'Obecnie używasz kiosku. Wyłączenie trybu kiosku spowoduje jego rozłączenie.',
|
515: 'Ta zmiana zacznie obowiązywać po następnym uruchomieniu',
|
||||||
516: 'Zalecane',
|
516: 'Zalecane',
|
||||||
517: 'Czy na pewno chcesz odrzucić to zadanie?',
|
517: 'Czy na pewno chcesz odrzucić to zadanie?',
|
||||||
518: 'Odrzuć',
|
518: 'Odrzuć',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { I18N, i18nKey } from './i18n.providers'
|
|||||||
export class i18nPipe implements PipeTransform {
|
export class i18nPipe implements PipeTransform {
|
||||||
private readonly i18n = inject(I18N)
|
private readonly i18n = inject(I18N)
|
||||||
|
|
||||||
transform(englishKey: i18nKey | null | undefined): string {
|
transform(englishKey: i18nKey | null | undefined | ''): string {
|
||||||
englishKey = englishKey || ('' as i18nKey)
|
englishKey = englishKey || ('' as i18nKey)
|
||||||
|
|
||||||
return this.i18n()?.[ENGLISH[englishKey]] || englishKey
|
return this.i18n()?.[ENGLISH[englishKey]] || englishKey
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function getIp({ clients, range }: MappedSubnet) {
|
|||||||
const net = IpNet.parse(range)
|
const net = IpNet.parse(range)
|
||||||
const last = net.broadcast()
|
const last = net.broadcast()
|
||||||
|
|
||||||
for (let ip = net.add(1); ip.cmp(last) === -1; ip.add(1)) {
|
for (let ip = net.add(1); ip.cmp(last) === -1; ip = ip.add(1)) {
|
||||||
if (!clients[ip.address]) {
|
if (!clients[ip.address]) {
|
||||||
return ip.address
|
return ip.address
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ import { HintPipe } from '../pipes/hint.pipe'
|
|||||||
[(ngModel)]="selected"
|
[(ngModel)]="selected"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<tui-data-list-wrapper *tuiTextfieldDropdown new [items]="items" />
|
@if (!mobile) {
|
||||||
|
<tui-data-list-wrapper *tuiTextfieldDropdown new [items]="items" />
|
||||||
|
}
|
||||||
@if (spec | hint; as hint) {
|
@if (spec | hint; as hint) {
|
||||||
<tui-icon [tuiTooltip]="hint" />
|
<tui-icon [tuiTooltip]="hint" />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
tuiButtonOptionsProvider,
|
tuiButtonOptionsProvider,
|
||||||
TuiDataList,
|
TuiDataList,
|
||||||
TuiDropdown,
|
TuiDropdown,
|
||||||
|
TuiIcon,
|
||||||
TuiTextfield,
|
TuiTextfield,
|
||||||
} from '@taiga-ui/core'
|
} from '@taiga-ui/core'
|
||||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
@@ -27,13 +28,9 @@ import { InterfaceComponent } from '../interface.component'
|
|||||||
selector: 'td[actions]',
|
selector: 'td[actions]',
|
||||||
template: `
|
template: `
|
||||||
<div class="desktop">
|
<div class="desktop">
|
||||||
<button
|
<button tuiIconButton appearance="flat-grayscale" (click)="viewDetails()">
|
||||||
tuiIconButton
|
|
||||||
appearance="flat-grayscale"
|
|
||||||
iconStart="@tui.info"
|
|
||||||
(click)="viewDetails()"
|
|
||||||
>
|
|
||||||
{{ 'Address details' | i18n }}
|
{{ 'Address details' | i18n }}
|
||||||
|
<tui-icon class="info" icon="@tui.info" background="@tui.info-filled" />
|
||||||
</button>
|
</button>
|
||||||
@if (interface.value()?.type === 'ui') {
|
@if (interface.value()?.type === 'ui') {
|
||||||
<a
|
<a
|
||||||
@@ -113,6 +110,19 @@ import { InterfaceComponent } from '../interface.component'
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host-context(.uncommon-hidden) .desktop {
|
||||||
|
height: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
background: var(--tui-status-info);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
mask-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mobile {
|
.mobile {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -127,7 +137,14 @@ import { InterfaceComponent } from '../interface.component'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
imports: [TuiButton, TuiDropdown, TuiDataList, i18nPipe, TuiTextfield],
|
imports: [
|
||||||
|
TuiButton,
|
||||||
|
TuiDropdown,
|
||||||
|
TuiDataList,
|
||||||
|
i18nPipe,
|
||||||
|
TuiTextfield,
|
||||||
|
TuiIcon,
|
||||||
|
],
|
||||||
providers: [tuiButtonOptionsProvider({ appearance: 'icon' })],
|
providers: [tuiButtonOptionsProvider({ appearance: 'icon' })],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||||
import { i18nPipe } from '@start9labs/shared'
|
import { i18nPipe } from '@start9labs/shared'
|
||||||
|
import { TuiButton } from '@taiga-ui/core'
|
||||||
import { TuiAccordion } from '@taiga-ui/experimental'
|
import { TuiAccordion } from '@taiga-ui/experimental'
|
||||||
import { TuiSkeleton } from '@taiga-ui/kit'
|
import { TuiElasticContainer, TuiSkeleton } from '@taiga-ui/kit'
|
||||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||||
|
|
||||||
@@ -12,91 +13,79 @@ import { InterfaceAddressItemComponent } from './item.component'
|
|||||||
selector: 'section[addresses]',
|
selector: 'section[addresses]',
|
||||||
template: `
|
template: `
|
||||||
<header>{{ 'Addresses' | i18n }}</header>
|
<header>{{ 'Addresses' | i18n }}</header>
|
||||||
<table [appTable]="['Type', 'Access', 'Gateway', 'URL', null]">
|
<tui-elastic-container>
|
||||||
@for (address of addresses()?.common; track $index) {
|
<table [appTable]="['Type', 'Access', 'Gateway', 'URL', null]">
|
||||||
<tr [address]="address" [isRunning]="isRunning()"></tr>
|
@for (address of addresses()?.common; track $index) {
|
||||||
} @empty {
|
<tr [address]="address" [isRunning]="isRunning()"></tr>
|
||||||
@if (addresses()) {
|
} @empty {
|
||||||
<tr>
|
@if (addresses()) {
|
||||||
<td colspan="5">
|
|
||||||
<app-placeholder icon="@tui.list-x">
|
|
||||||
{{ 'No addresses' | i18n }}
|
|
||||||
</app-placeholder>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
} @else {
|
|
||||||
@for (_ of [0, 1]; track $index) {
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6">
|
<td colspan="5">
|
||||||
<div [tuiSkeleton]="true">{{ 'Loading' | i18n }}</div>
|
<app-placeholder icon="@tui.list-x">
|
||||||
|
{{ 'No addresses' | i18n }}
|
||||||
|
</app-placeholder>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
} @else {
|
||||||
|
@for (_ of [0, 1]; track $index) {
|
||||||
|
<tr>
|
||||||
|
<td colspan="6">
|
||||||
|
<div [tuiSkeleton]="true">{{ 'Loading' | i18n }}</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
<tbody [class.uncommon-hidden]="!uncommon">
|
||||||
</table>
|
@if (addresses()?.uncommon?.length && uncommon) {
|
||||||
@if (addresses()?.uncommon?.length) {
|
<tr [style.background]="'var(--tui-background-neutral-1)'">
|
||||||
<tui-accordion>
|
<td colspan="5"></td>
|
||||||
<tui-expand>
|
</tr>
|
||||||
<hr />
|
|
||||||
<table class="g-table">
|
|
||||||
@for (address of addresses()?.uncommon; track $index) {
|
|
||||||
<tr [address]="address" [isRunning]="isRunning()"></tr>
|
|
||||||
}
|
|
||||||
</table>
|
|
||||||
</tui-expand>
|
|
||||||
<button
|
|
||||||
appearance="secondary-grayscale"
|
|
||||||
iconEnd=""
|
|
||||||
[(tuiAccordion)]="uncommon"
|
|
||||||
>
|
|
||||||
@if (uncommon) {
|
|
||||||
Hide uncommon
|
|
||||||
} @else {
|
|
||||||
Show uncommon
|
|
||||||
}
|
}
|
||||||
</button>
|
@for (address of addresses()?.uncommon; track $index) {
|
||||||
</tui-accordion>
|
<tr [address]="address" [isRunning]="isRunning()"></tr>
|
||||||
}
|
}
|
||||||
|
</tbody>
|
||||||
|
@if (addresses()?.uncommon?.length) {
|
||||||
|
<caption [style.caption-side]="'bottom'">
|
||||||
|
<button
|
||||||
|
tuiButton
|
||||||
|
size="m"
|
||||||
|
appearance="secondary-grayscale"
|
||||||
|
(click)="uncommon = !uncommon"
|
||||||
|
>
|
||||||
|
@if (uncommon) {
|
||||||
|
Hide uncommon
|
||||||
|
} @else {
|
||||||
|
Show uncommon
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</caption>
|
||||||
|
}
|
||||||
|
</table>
|
||||||
|
</tui-elastic-container>
|
||||||
`,
|
`,
|
||||||
styles: `
|
styles: `
|
||||||
tui-accordion {
|
.g-table:has(caption) {
|
||||||
border-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[tuiAccordion],
|
[tuiButton] {
|
||||||
tui-expand {
|
width: 100%;
|
||||||
box-shadow: none;
|
border-top-left-radius: 0;
|
||||||
padding: 0;
|
border-top-right-radius: 0;
|
||||||
}
|
|
||||||
|
|
||||||
[tuiAccordion] {
|
|
||||||
justify-content: center;
|
|
||||||
height: 3rem;
|
|
||||||
border-radius: 0 0 var(--tui-radius-m) var(--tui-radius-m) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
margin: 0;
|
|
||||||
height: 0.25rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host-context(tui-root._mobile) {
|
|
||||||
[tuiAccordion] {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
border-radius: var(--tui-radius-m) !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
host: { class: 'g-card' },
|
host: { class: 'g-card' },
|
||||||
imports: [
|
imports: [
|
||||||
|
TuiSkeleton,
|
||||||
|
TuiButton,
|
||||||
TableComponent,
|
TableComponent,
|
||||||
PlaceholderComponent,
|
PlaceholderComponent,
|
||||||
i18nPipe,
|
i18nPipe,
|
||||||
InterfaceAddressItemComponent,
|
InterfaceAddressItemComponent,
|
||||||
TuiAccordion,
|
TuiElasticContainer,
|
||||||
TuiSkeleton,
|
|
||||||
],
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,27 +8,31 @@ import { TuiBadge } from '@taiga-ui/kit'
|
|||||||
selector: 'tr[address]',
|
selector: 'tr[address]',
|
||||||
template: `
|
template: `
|
||||||
@if (address(); as address) {
|
@if (address(); as address) {
|
||||||
<td>{{ address.type }}</td>
|
|
||||||
<td>
|
<td>
|
||||||
@if (address.access === 'public') {
|
<div class="wrapper">{{ address.type }}</div>
|
||||||
<tui-badge size="s" appearance="primary-success">
|
</td>
|
||||||
{{ 'public' | i18n }}
|
<td>
|
||||||
</tui-badge>
|
<div class="wrapper">
|
||||||
} @else if (address.access === 'private') {
|
@if (address.access === 'public') {
|
||||||
<tui-badge size="s" appearance="primary-destructive">
|
<tui-badge size="s" appearance="primary-success">
|
||||||
{{ 'private' | i18n }}
|
{{ 'public' | i18n }}
|
||||||
</tui-badge>
|
</tui-badge>
|
||||||
} @else {
|
} @else if (address.access === 'private') {
|
||||||
-
|
<tui-badge size="s" appearance="primary-destructive">
|
||||||
}
|
{{ 'private' | i18n }}
|
||||||
|
</tui-badge>
|
||||||
|
} @else {
|
||||||
|
-
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td [style.order]="-1">
|
<td [style.order]="-1">
|
||||||
<div [title]="address.gatewayName">
|
<div class="wrapper" [title]="address.gatewayName">
|
||||||
{{ address.gatewayName || '-' }}
|
{{ address.gatewayName || '-' }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div [title]="address.url">{{ address.url }}</div>
|
<div class="wrapper" [title]="address.url">{{ address.url }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
actions
|
actions
|
||||||
@@ -48,6 +52,18 @@ import { TuiBadge } from '@taiga-ui/kit'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host-context(.uncommon-hidden) {
|
||||||
|
.wrapper {
|
||||||
|
height: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding-block: 0;
|
||||||
|
border: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user