Merge branch 'next/minor' of github.com:Start9Labs/start-os into feat/boot-param
20
.github/workflows/startos-iso.yaml
vendored
@@ -71,27 +71,27 @@ jobs:
|
||||
sudo mount -t tmpfs tmpfs .
|
||||
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODEJS_VERSION }}
|
||||
|
||||
- name: Set up docker QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up system dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y qemu-user-static systemd-container
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Make
|
||||
run: make ARCH=${{ matrix.arch }} compiled-${{ matrix.arch }}.tar
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: compiled-${{ matrix.arch }}.tar
|
||||
path: compiled-${{ matrix.arch }}.tar
|
||||
@@ -144,7 +144,7 @@ jobs:
|
||||
run: rm -rf /opt/hostedtoolcache*
|
||||
if: ${{ github.event.inputs.runner != 'fast' }}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -166,7 +166,7 @@ jobs:
|
||||
if: ${{ github.event.inputs.runner == 'fast' && (matrix.platform == 'x86_64' || matrix.platform == 'x86_64-nonfree') }}
|
||||
|
||||
- name: Download compiled artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: compiled-${{ env.ARCH }}.tar
|
||||
|
||||
@@ -190,18 +190,18 @@ jobs:
|
||||
run: PLATFORM=${{ matrix.platform }} make img
|
||||
if: ${{ matrix.platform == 'raspberrypi' }}
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.platform }}.squashfs
|
||||
path: results/*.squashfs
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.platform }}.iso
|
||||
path: results/*.iso
|
||||
if: ${{ matrix.platform != 'raspberrypi' }}
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.platform }}.img
|
||||
path: results/*.img
|
||||
|
||||
4
.github/workflows/test.yaml
vendored
@@ -19,11 +19,11 @@ jobs:
|
||||
name: Run Automated Tests
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODEJS_VERSION }}
|
||||
|
||||
|
||||
133
DEVELOPMENT.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Setting up your development environment on Debian/Ubuntu
|
||||
|
||||
A step-by-step guide
|
||||
|
||||
> This is the only officially supported build environment.
|
||||
> MacOS has limited build capabilities and Windows requires [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install)
|
||||
|
||||
## Installing dependencies
|
||||
|
||||
Run the following commands one at a time
|
||||
|
||||
```sh
|
||||
sudo apt update
|
||||
sudo apt install -y ca-certificates curl gpg build-essential
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg-architecture -q DEB_HOST_ARCH) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bookworm stable" | sudo tee /etc/apt/sources.list.d/docker.list
|
||||
sudo apt update
|
||||
sudo apt install -y sed grep gawk jq gzip brotli containerd.io docker-ce docker-ce-cli docker-compose-plugin qemu-user-static binfmt-support squashfs-tools git debspawn rsync b3sum
|
||||
sudo mkdir -p /etc/debspawn/
|
||||
echo "AllowUnsafePermissions=true" | sudo tee /etc/debspawn/global.toml
|
||||
sudo usermod -aG docker $USER
|
||||
sudo su $USER
|
||||
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
docker buildx create --use
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # proceed with default installation
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
|
||||
source ~/.bashrc
|
||||
nvm install 20
|
||||
nvm use 20
|
||||
```
|
||||
|
||||
## Cloning the repository
|
||||
|
||||
```sh
|
||||
git clone --recursive https://github.com/Start9Labs/start-os.git --branch next/minor
|
||||
cd start-os
|
||||
```
|
||||
|
||||
## Building an ISO
|
||||
|
||||
```sh
|
||||
PLATFORM=$(uname -m) ENVIRONMENT=dev make iso
|
||||
```
|
||||
|
||||
This will build an ISO for your current architecture. If you are building to run on an architecture other than the one you are currently on, replace `$(uname -m)` with the correct platform for the device (one of `aarch64`, `aarch64-nonfree`, `x86_64`, `x86_64-nonfree`, `raspberrypi`)
|
||||
|
||||
## Creating a VM
|
||||
|
||||
### Install virt-manager
|
||||
|
||||
```sh
|
||||
sudo apt update
|
||||
sudo apt install -y virt-manager
|
||||
sudo usermod -aG libvirt $USER
|
||||
sudo su $USER
|
||||
```
|
||||
|
||||
### Launch virt-manager
|
||||
|
||||
```sh
|
||||
virt-manager
|
||||
```
|
||||
|
||||
### Create new virtual machine
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### make sure to set "Target Path" to the path to your results directory in start-os
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Updating a VM
|
||||
|
||||
The fastest way to update a VM to your latest code depends on what you changed:
|
||||
|
||||
### UI or startd:
|
||||
|
||||
```sh
|
||||
PLATFORM=$(uname -m) ENVIRONMENT=dev make update-startbox REMOTE=start9@<VM IP>
|
||||
```
|
||||
|
||||
### Container runtime or debian dependencies:
|
||||
|
||||
```sh
|
||||
PLATFORM=$(uname -m) ENVIRONMENT=dev make update-deb REMOTE=start9@<VM IP>
|
||||
```
|
||||
|
||||
### Image recipe:
|
||||
|
||||
```sh
|
||||
PLATFORM=$(uname -m) ENVIRONMENT=dev make update-squashfs REMOTE=start9@<VM IP>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
If the device you are building for is not available via ssh, it is also possible to use `magic-wormhole` to send the relevant files.
|
||||
|
||||
### Prerequisites:
|
||||
|
||||
```sh
|
||||
sudo apt update
|
||||
sudo apt install -y magic-wormhole
|
||||
```
|
||||
|
||||
As before, the fastest way to update a VM to your latest code depends on what you changed. Each of the following commands will return a command to paste into the shell of the device you would like to upgrade.
|
||||
|
||||
### UI or startd:
|
||||
|
||||
```sh
|
||||
PLATFORM=$(uname -m) ENVIRONMENT=dev make wormhole
|
||||
```
|
||||
|
||||
### Container runtime or debian dependencies:
|
||||
|
||||
```sh
|
||||
PLATFORM=$(uname -m) ENVIRONMENT=dev make wormhole-deb
|
||||
```
|
||||
|
||||
### Image recipe:
|
||||
|
||||
```sh
|
||||
PLATFORM=$(uname -m) ENVIRONMENT=dev make wormhole-squashfs
|
||||
```
|
||||
51
Makefile
@@ -17,7 +17,7 @@ COMPAT_SRC := $(shell git ls-files system-images/compat/)
|
||||
UTILS_SRC := $(shell git ls-files system-images/utils/)
|
||||
BINFMT_SRC := $(shell git ls-files system-images/binfmt/)
|
||||
CORE_SRC := $(shell git ls-files core) $(shell git ls-files --recurse-submodules patch-db) web/dist/static web/patchdb-ui-seed.json $(GIT_HASH_FILE)
|
||||
WEB_SHARED_SRC := $(shell git ls-files web/projects/shared) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist web/patchdb-ui-seed.json
|
||||
WEB_SHARED_SRC := $(shell git ls-files web/projects/shared) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist web/patchdb-ui-seed.json sdk/dist
|
||||
WEB_UI_SRC := $(shell git ls-files web/projects/ui)
|
||||
WEB_SETUP_WIZARD_SRC := $(shell git ls-files web/projects/setup-wizard)
|
||||
WEB_INSTALL_WIZARD_SRC := $(shell git ls-files web/projects/install-wizard)
|
||||
@@ -152,16 +152,21 @@ update-overlay: $(ALL_TARGETS)
|
||||
$(call ssh,"sudo systemctl start startd")
|
||||
|
||||
wormhole: core/target/$(ARCH)-unknown-linux-musl/release/startbox
|
||||
@echo "Paste the following command into the shell of your start-os server:"
|
||||
@echo "Paste the following command into the shell of your StartOS server:"
|
||||
@echo
|
||||
@wormhole send core/target/$(ARCH)-unknown-linux-musl/release/startbox 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo /usr/lib/startos/scripts/chroot-and-upgrade \"cd /usr/bin && rm startbox && wormhole receive --accept-file %s && chmod +x startbox\"\n", $$3 }'
|
||||
|
||||
wormhole-deb: results/$(BASENAME).deb
|
||||
@echo "Paste the following command into the shell of your start-os server:"
|
||||
@echo "Paste the following command into the shell of your StartOS server:"
|
||||
@echo
|
||||
@wormhole send results/$(BASENAME).deb 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo /usr/lib/startos/scripts/chroot-and-upgrade '"'"'cd $$(mktemp -d) && wormhole receive --accept-file %s && apt-get install -y --reinstall ./$(BASENAME).deb'"'"'\n", $$3 }'
|
||||
|
||||
wormhole-cli: core/target/$(ARCH)-unknown-linux-musl/release/start-cli
|
||||
@echo "Paste the following command into the shell of your start-os server:"
|
||||
@wormhole send results/$(BASENAME).deb 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo /usr/lib/startos/scripts/chroot-and-upgrade '"'"'cd $$(mktemp -d) && wormhole receive --accept-file %s && apt-get install -y --reinstall ./$(BASENAME).deb'"'"'\n", $$3 }'
|
||||
wormhole-squashfs: results/$(BASENAME).squashfs
|
||||
$(eval SQFS_SUM := $(shell b3sum results/$(BASENAME).squashfs | head -c 32))
|
||||
$(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
|
||||
@wormhole send results/$(BASENAME).squashfs 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo sh -c '"'"'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE) && cd /media/startos/images && wormhole receive --accept-file %s && mv $(BASENAME).squashfs $(SQFS_SUM).rootfs && ln -rsf ./$(SQFS_SUM).rootfs ../config/current.rootfs && sync && reboot'"'"'\n", $$3 }'
|
||||
|
||||
update: $(ALL_TARGETS)
|
||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||
@@ -169,6 +174,28 @@ update: $(ALL_TARGETS)
|
||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/startos/next PLATFORM=$(PLATFORM)
|
||||
$(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync "apt-get install -y $(shell cat ./build/lib/depends)"')
|
||||
|
||||
update-startbox: core/target/$(ARCH)-unknown-linux-musl/release/startbox # only update binary (faster than full update)
|
||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
||||
$(call cp,core/target/$(ARCH)-unknown-linux-musl/release/startbox,/media/startos/next/usr/bin/startbox)
|
||||
$(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync true')
|
||||
|
||||
update-deb: results/$(BASENAME).deb # better than update, but only available from debian
|
||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
||||
$(call mkdir,/media/startos/next/tmp/startos-deb)
|
||||
$(call cp,results/$(BASENAME).deb,/media/startos/next/tmp/startos-deb/$(BASENAME).deb)
|
||||
$(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync "apt-get install -y --reinstall /tmp/startos-deb/$(BASENAME).deb"')
|
||||
|
||||
update-squashfs: results/$(BASENAME).squashfs
|
||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||
$(eval SQFS_SUM := $(shell b3sum results/$(BASENAME).squashfs))
|
||||
$(eval SQFS_SIZE := $(shell du -s --bytes results/$(BASENAME).squashfs | awk '{print $$1}'))
|
||||
$(call ssh,'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE)')
|
||||
$(call cp,results/$(BASENAME).squashfs,/media/startos/images/$(SQFS_SUM).rootfs)
|
||||
$(call ssh,'sudo ln -rsf /media/startos/images/$(SQFS_SUM).rootfs /media/startos/config/current.rootfs')
|
||||
$(call ssh,'sudo reboot')
|
||||
|
||||
emulate-reflash: $(ALL_TARGETS)
|
||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
||||
@@ -228,22 +255,26 @@ system-images/binfmt/docker-images/$(ARCH).tar: $(BINFMT_SRC)
|
||||
cd system-images/binfmt && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
||||
|
||||
$(BINS): $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||
cd core && ARCH=$(ARCH) ./build-prod.sh
|
||||
cd core && ARCH=$(ARCH) ./build-startos-bins.sh
|
||||
touch $(BINS)
|
||||
|
||||
web/node_modules/.package-lock.json: web/package.json sdk/dist
|
||||
npm --prefix web ci
|
||||
touch web/node_modules/.package-lock.json
|
||||
|
||||
web/dist/raw/ui: $(WEB_UI_SRC) $(WEB_SHARED_SRC)
|
||||
web/.angular: patch-db/client/dist sdk/dist web/node_modules/.package-lock.json
|
||||
rm -rf web/.angular
|
||||
mkdir -p web/.angular
|
||||
|
||||
web/dist/raw/ui: $(WEB_UI_SRC) $(WEB_SHARED_SRC) web/.angular
|
||||
npm --prefix web run build:ui
|
||||
touch web/dist/raw/ui
|
||||
|
||||
web/dist/raw/setup-wizard: $(WEB_SETUP_WIZARD_SRC) $(WEB_SHARED_SRC)
|
||||
web/dist/raw/setup-wizard: $(WEB_SETUP_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular
|
||||
npm --prefix web run build:setup
|
||||
touch web/dist/raw/setup-wizard
|
||||
|
||||
web/dist/raw/install-wizard: $(WEB_INSTALL_WIZARD_SRC) $(WEB_SHARED_SRC)
|
||||
web/dist/raw/install-wizard: $(WEB_INSTALL_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular
|
||||
npm --prefix web run build:install-wiz
|
||||
touch web/dist/raw/install-wizard
|
||||
|
||||
|
||||
BIN
assets/create-vm/step-1.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
assets/create-vm/step-10.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
assets/create-vm/step-11.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
assets/create-vm/step-12.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
assets/create-vm/step-2.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
assets/create-vm/step-3.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
assets/create-vm/step-4.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
assets/create-vm/step-5.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
assets/create-vm/step-6.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
assets/create-vm/step-7.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
assets/create-vm/step-8.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
assets/create-vm/step-9.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
@@ -1,5 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
SOURCE_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
if [ "$UID" -ne 0 ]; then
|
||||
>&2 echo 'Must be run as root'
|
||||
exit 1
|
||||
@@ -77,20 +79,7 @@ umount /media/startos/next/boot
|
||||
if [ "$CHROOT_RES" -eq 0 ]; then
|
||||
|
||||
if [ -h /media/startos/config/current.rootfs ] && [ -e /media/startos/config/current.rootfs ]; then
|
||||
echo 'Pruning...'
|
||||
current="$(readlink -f /media/startos/config/current.rootfs)"
|
||||
needed=$(du -s --bytes /media/startos/next | awk '{print $1}')
|
||||
while [[ "$(df -B1 --output=avail --sync /media/startos/images | tail -n1)" -lt "$needed" ]]; do
|
||||
to_prune="$(ls -t1 /media/startos/images/*.rootfs | grep -v "$current" | tail -n1)"
|
||||
if [ -e "$to_prune" ]; then
|
||||
echo " Pruning $to_prune"
|
||||
rm -rf "$to_prune"
|
||||
else
|
||||
>&2 echo "Not enough space and nothing to prune!"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo 'done.'
|
||||
${SOURCE_DIR}/prune-images $(du -s --bytes /media/startos/next | awk '{print $1}')
|
||||
fi
|
||||
|
||||
echo 'Upgrading...'
|
||||
|
||||
49
build/lib/scripts/prune-images
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ "$UID" -ne 0 ]; then
|
||||
>&2 echo 'Must be run as root'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
POSITIONAL_ARGS=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-*|--*)
|
||||
echo "Unknown option $1"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
POSITIONAL_ARGS+=("$1") # save positional arg
|
||||
shift # past argument
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
|
||||
|
||||
needed=$1
|
||||
|
||||
if [ -z "$needed" ]; then
|
||||
>&2 echo "usage: $0 <SPACE NEEDED>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -h /media/startos/config/current.rootfs ] && [ -e /media/startos/config/current.rootfs ]; then
|
||||
echo 'Pruning...'
|
||||
current="$(readlink -f /media/startos/config/current.rootfs)"
|
||||
while [[ "$(df -B1 --output=avail --sync /media/startos/images | tail -n1)" -lt "$needed" ]]; do
|
||||
to_prune="$(ls -t1 /media/startos/images/*.rootfs | grep -v "$current" | tail -n1)"
|
||||
if [ -e "$to_prune" ]; then
|
||||
echo " Pruning $to_prune"
|
||||
rm -rf "$to_prune"
|
||||
else
|
||||
>&2 echo "Not enough space and nothing to prune!"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo 'done.'
|
||||
else
|
||||
>&2 echo 'No current.rootfs, not safe to prune'
|
||||
exit 1
|
||||
fi
|
||||
81
container-runtime/package-lock.json
generated
@@ -9,11 +9,13 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@noble/curves": "^1.4.0",
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@start9labs/start-sdk": "file:../sdk/dist",
|
||||
"esbuild-plugin-resolve": "^2.0.0",
|
||||
"filebrowser": "^1.0.0",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"node-fetch": "^3.1.0",
|
||||
"ts-matches": "^5.5.1",
|
||||
"tslib": "^2.5.3",
|
||||
@@ -30,24 +32,27 @@
|
||||
},
|
||||
"../sdk/dist": {
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.3.6-alpha1",
|
||||
"version": "0.3.6-alpha5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@noble/curves": "^1.4.0",
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"ts-matches": "^5.4.1"
|
||||
"lodash.merge": "^4.6.2",
|
||||
"mime": "^4.0.3",
|
||||
"ts-matches": "^5.5.1",
|
||||
"yaml": "^2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@types/lodash": "^4.17.5",
|
||||
"@types/lodash.merge": "^4.6.2",
|
||||
"jest": "^29.4.3",
|
||||
"prettier": "^3.2.5",
|
||||
"ts-jest": "^29.0.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.0.4",
|
||||
"yaml": "^2.2.2"
|
||||
"typescript": "^5.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@iarna/toml": {
|
||||
@@ -72,6 +77,28 @@
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/curves": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz",
|
||||
"integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
|
||||
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"dev": true,
|
||||
@@ -1316,10 +1343,10 @@
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
||||
},
|
||||
"node_modules/lowercase-keys": {
|
||||
"version": "2.0.0",
|
||||
@@ -2233,6 +2260,19 @@
|
||||
"os-filter-obj": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@noble/curves": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz",
|
||||
"integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==",
|
||||
"requires": {
|
||||
"@noble/hashes": "1.4.0"
|
||||
}
|
||||
},
|
||||
"@noble/hashes": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
|
||||
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"dev": true,
|
||||
@@ -2261,14 +2301,17 @@
|
||||
"version": "file:../sdk/dist",
|
||||
"requires": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@noble/curves": "^1.4.0",
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@types/lodash": "^4.17.5",
|
||||
"@types/lodash.merge": "^4.6.2",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"jest": "^29.4.3",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"mime": "^4.0.3",
|
||||
"prettier": "^3.2.5",
|
||||
"ts-jest": "^29.0.5",
|
||||
"ts-matches": "^5.4.1",
|
||||
"ts-matches": "^5.5.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.0.4",
|
||||
@@ -2988,10 +3031,10 @@
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
"lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
||||
},
|
||||
"lowercase-keys": {
|
||||
"version": "2.0.0",
|
||||
|
||||
@@ -18,10 +18,12 @@
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@start9labs/start-sdk": "file:../sdk/dist",
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@noble/curves": "^1.4.0",
|
||||
"esbuild-plugin-resolve": "^2.0.0",
|
||||
"filebrowser": "^1.0.0",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"node-fetch": "^3.1.0",
|
||||
"ts-matches": "^5.5.1",
|
||||
"tslib": "^2.5.3",
|
||||
|
||||
@@ -42,6 +42,12 @@ import {
|
||||
} from "@start9labs/start-sdk/cjs/lib/interfaces/Host"
|
||||
import { ServiceInterfaceBuilder } from "@start9labs/start-sdk/cjs/lib/interfaces/ServiceInterfaceBuilder"
|
||||
import { Effects } from "../../../Models/Effects"
|
||||
import {
|
||||
OldConfigSpec,
|
||||
matchOldConfigSpec,
|
||||
transformConfigSpec,
|
||||
transformOldConfigToNew,
|
||||
} from "./transformConfigSpec"
|
||||
|
||||
type Optional<A> = A | undefined | null
|
||||
function todo(): never {
|
||||
@@ -533,7 +539,9 @@ export class SystemForEmbassy implements System {
|
||||
effects: Effects,
|
||||
timeoutMs: number | null,
|
||||
): Promise<T.ConfigRes> {
|
||||
return this.getConfigUncleaned(effects, timeoutMs).then(removePointers)
|
||||
return this.getConfigUncleaned(effects, timeoutMs)
|
||||
.then(removePointers)
|
||||
.then(convertToNewConfig)
|
||||
}
|
||||
private async getConfigUncleaned(
|
||||
effects: Effects,
|
||||
@@ -1054,3 +1062,10 @@ function extractServiceInterfaceId(manifest: Manifest, specInterface: string) {
|
||||
const serviceInterfaceId = `${specInterface}-${internalPort}`
|
||||
return serviceInterfaceId
|
||||
}
|
||||
async function convertToNewConfig(value: T.ConfigRes): Promise<T.ConfigRes> {
|
||||
const valueSpec: OldConfigSpec = matchOldConfigSpec.unsafeCast(value.spec)
|
||||
const spec = transformConfigSpec(valueSpec)
|
||||
if (!value.config) return { spec, config: null }
|
||||
const config = transformOldConfigToNew(valueSpec, value.config)
|
||||
return { spec, config }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,550 @@
|
||||
import { CT } from "@start9labs/start-sdk"
|
||||
import {
|
||||
dictionary,
|
||||
object,
|
||||
anyOf,
|
||||
string,
|
||||
literals,
|
||||
array,
|
||||
number,
|
||||
boolean,
|
||||
Parser,
|
||||
deferred,
|
||||
every,
|
||||
nill,
|
||||
} from "ts-matches"
|
||||
|
||||
export function transformConfigSpec(oldSpec: OldConfigSpec): CT.InputSpec {
|
||||
return Object.entries(oldSpec).reduce((inputSpec, [key, oldVal]) => {
|
||||
let newVal: CT.ValueSpec
|
||||
|
||||
if (oldVal.type === "boolean") {
|
||||
newVal = {
|
||||
type: "toggle",
|
||||
name: oldVal.name,
|
||||
default: oldVal.default,
|
||||
description: oldVal.description || null,
|
||||
warning: oldVal.warning || null,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
}
|
||||
} else if (oldVal.type === "enum") {
|
||||
newVal = {
|
||||
type: "select",
|
||||
name: oldVal.name,
|
||||
description: oldVal.description || null,
|
||||
warning: oldVal.warning || null,
|
||||
default: oldVal.default,
|
||||
values: oldVal.values.reduce(
|
||||
(obj, curr) => ({
|
||||
...obj,
|
||||
[curr]: oldVal["value-names"][curr],
|
||||
}),
|
||||
{},
|
||||
),
|
||||
required: false,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
}
|
||||
} else if (oldVal.type === "list") {
|
||||
newVal = getListSpec(oldVal)
|
||||
} else if (oldVal.type === "number") {
|
||||
const range = Range.from(oldVal.range)
|
||||
|
||||
newVal = {
|
||||
type: "number",
|
||||
name: oldVal.name,
|
||||
default: oldVal.default || null,
|
||||
description: oldVal.description || null,
|
||||
warning: oldVal.warning || null,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
required: !oldVal.nullable,
|
||||
min: range.min
|
||||
? range.minInclusive
|
||||
? range.min
|
||||
: range.min + 1
|
||||
: null,
|
||||
max: range.max
|
||||
? range.maxInclusive
|
||||
? range.max
|
||||
: range.max - 1
|
||||
: null,
|
||||
integer: oldVal.integral,
|
||||
step: null,
|
||||
units: oldVal.units || null,
|
||||
placeholder: oldVal.placeholder || null,
|
||||
}
|
||||
} else if (oldVal.type === "object") {
|
||||
newVal = {
|
||||
type: "object",
|
||||
name: oldVal.name,
|
||||
description: oldVal.description || null,
|
||||
warning: oldVal.warning || null,
|
||||
spec: transformConfigSpec(matchOldConfigSpec.unsafeCast(oldVal.spec)),
|
||||
}
|
||||
} else if (oldVal.type === "string") {
|
||||
newVal = {
|
||||
type: "text",
|
||||
name: oldVal.name,
|
||||
default: oldVal.default || null,
|
||||
description: oldVal.description || null,
|
||||
warning: oldVal.warning || null,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
required: !oldVal.nullable,
|
||||
patterns:
|
||||
oldVal.pattern && oldVal["pattern-description"]
|
||||
? [
|
||||
{
|
||||
regex: oldVal.pattern,
|
||||
description: oldVal["pattern-description"],
|
||||
},
|
||||
]
|
||||
: [],
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
masked: oldVal.masked,
|
||||
generate: null,
|
||||
inputmode: "text",
|
||||
placeholder: oldVal.placeholder || null,
|
||||
}
|
||||
} else {
|
||||
newVal = {
|
||||
type: "union",
|
||||
name: oldVal.tag.name,
|
||||
description: oldVal.tag.description || null,
|
||||
warning: oldVal.tag.warning || null,
|
||||
variants: Object.entries(oldVal.variants).reduce(
|
||||
(obj, [id, spec]) => ({
|
||||
...obj,
|
||||
[id]: {
|
||||
name: oldVal.tag["variant-names"][id],
|
||||
spec: transformConfigSpec(matchOldConfigSpec.unsafeCast(spec)),
|
||||
},
|
||||
}),
|
||||
{} as Record<string, { name: string; spec: CT.InputSpec }>,
|
||||
),
|
||||
disabled: false,
|
||||
required: true,
|
||||
default: oldVal.default,
|
||||
immutable: false,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...inputSpec,
|
||||
[key]: newVal,
|
||||
}
|
||||
}, {} as CT.InputSpec)
|
||||
}
|
||||
|
||||
export function transformOldConfigToNew(
|
||||
spec: OldConfigSpec,
|
||||
config: Record<string, any>,
|
||||
): Record<string, any> {
|
||||
return Object.entries(spec).reduce((obj, [key, val]) => {
|
||||
let newVal = config[key]
|
||||
|
||||
if (isObject(val)) {
|
||||
newVal = transformOldConfigToNew(
|
||||
matchOldConfigSpec.unsafeCast(val.spec),
|
||||
config[key],
|
||||
)
|
||||
}
|
||||
|
||||
if (isUnion(val)) {
|
||||
const selection = config[key][val.tag.id]
|
||||
delete config[key][val.tag.id]
|
||||
|
||||
newVal = {
|
||||
selection,
|
||||
value: transformOldConfigToNew(
|
||||
matchOldConfigSpec.unsafeCast(val.variants[selection]),
|
||||
config[key],
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
if (isList(val) && isObjectList(val)) {
|
||||
newVal = (config[key] as object[]).map((obj) =>
|
||||
transformOldConfigToNew(
|
||||
matchOldConfigSpec.unsafeCast(val.spec.spec),
|
||||
obj,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
...obj,
|
||||
[key]: newVal,
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
|
||||
export function transformNewConfigToOld(
|
||||
spec: OldConfigSpec,
|
||||
config: Record<string, any>,
|
||||
): Record<string, any> {
|
||||
return Object.entries(spec).reduce((obj, [key, val]) => {
|
||||
let newVal = config[key]
|
||||
|
||||
if (isObject(val)) {
|
||||
newVal = transformNewConfigToOld(
|
||||
matchOldConfigSpec.unsafeCast(val.spec),
|
||||
config[key],
|
||||
)
|
||||
}
|
||||
|
||||
if (isUnion(val)) {
|
||||
newVal = {
|
||||
[val.tag.id]: config[key].selection,
|
||||
...transformNewConfigToOld(
|
||||
matchOldConfigSpec.unsafeCast(val.variants[config[key].selection]),
|
||||
config[key].unionSelectValue,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
if (isList(val) && isObjectList(val)) {
|
||||
newVal = (config[key] as object[]).map((obj) =>
|
||||
transformNewConfigToOld(
|
||||
matchOldConfigSpec.unsafeCast(val.spec.spec),
|
||||
obj,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
...obj,
|
||||
[key]: newVal,
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
|
||||
function getListSpec(
|
||||
oldVal: OldValueSpecList,
|
||||
): CT.ValueSpecMultiselect | CT.ValueSpecList {
|
||||
const range = Range.from(oldVal.range)
|
||||
|
||||
let partial: Omit<CT.ValueSpecList, "type" | "spec" | "default"> = {
|
||||
name: oldVal.name,
|
||||
description: oldVal.description || null,
|
||||
warning: oldVal.warning || null,
|
||||
minLength: range.min
|
||||
? range.minInclusive
|
||||
? range.min
|
||||
: range.min + 1
|
||||
: null,
|
||||
maxLength: range.max
|
||||
? range.maxInclusive
|
||||
? range.max
|
||||
: range.max - 1
|
||||
: null,
|
||||
disabled: false,
|
||||
}
|
||||
|
||||
if (isEnumList(oldVal)) {
|
||||
return {
|
||||
...partial,
|
||||
type: "multiselect",
|
||||
default: oldVal.default as string[],
|
||||
immutable: false,
|
||||
values: oldVal.spec.values.reduce(
|
||||
(obj, curr) => ({
|
||||
...obj,
|
||||
[curr]: oldVal.spec["value-names"][curr],
|
||||
}),
|
||||
{},
|
||||
),
|
||||
}
|
||||
} else if (isStringList(oldVal)) {
|
||||
return {
|
||||
...partial,
|
||||
type: "list",
|
||||
default: oldVal.default as string[],
|
||||
spec: {
|
||||
type: "text",
|
||||
patterns:
|
||||
oldVal.spec.pattern && oldVal.spec["pattern-description"]
|
||||
? [
|
||||
{
|
||||
regex: oldVal.spec.pattern,
|
||||
description: oldVal.spec["pattern-description"],
|
||||
},
|
||||
]
|
||||
: [],
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
masked: oldVal.spec.masked,
|
||||
generate: null,
|
||||
inputmode: "text",
|
||||
placeholder: oldVal.spec.placeholder || null,
|
||||
},
|
||||
}
|
||||
} else if (isObjectList(oldVal)) {
|
||||
return {
|
||||
...partial,
|
||||
type: "list",
|
||||
default: oldVal.default as Record<string, unknown>[],
|
||||
spec: {
|
||||
type: "object",
|
||||
spec: transformConfigSpec(
|
||||
matchOldConfigSpec.unsafeCast(oldVal.spec.spec),
|
||||
),
|
||||
uniqueBy: oldVal.spec["unique-by"],
|
||||
displayAs: oldVal.spec["display-as"] || null,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
throw new Error("Invalid list subtype. enum, string, and object permitted.")
|
||||
}
|
||||
}
|
||||
|
||||
function isObject(val: OldValueSpec): val is OldValueSpecObject {
|
||||
return val.type === "object"
|
||||
}
|
||||
|
||||
function isUnion(val: OldValueSpec): val is OldValueSpecUnion {
|
||||
return val.type === "union"
|
||||
}
|
||||
|
||||
function isList(val: OldValueSpec): val is OldValueSpecList {
|
||||
return val.type === "list"
|
||||
}
|
||||
|
||||
function isEnumList(
|
||||
val: OldValueSpecList,
|
||||
): val is OldValueSpecList & { subtype: "enum" } {
|
||||
return val.subtype === "enum"
|
||||
}
|
||||
|
||||
function isStringList(
|
||||
val: OldValueSpecList,
|
||||
): val is OldValueSpecList & { subtype: "string" } {
|
||||
return val.subtype === "string"
|
||||
}
|
||||
|
||||
function isObjectList(
|
||||
val: OldValueSpecList,
|
||||
): val is OldValueSpecList & { subtype: "object" } {
|
||||
if (["number", "union"].includes(val.subtype)) {
|
||||
throw new Error("Invalid list subtype. enum, string, and object permitted.")
|
||||
}
|
||||
return val.subtype === "object"
|
||||
}
|
||||
export type OldConfigSpec = Record<string, OldValueSpec>
|
||||
const [_matchOldConfigSpec, setMatchOldConfigSpec] = deferred<unknown>()
|
||||
export const matchOldConfigSpec = _matchOldConfigSpec as Parser<
|
||||
unknown,
|
||||
OldConfigSpec
|
||||
>
|
||||
export const matchOldDefaultString = anyOf(
|
||||
string,
|
||||
object({ charset: string, len: number }),
|
||||
)
|
||||
type OldDefaultString = typeof matchOldDefaultString._TYPE
|
||||
|
||||
export const matchOldValueSpecString = object(
|
||||
{
|
||||
masked: boolean,
|
||||
copyable: boolean,
|
||||
type: literals("string"),
|
||||
nullable: boolean,
|
||||
name: string,
|
||||
placeholder: string,
|
||||
pattern: string,
|
||||
"pattern-description": string,
|
||||
default: matchOldDefaultString,
|
||||
textarea: boolean,
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
[
|
||||
"placeholder",
|
||||
"pattern",
|
||||
"pattern-description",
|
||||
"default",
|
||||
"textarea",
|
||||
"description",
|
||||
"warning",
|
||||
],
|
||||
)
|
||||
|
||||
export const matchOldValueSpecNumber = object(
|
||||
{
|
||||
type: literals("number"),
|
||||
nullable: boolean,
|
||||
name: string,
|
||||
range: string,
|
||||
integral: boolean,
|
||||
default: number,
|
||||
description: string,
|
||||
warning: string,
|
||||
units: string,
|
||||
placeholder: string,
|
||||
},
|
||||
["default", "description", "warning", "units", "placeholder"],
|
||||
)
|
||||
type OldValueSpecNumber = typeof matchOldValueSpecNumber._TYPE
|
||||
|
||||
export const matchOldValueSpecBoolean = object(
|
||||
{
|
||||
type: literals("boolean"),
|
||||
default: boolean,
|
||||
name: string,
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["description", "warning"],
|
||||
)
|
||||
type OldValueSpecBoolean = typeof matchOldValueSpecBoolean._TYPE
|
||||
|
||||
const matchOldValueSpecObject = object(
|
||||
{
|
||||
type: literals("object"),
|
||||
spec: _matchOldConfigSpec,
|
||||
name: string,
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["description", "warning"],
|
||||
)
|
||||
type OldValueSpecObject = typeof matchOldValueSpecObject._TYPE
|
||||
|
||||
const matchOldValueSpecEnum = object(
|
||||
{
|
||||
values: array(string),
|
||||
"value-names": dictionary([string, string]),
|
||||
type: literals("enum"),
|
||||
default: string,
|
||||
name: string,
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["description", "warning"],
|
||||
)
|
||||
type OldValueSpecEnum = typeof matchOldValueSpecEnum._TYPE
|
||||
|
||||
const matchOldUnionTagSpec = object(
|
||||
{
|
||||
id: string, // The name of the field containing one of the union variants
|
||||
"variant-names": dictionary([string, string]), // The name of each variant
|
||||
name: string,
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["description", "warning"],
|
||||
)
|
||||
const matchOldValueSpecUnion = object({
|
||||
type: literals("union"),
|
||||
tag: matchOldUnionTagSpec,
|
||||
variants: dictionary([string, _matchOldConfigSpec]),
|
||||
default: string,
|
||||
})
|
||||
type OldValueSpecUnion = typeof matchOldValueSpecUnion._TYPE
|
||||
|
||||
const [matchOldUniqueBy, setOldUniqueBy] = deferred<OldUniqueBy>()
|
||||
type OldUniqueBy =
|
||||
| null
|
||||
| string
|
||||
| { any: OldUniqueBy[] }
|
||||
| { all: OldUniqueBy[] }
|
||||
|
||||
setOldUniqueBy(
|
||||
anyOf(
|
||||
nill,
|
||||
string,
|
||||
object({ any: array(matchOldUniqueBy) }),
|
||||
object({ all: array(matchOldUniqueBy) }),
|
||||
),
|
||||
)
|
||||
|
||||
const matchOldListValueSpecObject = object(
|
||||
{
|
||||
spec: _matchOldConfigSpec, // this is a mapped type of the config object at this level, replacing the object's values with specs on those values
|
||||
"unique-by": matchOldUniqueBy, // indicates whether duplicates can be permitted in the list
|
||||
"display-as": string, // this should be a handlebars template which can make use of the entire config which corresponds to 'spec'
|
||||
},
|
||||
["display-as"],
|
||||
)
|
||||
const matchOldListValueSpecString = object(
|
||||
{
|
||||
masked: boolean,
|
||||
copyable: boolean,
|
||||
pattern: string,
|
||||
"pattern-description": string,
|
||||
placeholder: string,
|
||||
},
|
||||
["pattern", "pattern-description", "placeholder"],
|
||||
)
|
||||
|
||||
const matchOldListValueSpecEnum = object({
|
||||
values: array(string),
|
||||
"value-names": dictionary([string, string]),
|
||||
})
|
||||
|
||||
// represents a spec for a list
|
||||
const matchOldValueSpecList = every(
|
||||
object(
|
||||
{
|
||||
type: literals("list"),
|
||||
range: string, // '[0,1]' (inclusive) OR '[0,*)' (right unbounded), normal math rules
|
||||
default: anyOf(
|
||||
array(string),
|
||||
array(number),
|
||||
array(matchOldDefaultString),
|
||||
array(object),
|
||||
),
|
||||
name: string,
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["description", "warning"],
|
||||
),
|
||||
anyOf(
|
||||
object({
|
||||
subtype: literals("string"),
|
||||
spec: matchOldListValueSpecString,
|
||||
}),
|
||||
object({
|
||||
subtype: literals("enum"),
|
||||
spec: matchOldListValueSpecEnum,
|
||||
}),
|
||||
object({
|
||||
subtype: literals("object"),
|
||||
spec: matchOldListValueSpecObject,
|
||||
}),
|
||||
),
|
||||
)
|
||||
type OldValueSpecList = typeof matchOldValueSpecList._TYPE
|
||||
|
||||
export const matchOldValueSpec = anyOf(
|
||||
matchOldValueSpecString,
|
||||
matchOldValueSpecNumber,
|
||||
matchOldValueSpecBoolean,
|
||||
matchOldValueSpecObject,
|
||||
matchOldValueSpecEnum,
|
||||
matchOldValueSpecList,
|
||||
matchOldValueSpecUnion,
|
||||
)
|
||||
type OldValueSpec = typeof matchOldValueSpec._TYPE
|
||||
|
||||
setMatchOldConfigSpec(dictionary([string, matchOldValueSpec]))
|
||||
|
||||
export class Range {
|
||||
min?: number
|
||||
max?: number
|
||||
minInclusive!: boolean
|
||||
maxInclusive!: boolean
|
||||
|
||||
static from(s: string = "(*,*)"): Range {
|
||||
const r = new Range()
|
||||
r.minInclusive = s.startsWith("[")
|
||||
r.maxInclusive = s.endsWith("]")
|
||||
const [minStr, maxStr] = s.split(",").map((a) => a.trim())
|
||||
r.min = minStr === "(*" ? undefined : Number(minStr.slice(1))
|
||||
r.max = maxStr === "*)" ? undefined : Number(maxStr.slice(0, -1))
|
||||
return r
|
||||
}
|
||||
}
|
||||
705
core/Cargo.lock
generated
@@ -12,7 +12,7 @@ color-eyre = "0.6.2"
|
||||
ed25519-dalek = { version = "2.0.0", features = ["serde"] }
|
||||
lazy_static = "1.4"
|
||||
mbrman = "0.5.2"
|
||||
emver = { version = "0.1", git = "https://github.com/Start9Labs/emver-rs.git", features = [
|
||||
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
||||
"serde",
|
||||
] }
|
||||
ipnet = "2.8.0"
|
||||
|
||||
@@ -242,8 +242,8 @@ impl From<std::string::FromUtf8Error> for Error {
|
||||
Error::new(e, ErrorKind::Utf8)
|
||||
}
|
||||
}
|
||||
impl From<emver::ParseError> for Error {
|
||||
fn from(e: emver::ParseError) -> Self {
|
||||
impl From<exver::ParseError> for Error {
|
||||
fn from(e: exver::ParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,14 @@ use ts_rs::TS;
|
||||
#[derive(Debug, Clone, TS)]
|
||||
#[ts(type = "string", rename = "Version")]
|
||||
pub struct VersionString {
|
||||
version: emver::Version,
|
||||
version: exver::ExtendedVersion,
|
||||
string: String,
|
||||
}
|
||||
impl VersionString {
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.string.as_str()
|
||||
}
|
||||
pub fn into_version(self) -> emver::Version {
|
||||
pub fn into_version(self) -> exver::ExtendedVersion {
|
||||
self.version
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ impl std::fmt::Display for VersionString {
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for VersionString {
|
||||
type Err = <emver::Version as FromStr>::Err;
|
||||
type Err = <exver::ExtendedVersion as FromStr>::Err;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(VersionString {
|
||||
string: s.to_owned(),
|
||||
@@ -33,32 +33,32 @@ impl std::str::FromStr for VersionString {
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<emver::Version> for VersionString {
|
||||
fn from(v: emver::Version) -> Self {
|
||||
impl From<exver::ExtendedVersion> for VersionString {
|
||||
fn from(v: exver::ExtendedVersion) -> Self {
|
||||
VersionString {
|
||||
string: v.to_string(),
|
||||
version: v,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<VersionString> for emver::Version {
|
||||
impl From<VersionString> for exver::ExtendedVersion {
|
||||
fn from(v: VersionString) -> Self {
|
||||
v.version
|
||||
}
|
||||
}
|
||||
impl Default for VersionString {
|
||||
fn default() -> Self {
|
||||
Self::from(emver::Version::default())
|
||||
Self::from(exver::ExtendedVersion::default())
|
||||
}
|
||||
}
|
||||
impl Deref for VersionString {
|
||||
type Target = emver::Version;
|
||||
type Target = exver::ExtendedVersion;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.version
|
||||
}
|
||||
}
|
||||
impl AsRef<emver::Version> for VersionString {
|
||||
fn as_ref(&self) -> &emver::Version {
|
||||
impl AsRef<exver::ExtendedVersion> for VersionString {
|
||||
fn as_ref(&self) -> &exver::ExtendedVersion {
|
||||
&self.version
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,13 @@ impl PartialOrd for VersionString {
|
||||
}
|
||||
impl Ord for VersionString {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.version.cmp(&other.version)
|
||||
self.version.partial_cmp(&other.version).unwrap_or_else(|| {
|
||||
match (self.version.flavor(), other.version.flavor()) {
|
||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||
(Some(_), None) => std::cmp::Ordering::Less,
|
||||
(a, b) => a.cmp(&b),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Hash for VersionString {
|
||||
@@ -94,7 +100,8 @@ impl<'de> Deserialize<'de> for VersionString {
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let string = String::deserialize(deserializer)?;
|
||||
let version = emver::Version::from_str(&string).map_err(::serde::de::Error::custom)?;
|
||||
let version =
|
||||
exver::ExtendedVersion::from_str(&string).map_err(::serde::de::Error::custom)?;
|
||||
Ok(Self { string, version })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO network_keys (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1ce5254f27de971fd87f5ab66d300f2b22433c86617a0dbf796bf2170186dd2e"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM ssh_keys WHERE fingerprint = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "21471490cdc3adb206274cc68e1ea745ffa5da4479478c1fd2158a45324b1930"
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT hostname, path, username, password FROM cifs_shares WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "hostname",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "path",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "username",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "password",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "28ea34bbde836e0618c5fc9bb7c36e463c20c841a7d6a0eb15be0f24f4a928ec"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM tor WHERE package = $1 AND interface = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "350ab82048fb4a049042e4fdbe1b8c606ca400e43e31b9a05d2937217e0f6962"
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM ssh_keys WHERE fingerprint = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "fingerprint",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "openssh_pubkey",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_at",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4099028a5c0de578255bf54a67cef6cb0f1e9a4e158260700f1639dd4b438997"
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM session WHERE logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "logged_in",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "logged_out",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "last_active",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "user_agent",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "metadata",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4691e3a2ce80b59009ac17124f54f925f61dc5ea371903e62cdffa5d7b67ca96"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE session SET logged_out = CURRENT_TIMESTAMP WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4bcfbefb1eb3181343871a1cd7fc3afb81c2be5c681cfa8b4be0ce70610e9c3a"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT password FROM account",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "password",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "629be61c3c341c131ddbbff0293a83dbc6afd07cae69d246987f62cf0cc35c2a"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT key FROM tor WHERE package = $1 AND interface = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "key",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "687688055e63d27123cdc89a5bbbd8361776290a9411d527eaf1fdb40bef399d"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE session SET last_active = CURRENT_TIMESTAMP WHERE id = $1 AND logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6d35ccf780fb2bb62586dd1d3df9c1550a41ee580dad3f49d35cb843ebef10ca"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO network_keys (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO UPDATE SET package = EXCLUDED.package RETURNING key",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "key",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "770c1017734720453dc87b58c385b987c5af5807151ff71a59000014586752e0"
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications WHERE id < $1 ORDER BY id DESC LIMIT $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "package_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "code",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "level",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "title",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "message",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "data",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "7b64f032d507e8ffe37c41f4c7ad514a66c421a11ab04c26d89a7aa8f6b67210"
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO account (\n id,\n server_id,\n hostname,\n password,\n network_key,\n root_ca_key_pem,\n root_ca_cert_pem\n ) VALUES (\n 0, $1, $2, $3, $4, $5, $6\n ) ON CONFLICT (id) DO UPDATE SET\n server_id = EXCLUDED.server_id,\n hostname = EXCLUDED.hostname,\n password = EXCLUDED.password,\n network_key = EXCLUDED.network_key,\n root_ca_key_pem = EXCLUDED.root_ca_key_pem,\n root_ca_cert_pem = EXCLUDED.root_ca_cert_pem\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bytea",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7c7a3549c997eb75bf964ea65fbb98a73045adf618696cd838d79203ef5383fb"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM tor WHERE package = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7e0649d839927e57fa03ee51a2c9f96a8bdb0fc97ee8a3c6df1069e1e2b98576"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8951b9126fbf60dbb5997241e11e3526b70bccf3e407327917294a993bc17ed5"
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications ORDER BY id DESC LIMIT $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "package_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "code",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "level",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "title",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "message",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "data",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "94d471bb374b4965c6cbedf8c17bbf6bea226d38efaf6559923c79a36d5ca08c"
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, hostname, path, username, password FROM cifs_shares",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "hostname",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "path",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "username",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "password",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "95c4ab4c645f3302568c6ff13d85ab58252362694cf0f56999bf60194d20583a"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM cifs_shares WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a60d6e66719325b08dc4ecfacaf337527233c84eee758ac9be967906e5841d27"
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT fingerprint, openssh_pubkey, created_at FROM ssh_keys",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "fingerprint",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "openssh_pubkey",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_at",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a6b0c8909a3a5d6d9156aebfb359424e6b5a1d1402e028219e21726f1ebd282e"
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE cifs_shares SET hostname = $1, path = $2, username = $3, password = $4 WHERE id = $5",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b1147beaaabbed89f2ab8c1e13ec4393a9a8fde2833cf096af766a979d94dee6"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM network_keys WHERE package = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b203820ee1c553a4b246eac74b79bd10d5717b2a0ddecf22330b7d531aac7c5d"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE account SET tor_key = NULL, network_key = gen_random_bytes(32)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b81592b3a74940ab56d41537484090d45cfa4c85168a587b1a41dc5393cccea1"
|
||||
}
|
||||
16
core/startos/.sqlx/query-bc9382d34bf93f468c64d0d02613452e7a69768da179e78479cd35ee42b493ae.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO user_activity (created_at, server_id, arch) VALUES ($1, $2, $3)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Timestamptz",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "bc9382d34bf93f468c64d0d02613452e7a69768da179e78479cd35ee42b493ae"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT openssh_pubkey FROM ssh_keys",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "openssh_pubkey",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d5117054072476377f3c4f040ea429d4c9b2cf534e76f35c80a2bf60e8599cca"
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO notifications (package_id, code, level, title, message, data) VALUES ($1, $2, $3, $4, $5, $6)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "da71f94b29798d1738d2b10b9a721ea72db8cfb362e7181c8226d9297507c62b"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM network_keys WHERE package = $1 AND interface = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "dfc23b7e966c3853284753a7e934351ba0cae3825988b3e0ecd3b6781bcff524"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM notifications WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e185203cf84e43b801dfb23b4159e34aeaef1154dcd3d6811ab504915497ccf7"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT tor_key FROM account WHERE id = 0",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "tor_key",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "e545696735f202f9d13cf22a561f3ff3f9aed7f90027a9ba97634bcb47d772f0"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO session (id, user_agent, metadata) VALUES ($1, $2, $3)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e5843c5b0e7819b29aa1abf2266799bd4f82e761837b526a0972c3d4439a264d"
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n network_keys.package,\n network_keys.interface,\n network_keys.key,\n tor.key AS \"tor_key?\"\n FROM\n network_keys\n LEFT JOIN\n tor\n ON\n network_keys.package = tor.package\n AND\n network_keys.interface = tor.interface\n WHERE\n network_keys.package = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "package",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "interface",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "key",
|
||||
"type_info": "Bytea"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "tor_key?",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e95322a8e2ae3b93f1e974b24c0b81803f1e9ec9e8ebbf15cafddfc1c5a028ed"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM notifications WHERE id < $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "eb750adaa305bdbf3c5b70aaf59139c7b7569602adb58f2d6b3a94da4f167b0a"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO cifs_shares (hostname, path, username, password) VALUES ($1, $2, $3, $4) RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ecc765d8205c0876956f95f76944ac6a5f34dd820c4073b7728c7067aab9fded"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO ssh_keys (fingerprint, openssh_pubkey, created_at) VALUES ($1, $2, $3)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f6d1c5ef0f9d9577bea8382318967b9deb46da75788c7fe6082b43821c22d556"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT network_key FROM account WHERE id = 0",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "network_key",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f7d2dae84613bcef330f7403352cc96547f3f6dbec11bf2eadfaf53ad8ab51b5"
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM account WHERE id = 0",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "password",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "tor_key",
|
||||
"type_info": "Bytea"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "server_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "hostname",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "network_key",
|
||||
"type_info": "Bytea"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "root_ca_key_pem",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "root_ca_cert_pem",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "fe6e4f09f3028e5b6b6259e86cbad285680ce157aae9d7837ac020c8b2945e7f"
|
||||
}
|
||||
@@ -42,7 +42,7 @@ cli = []
|
||||
container-runtime = []
|
||||
daemon = []
|
||||
registry = []
|
||||
default = ["cli", "daemon", "registry"]
|
||||
default = ["cli", "daemon"]
|
||||
dev = []
|
||||
unstable = ["console-subscriber", "tokio/tracing"]
|
||||
docker = []
|
||||
@@ -86,7 +86,7 @@ ed25519-dalek = { version = "2.1.1", features = [
|
||||
"pkcs8",
|
||||
] }
|
||||
ed25519-dalek-v1 = { package = "ed25519-dalek", version = "1" }
|
||||
emver = { version = "0.1.7", git = "https://github.com/Start9Labs/emver-rs.git", features = [
|
||||
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
||||
"serde",
|
||||
] }
|
||||
fd-lock-rs = "0.1.4"
|
||||
@@ -97,6 +97,7 @@ hex = "0.4.3"
|
||||
hmac = "0.12.1"
|
||||
http = "1.0.0"
|
||||
http-body-util = "0.1"
|
||||
hyper-util = { version = "0.1.5", features = ["tokio", "service"] }
|
||||
id-pool = { version = "0.2.2", default-features = false, features = [
|
||||
"serde",
|
||||
"u16",
|
||||
@@ -159,6 +160,7 @@ serde_yaml = { package = "serde_yml", version = "0.0.10" }
|
||||
sha2 = "0.10.2"
|
||||
shell-words = "1"
|
||||
simple-logging = "2.0.2"
|
||||
socket2 = "0.5.7"
|
||||
sqlx = { version = "0.7.2", features = [
|
||||
"chrono",
|
||||
"runtime-tokio-rustls",
|
||||
|
||||
@@ -28,6 +28,7 @@ pub struct AccountInfo {
|
||||
pub root_ca_key: PKey<Private>,
|
||||
pub root_ca_cert: X509,
|
||||
pub ssh_key: ssh_key::PrivateKey,
|
||||
pub compat_s9pk_key: ed25519_dalek::SigningKey,
|
||||
}
|
||||
impl AccountInfo {
|
||||
pub fn new(password: &str, start_time: SystemTime) -> Result<Self, Error> {
|
||||
@@ -39,6 +40,7 @@ impl AccountInfo {
|
||||
let ssh_key = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::random(
|
||||
&mut rand::thread_rng(),
|
||||
));
|
||||
let compat_s9pk_key = ed25519_dalek::SigningKey::generate(&mut rand::thread_rng());
|
||||
Ok(Self {
|
||||
server_id,
|
||||
hostname,
|
||||
@@ -47,6 +49,7 @@ impl AccountInfo {
|
||||
root_ca_key,
|
||||
root_ca_cert,
|
||||
ssh_key,
|
||||
compat_s9pk_key,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,6 +64,7 @@ impl AccountInfo {
|
||||
let root_ca_key = cert_store.as_root_key().de()?.0;
|
||||
let root_ca_cert = cert_store.as_root_cert().de()?.0;
|
||||
let ssh_key = db.as_private().as_ssh_privkey().de()?.0;
|
||||
let compat_s9pk_key = db.as_private().as_compat_s9pk_key().de()?.0;
|
||||
|
||||
Ok(Self {
|
||||
server_id,
|
||||
@@ -70,6 +74,7 @@ impl AccountInfo {
|
||||
root_ca_key,
|
||||
root_ca_cert,
|
||||
ssh_key,
|
||||
compat_s9pk_key,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -92,6 +97,9 @@ impl AccountInfo {
|
||||
db.as_private_mut()
|
||||
.as_ssh_privkey_mut()
|
||||
.ser(Pem::new_ref(&self.ssh_key))?;
|
||||
db.as_private_mut()
|
||||
.as_compat_s9pk_key_mut()
|
||||
.ser(Pem::new_ref(&self.compat_s9pk_key))?;
|
||||
let key_store = db.as_private_mut().as_key_store_mut();
|
||||
key_store.as_onion_mut().insert_key(&self.tor_key)?;
|
||||
let cert_store = key_store.as_local_certs_mut();
|
||||
|
||||
@@ -85,6 +85,7 @@ impl OsBackupV0 {
|
||||
ssh_key::Algorithm::Ed25519,
|
||||
)?,
|
||||
tor_key: TorSecretKeyV3::from(self.tor_key.0),
|
||||
compat_s9pk_key: ed25519_dalek::SigningKey::generate(&mut rand::thread_rng()),
|
||||
},
|
||||
ui: self.ui,
|
||||
})
|
||||
@@ -113,6 +114,7 @@ impl OsBackupV1 {
|
||||
root_ca_cert: self.root_ca_cert.0,
|
||||
ssh_key: ssh_key::PrivateKey::from(Ed25519Keypair::from_seed(&self.net_key.0)),
|
||||
tor_key: TorSecretKeyV3::from(ed25519_expand_key(&self.net_key.0)),
|
||||
compat_s9pk_key: ed25519_dalek::SigningKey::from_bytes(&self.net_key),
|
||||
},
|
||||
ui: self.ui,
|
||||
}
|
||||
@@ -124,13 +126,14 @@ impl OsBackupV1 {
|
||||
#[serde(rename = "kebab-case")]
|
||||
|
||||
struct OsBackupV2 {
|
||||
server_id: String, // uuidv4
|
||||
hostname: String, // <adjective>-<noun>
|
||||
root_ca_key: Pem<PKey<Private>>, // PEM Encoded OpenSSL Key
|
||||
root_ca_cert: Pem<X509>, // PEM Encoded OpenSSL X509 Certificate
|
||||
ssh_key: Pem<ssh_key::PrivateKey>, // PEM Encoded OpenSSH Key
|
||||
tor_key: TorSecretKeyV3, // Base64 Encoded Ed25519 Expanded Secret Key
|
||||
ui: Value, // JSON Value
|
||||
server_id: String, // uuidv4
|
||||
hostname: String, // <adjective>-<noun>
|
||||
root_ca_key: Pem<PKey<Private>>, // PEM Encoded OpenSSL Key
|
||||
root_ca_cert: Pem<X509>, // PEM Encoded OpenSSL X509 Certificate
|
||||
ssh_key: Pem<ssh_key::PrivateKey>, // PEM Encoded OpenSSH Key
|
||||
tor_key: TorSecretKeyV3, // Base64 Encoded Ed25519 Expanded Secret Key
|
||||
compat_s9pk_key: Pem<ed25519_dalek::SigningKey>, // PEM Encoded ED25519 Key
|
||||
ui: Value, // JSON Value
|
||||
}
|
||||
impl OsBackupV2 {
|
||||
fn project(self) -> OsBackup {
|
||||
@@ -143,6 +146,7 @@ impl OsBackupV2 {
|
||||
root_ca_cert: self.root_ca_cert.0,
|
||||
ssh_key: self.ssh_key.0,
|
||||
tor_key: self.tor_key,
|
||||
compat_s9pk_key: self.compat_s9pk_key.0,
|
||||
},
|
||||
ui: self.ui,
|
||||
}
|
||||
@@ -155,6 +159,7 @@ impl OsBackupV2 {
|
||||
root_ca_cert: Pem(backup.account.root_ca_cert.clone()),
|
||||
ssh_key: Pem(backup.account.ssh_key.clone()),
|
||||
tor_key: backup.account.tor_key.clone(),
|
||||
compat_s9pk_key: Pem(backup.account.compat_s9pk_key.clone()),
|
||||
ui: backup.ui.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,16 +156,14 @@ async fn restore_packages(
|
||||
let mut tasks = BTreeMap::new();
|
||||
for id in ids {
|
||||
let backup_dir = backup_guard.clone().package_backup(&id);
|
||||
let s9pk_path = backup_dir.path().join(&id).with_extension("s9pk");
|
||||
let task = ctx
|
||||
.services
|
||||
.install(
|
||||
ctx.clone(),
|
||||
S9pk::open(
|
||||
backup_dir.path().join(&id).with_extension("s9pk"),
|
||||
Some(&id),
|
||||
)
|
||||
.await?,
|
||||
|| S9pk::open(s9pk_path, Some(&id)),
|
||||
Some(backup_dir),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tasks.insert(id, task);
|
||||
|
||||
@@ -7,6 +7,7 @@ use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::OutputSizeUser;
|
||||
use exver::Version;
|
||||
use models::PackageId;
|
||||
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -194,7 +195,7 @@ pub async fn list(ctx: RpcContext) -> Result<BTreeMap<BackupTargetId, BackupTarg
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BackupInfo {
|
||||
pub version: VersionString,
|
||||
pub version: Version,
|
||||
pub timestamp: Option<DateTime<Utc>>,
|
||||
pub package_backups: BTreeMap<PackageId, PackageBackupInfo>,
|
||||
}
|
||||
@@ -204,7 +205,7 @@ pub struct BackupInfo {
|
||||
pub struct PackageBackupInfo {
|
||||
pub title: String,
|
||||
pub version: VersionString,
|
||||
pub os_version: VersionString,
|
||||
pub os_version: Version,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
@@ -223,9 +224,9 @@ fn display_backup_info(params: WithIoFormat<InfoParams>, info: BackupInfo) {
|
||||
"TIMESTAMP",
|
||||
]);
|
||||
table.add_row(row![
|
||||
"EMBASSY OS",
|
||||
info.version.as_str(),
|
||||
info.version.as_str(),
|
||||
"StartOS",
|
||||
&info.version.to_string(),
|
||||
&info.version.to_string(),
|
||||
&if let Some(ts) = &info.timestamp {
|
||||
ts.to_string()
|
||||
} else {
|
||||
@@ -236,7 +237,7 @@ fn display_backup_info(params: WithIoFormat<InfoParams>, info: BackupInfo) {
|
||||
let row = row![
|
||||
&*id,
|
||||
info.version.as_str(),
|
||||
info.os_version.as_str(),
|
||||
&info.os_version.to_string(),
|
||||
&info.timestamp.to_string(),
|
||||
];
|
||||
table.add_row(row);
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::rpc_continuations::Guid;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CliContextSeed {
|
||||
pub runtime: OnceCell<Runtime>,
|
||||
pub runtime: OnceCell<Arc<Runtime>>,
|
||||
pub base_url: Url,
|
||||
pub rpc_url: Url,
|
||||
pub registry_url: Option<Url>,
|
||||
@@ -43,7 +43,9 @@ impl Drop for CliContextSeed {
|
||||
std::fs::create_dir_all(&parent_dir).unwrap();
|
||||
}
|
||||
let mut writer = fd_lock_rs::FdLock::lock(
|
||||
File::create(&tmp).unwrap(),
|
||||
File::create(&tmp)
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, &tmp))
|
||||
.unwrap(),
|
||||
fd_lock_rs::LockType::Exclusive,
|
||||
true,
|
||||
)
|
||||
@@ -80,9 +82,12 @@ impl CliContext {
|
||||
});
|
||||
let cookie_store = Arc::new(CookieStoreMutex::new({
|
||||
let mut store = if cookie_path.exists() {
|
||||
CookieStore::load_json(BufReader::new(File::open(&cookie_path)?))
|
||||
.map_err(|e| eyre!("{}", e))
|
||||
.with_kind(crate::ErrorKind::Deserialization)?
|
||||
CookieStore::load_json(BufReader::new(
|
||||
File::open(&cookie_path)
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, cookie_path.display()))?,
|
||||
))
|
||||
.map_err(|e| eyre!("{}", e))
|
||||
.with_kind(crate::ErrorKind::Deserialization)?
|
||||
} else {
|
||||
CookieStore::default()
|
||||
};
|
||||
@@ -249,16 +254,19 @@ impl std::ops::Deref for CliContext {
|
||||
}
|
||||
}
|
||||
impl Context for CliContext {
|
||||
fn runtime(&self) -> tokio::runtime::Handle {
|
||||
self.runtime
|
||||
.get_or_init(|| {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
.handle()
|
||||
.clone()
|
||||
fn runtime(&self) -> Option<Arc<Runtime>> {
|
||||
Some(
|
||||
self.runtime
|
||||
.get_or_init(|| {
|
||||
Arc::new(
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl CallRemote<RpcContext> for CliContext {
|
||||
@@ -290,7 +298,7 @@ impl CallRemote<InstallContext> for CliContext {
|
||||
#[test]
|
||||
fn test() {
|
||||
let ctx = CliContext::init(ClientConfig::default()).unwrap();
|
||||
ctx.runtime().block_on(async {
|
||||
ctx.runtime().unwrap().block_on(async {
|
||||
reqwest::Client::new()
|
||||
.get("http://example.com")
|
||||
.send()
|
||||
|
||||
@@ -37,7 +37,10 @@ pub trait ContextConfig: DeserializeOwned + Default {
|
||||
.map(|f| f.parse())
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
format.from_reader(File::open(path)?)
|
||||
format.from_reader(
|
||||
File::open(path.as_ref())
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, path.as_ref().display()))?,
|
||||
)
|
||||
}
|
||||
fn load_path_rec(&mut self, path: Option<impl AsRef<Path>>) -> Result<(), Error> {
|
||||
if let Some(path) = path.filter(|p| p.as_ref().exists()) {
|
||||
|
||||
@@ -57,6 +57,7 @@ pub struct RpcContextSeed {
|
||||
pub client: Client,
|
||||
pub hardware: Hardware,
|
||||
pub start_time: Instant,
|
||||
#[cfg(feature = "dev")]
|
||||
pub dev: Dev,
|
||||
}
|
||||
|
||||
@@ -249,6 +250,7 @@ impl RpcContext {
|
||||
.with_kind(crate::ErrorKind::ParseUrl)?,
|
||||
hardware: Hardware { devices, ram },
|
||||
start_time: Instant::now(),
|
||||
#[cfg(feature = "dev")]
|
||||
dev: Dev {
|
||||
lxc: Mutex::new(BTreeMap::new()),
|
||||
},
|
||||
|
||||
@@ -40,6 +40,7 @@ impl Database {
|
||||
notifications: Notifications::new(),
|
||||
cifs: CifsTargets::new(),
|
||||
package_stores: BTreeMap::new(),
|
||||
compat_s9pk_key: Pem(account.compat_s9pk_key.clone()),
|
||||
}, // TODO
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use emver::VersionRange;
|
||||
use exver::VersionRange;
|
||||
use imbl_value::InternedString;
|
||||
use models::{ActionId, DataUrl, HealthCheckId, HostId, PackageId, ServiceInterfaceId};
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
|
||||
@@ -19,6 +19,8 @@ use crate::util::serde::Pem;
|
||||
pub struct Private {
|
||||
pub key_store: KeyStore,
|
||||
pub password: String, // argon2 hash
|
||||
#[serde(default = "generate_compat_key")]
|
||||
pub compat_s9pk_key: Pem<ed25519_dalek::SigningKey>,
|
||||
pub ssh_privkey: Pem<ssh_key::PrivateKey>,
|
||||
pub ssh_pubkeys: SshKeys,
|
||||
pub available_ports: AvailablePorts,
|
||||
@@ -28,3 +30,7 @@ pub struct Private {
|
||||
#[serde(default)]
|
||||
pub package_stores: BTreeMap<PackageId, Value>,
|
||||
}
|
||||
|
||||
fn generate_compat_key() -> Pem<ed25519_dalek::SigningKey> {
|
||||
Pem(ed25519_dalek::SigningKey::generate(&mut rand::thread_rng()))
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use emver::VersionRange;
|
||||
use exver::{Version, VersionRange};
|
||||
use imbl_value::InternedString;
|
||||
use ipnet::{Ipv4Net, Ipv6Net};
|
||||
use isocountry::CountryCode;
|
||||
@@ -21,7 +21,6 @@ use crate::net::utils::{get_iface_ipv4_addr, get_iface_ipv6_addr};
|
||||
use crate::prelude::*;
|
||||
use crate::progress::FullProgress;
|
||||
use crate::util::cpupower::Governor;
|
||||
use crate::util::VersionString;
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::{ARCH, PLATFORM};
|
||||
|
||||
@@ -43,7 +42,7 @@ impl Public {
|
||||
arch: get_arch(),
|
||||
platform: get_platform(),
|
||||
id: account.server_id.clone(),
|
||||
version: Current::new().semver().into(),
|
||||
version: Current::new().semver(),
|
||||
hostname: account.hostname.no_dot_host_name(),
|
||||
last_backup: None,
|
||||
eos_version_compat: Current::new().compat().clone(),
|
||||
@@ -109,7 +108,8 @@ pub struct ServerInfo {
|
||||
pub platform: InternedString,
|
||||
pub id: String,
|
||||
pub hostname: String,
|
||||
pub version: VersionString,
|
||||
#[ts(type = "string")]
|
||||
pub version: Version,
|
||||
#[ts(type = "string | null")]
|
||||
pub last_backup: Option<DateTime<Utc>>,
|
||||
#[ts(type = "string")]
|
||||
|
||||
@@ -8,8 +8,8 @@ use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::CliContext;
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::Pem;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn init(ctx: CliContext) -> Result<(), Error> {
|
||||
@@ -26,7 +26,8 @@ pub fn init(ctx: CliContext) -> Result<(), Error> {
|
||||
secret_key: secret.to_bytes(),
|
||||
public_key: Some(PublicKeyBytes(VerifyingKey::from(&secret).to_bytes())),
|
||||
};
|
||||
let mut dev_key_file = File::create(&ctx.developer_key_path)?;
|
||||
let mut dev_key_file = File::create(&ctx.developer_key_path)
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, ctx.developer_key_path.display()))?;
|
||||
dev_key_file.write_all(
|
||||
keypair_bytes
|
||||
.to_pkcs8_pem(base64ct::LineEnding::default())
|
||||
|
||||
@@ -102,10 +102,10 @@ fn display_disk_info(params: WithIoFormat<Empty>, args: Vec<DiskInfo>) {
|
||||
} else {
|
||||
"N/A"
|
||||
},
|
||||
if let Some(eos) = part.start_os.as_ref() {
|
||||
eos.version.as_str()
|
||||
&if let Some(eos) = part.start_os.as_ref() {
|
||||
eos.version.to_string()
|
||||
} else {
|
||||
"N/A"
|
||||
"N/A".to_owned()
|
||||
},
|
||||
];
|
||||
table.add_row(row);
|
||||
|
||||
@@ -20,7 +20,7 @@ use super::mount::guard::TmpMountGuard;
|
||||
use crate::disk::mount::guard::GenericMountGuard;
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::util::{Invoke, VersionString};
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ResultExt as _};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||
@@ -56,7 +56,7 @@ pub struct PartitionInfo {
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EmbassyOsRecoveryInfo {
|
||||
pub version: VersionString,
|
||||
pub version: exver::Version,
|
||||
pub full: bool,
|
||||
pub password_hash: Option<String>,
|
||||
pub wrapped_key: Option<String>,
|
||||
|
||||
@@ -3,13 +3,12 @@ use std::path::Path;
|
||||
|
||||
use async_compression::tokio::bufread::GzipDecoder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::BufReader;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::disk::fsck::RequiresReboot;
|
||||
use crate::prelude::*;
|
||||
use crate::progress::PhaseProgressTrackerHandle;
|
||||
use crate::util::io::open_file;
|
||||
use crate::util::Invoke;
|
||||
use crate::PLATFORM;
|
||||
|
||||
@@ -134,7 +133,7 @@ pub async fn update_firmware(firmware: Firmware) -> Result<(), Error> {
|
||||
.invoke(ErrorKind::Filesystem)
|
||||
.await?;
|
||||
let mut rdr = if tokio::fs::metadata(&firmware_path).await.is_ok() {
|
||||
GzipDecoder::new(BufReader::new(File::open(&firmware_path).await?))
|
||||
GzipDecoder::new(BufReader::new(open_file(&firmware_path).await?))
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("Firmware {id}.rom.gz not found in {firmware_dir:?}"),
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use axum::extract::ws::{self, CloseFrame};
|
||||
use axum::extract::ws::{self};
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use itertools::Itertools;
|
||||
@@ -31,7 +31,7 @@ use crate::progress::{
|
||||
};
|
||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||
use crate::ssh::SSH_AUTHORIZED_KEYS_FILE;
|
||||
use crate::util::io::IOHook;
|
||||
use crate::util::io::{create_file, IOHook};
|
||||
use crate::util::net::WebSocketExt;
|
||||
use crate::util::{cpupower, Invoke};
|
||||
use crate::Error;
|
||||
@@ -138,10 +138,7 @@ pub async fn init_postgres(datadir: impl AsRef<Path>) -> Result<(), Error> {
|
||||
old_version -= 1;
|
||||
let old_datadir = db_dir.join(old_version.to_string());
|
||||
if tokio::fs::metadata(&old_datadir).await.is_ok() {
|
||||
tokio::fs::File::create(&incomplete_path)
|
||||
.await?
|
||||
.sync_all()
|
||||
.await?;
|
||||
create_file(&incomplete_path).await?.sync_all().await?;
|
||||
Command::new("pg_upgradecluster")
|
||||
.arg(old_version.to_string())
|
||||
.arg("main")
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::builder::ValueParserFactory;
|
||||
use clap::{value_parser, CommandFactory, FromArgMatches, Parser};
|
||||
use color_eyre::eyre::eyre;
|
||||
use emver::VersionRange;
|
||||
use futures::StreamExt;
|
||||
use imbl_value::InternedString;
|
||||
use exver::VersionRange;
|
||||
use futures::{AsyncWriteExt, StreamExt};
|
||||
use imbl_value::{json, InternedString};
|
||||
use itertools::Itertools;
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use models::VersionString;
|
||||
use reqwest::header::{HeaderMap, CONTENT_LENGTH};
|
||||
use reqwest::Url;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use rpc_toolkit::HandlerArgs;
|
||||
use rustyline_async::ReadlineEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use tokio::sync::oneshot;
|
||||
use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
@@ -23,13 +23,14 @@ use ts_rs::TS;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::model::package::{ManifestPreference, PackageState, PackageStateMatchModelRef};
|
||||
use crate::prelude::*;
|
||||
use crate::progress::{FullProgress, PhasedProgressBar};
|
||||
use crate::progress::{FullProgress, FullProgressTracker, PhasedProgressBar};
|
||||
use crate::registry::context::{RegistryContext, RegistryUrlParams};
|
||||
use crate::registry::package::get::GetPackageResponse;
|
||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
||||
use crate::s9pk::S9pk;
|
||||
use crate::upload::upload;
|
||||
use crate::util::clap::FromStrParser;
|
||||
use crate::util::io::open_file;
|
||||
use crate::util::net::WebSocketExt;
|
||||
use crate::util::Never;
|
||||
|
||||
@@ -38,32 +39,33 @@ pub const PKG_PUBLIC_DIR: &str = "package-data/public";
|
||||
pub const PKG_WASM_DIR: &str = "package-data/wasm";
|
||||
|
||||
// #[command(display(display_serializable))]
|
||||
pub async fn list(ctx: RpcContext) -> Result<Value, Error> {
|
||||
Ok(ctx.db.peek().await.as_public().as_package_data().as_entries()?
|
||||
pub async fn list(ctx: RpcContext) -> Result<Vec<Value>, Error> {
|
||||
Ok(ctx
|
||||
.db
|
||||
.peek()
|
||||
.await
|
||||
.as_public()
|
||||
.as_package_data()
|
||||
.as_entries()?
|
||||
.iter()
|
||||
.filter_map(|(id, pde)| {
|
||||
let status = match pde.as_state_info().as_match() {
|
||||
PackageStateMatchModelRef::Installed(_) => {
|
||||
"installed"
|
||||
}
|
||||
PackageStateMatchModelRef::Installing(_) => {
|
||||
"installing"
|
||||
}
|
||||
PackageStateMatchModelRef::Updating(_) => {
|
||||
"updating"
|
||||
}
|
||||
PackageStateMatchModelRef::Restoring(_) => {
|
||||
"restoring"
|
||||
}
|
||||
PackageStateMatchModelRef::Removing(_) => {
|
||||
"removing"
|
||||
}
|
||||
PackageStateMatchModelRef::Error(_) => {
|
||||
"error"
|
||||
}
|
||||
PackageStateMatchModelRef::Installed(_) => "installed",
|
||||
PackageStateMatchModelRef::Installing(_) => "installing",
|
||||
PackageStateMatchModelRef::Updating(_) => "updating",
|
||||
PackageStateMatchModelRef::Restoring(_) => "restoring",
|
||||
PackageStateMatchModelRef::Removing(_) => "removing",
|
||||
PackageStateMatchModelRef::Error(_) => "error",
|
||||
};
|
||||
serde_json::to_value(json!({ "status": status, "id": id.clone(), "version": pde.as_state_info().as_manifest(ManifestPreference::Old).as_version().de().ok()?}))
|
||||
.ok()
|
||||
Some(json!({
|
||||
"status": status,
|
||||
"id": id.clone(),
|
||||
"version": pde.as_state_info()
|
||||
.as_manifest(ManifestPreference::Old)
|
||||
.as_version()
|
||||
.de()
|
||||
.ok()?
|
||||
}))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
@@ -107,65 +109,57 @@ impl std::fmt::Display for MinMax {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[derive(Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct InstallParams {
|
||||
#[ts(type = "string")]
|
||||
registry: Url,
|
||||
id: PackageId,
|
||||
#[arg(short = 'm', long = "marketplace-url")]
|
||||
#[ts(type = "string | null")]
|
||||
registry: Option<Url>,
|
||||
#[arg(short = 'v', long = "version-spec")]
|
||||
version_spec: Option<String>,
|
||||
#[arg(long = "version-priority")]
|
||||
version_priority: Option<MinMax>,
|
||||
version: VersionString,
|
||||
}
|
||||
|
||||
// #[command(
|
||||
// custom_cli(cli_install(async, context(CliContext))),
|
||||
// )]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn install(
|
||||
ctx: RpcContext,
|
||||
InstallParams {
|
||||
id,
|
||||
registry,
|
||||
version_spec,
|
||||
version_priority,
|
||||
id,
|
||||
version,
|
||||
}: InstallParams,
|
||||
) -> Result<(), Error> {
|
||||
let version_str = match &version_spec {
|
||||
None => "*",
|
||||
Some(v) => &*v,
|
||||
};
|
||||
let version: VersionRange = version_str.parse()?;
|
||||
let registry = registry.unwrap_or_else(|| crate::DEFAULT_MARKETPLACE.parse().unwrap());
|
||||
let version_priority = version_priority.unwrap_or_default();
|
||||
let s9pk = S9pk::deserialize(
|
||||
&Arc::new(
|
||||
HttpSource::new(
|
||||
ctx.client.clone(),
|
||||
format!(
|
||||
"{}/package/v0/{}.s9pk?spec={}&version-priority={}",
|
||||
registry, id, version, version_priority,
|
||||
)
|
||||
.parse()?,
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
None, // TODO
|
||||
)
|
||||
.await?;
|
||||
let package: GetPackageResponse = from_value(
|
||||
ctx.call_remote_with::<RegistryContext, _>(
|
||||
"package.get",
|
||||
json!({
|
||||
"id": id,
|
||||
"version": VersionRange::exactly(version.deref().clone()),
|
||||
}),
|
||||
RegistryUrlParams {
|
||||
registry: registry.clone(),
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)?;
|
||||
|
||||
ensure_code!(
|
||||
&s9pk.as_manifest().id == &id,
|
||||
ErrorKind::ValidateS9pk,
|
||||
"manifest.id does not match expected"
|
||||
);
|
||||
let asset = &package
|
||||
.best
|
||||
.get(&version)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("{id}@{version} not found on {registry}"),
|
||||
ErrorKind::NotFound,
|
||||
)
|
||||
})?
|
||||
.s9pk;
|
||||
|
||||
let download = ctx
|
||||
.services
|
||||
.install(ctx.clone(), s9pk, None::<Never>)
|
||||
.install(
|
||||
ctx.clone(),
|
||||
|| asset.deserialize_s9pk(ctx.client.clone()),
|
||||
None::<Never>,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tokio::spawn(async move { download.await?.await });
|
||||
|
||||
@@ -193,113 +187,74 @@ pub async fn sideload(
|
||||
SideloadParams { session }: SideloadParams,
|
||||
) -> Result<SideloadResponse, Error> {
|
||||
let (upload, file) = upload(&ctx, session.clone()).await?;
|
||||
let (id_send, id_recv) = oneshot::channel();
|
||||
let (err_send, err_recv) = oneshot::channel();
|
||||
let progress = Guid::new();
|
||||
let db = ctx.db.clone();
|
||||
let mut sub = db
|
||||
.subscribe(
|
||||
"/package-data/{id}/install-progress"
|
||||
.parse::<JsonPointer>()
|
||||
.with_kind(ErrorKind::Database)?,
|
||||
)
|
||||
.await;
|
||||
ctx.rpc_continuations.add(
|
||||
progress.clone(),
|
||||
RpcContinuation::ws_authed(&ctx, session,
|
||||
|mut ws| {
|
||||
use axum::extract::ws::Message;
|
||||
async move {
|
||||
if let Err(e) = async {
|
||||
let id = match id_recv.await.map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("Could not get id to watch progress"),
|
||||
ErrorKind::Cancelled,
|
||||
)
|
||||
}).and_then(|a|a) {
|
||||
Ok(a) => a,
|
||||
Err(e) =>{ ws.send(Message::Text(
|
||||
serde_json::to_string(&Err::<(), _>(RpcError::from(e.clone_output())))
|
||||
.with_kind(ErrorKind::Serialization)?,
|
||||
))
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
tokio::select! {
|
||||
res = async {
|
||||
while let Some(_) = sub.recv().await {
|
||||
ws.send(Message::Text(
|
||||
serde_json::to_string(&if let Some(p) = db
|
||||
.peek()
|
||||
.await
|
||||
.as_public()
|
||||
.as_package_data()
|
||||
.as_idx(&id)
|
||||
.and_then(|e| e.as_state_info().as_installing_info()).map(|i| i.as_progress())
|
||||
{
|
||||
Ok::<_, ()>(p.de()?)
|
||||
} else {
|
||||
let mut p = FullProgress::new();
|
||||
p.overall.complete();
|
||||
Ok(p)
|
||||
})
|
||||
.with_kind(ErrorKind::Serialization)?,
|
||||
))
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
}
|
||||
Ok::<_, Error>(())
|
||||
} => res?,
|
||||
err = err_recv => {
|
||||
if let Ok(e) = err {
|
||||
ws.send(Message::Text(
|
||||
serde_json::to_string(&Err::<(), _>(e))
|
||||
.with_kind(ErrorKind::Serialization)?,
|
||||
))
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
let progress_tracker = FullProgressTracker::new();
|
||||
let mut progress_listener = progress_tracker.stream(Some(Duration::from_millis(200)));
|
||||
ctx.rpc_continuations
|
||||
.add(
|
||||
progress.clone(),
|
||||
RpcContinuation::ws_authed(
|
||||
&ctx,
|
||||
session,
|
||||
|mut ws| {
|
||||
use axum::extract::ws::Message;
|
||||
async move {
|
||||
if let Err(e) = async {
|
||||
tokio::select! {
|
||||
res = async {
|
||||
while let Some(progress) = progress_listener.next().await {
|
||||
ws.send(Message::Text(
|
||||
serde_json::to_string(&Ok::<_, ()>(progress))
|
||||
.with_kind(ErrorKind::Serialization)?,
|
||||
))
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
}
|
||||
Ok::<_, Error>(())
|
||||
} => res?,
|
||||
err = err_recv => {
|
||||
if let Ok(e) = err {
|
||||
ws.send(Message::Text(
|
||||
serde_json::to_string(&Err::<(), _>(e))
|
||||
.with_kind(ErrorKind::Serialization)?,
|
||||
))
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ws.normal_close("complete").await?;
|
||||
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("Error tracking sideload progress: {e}");
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
|
||||
ws.normal_close("complete").await?;
|
||||
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!("Error tracking sideload progress: {e}");
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
}
|
||||
},
|
||||
Duration::from_secs(600),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
},
|
||||
Duration::from_secs(600),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = async {
|
||||
match S9pk::deserialize(
|
||||
&file, None, // TODO
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(s9pk) => {
|
||||
let _ = id_send.send(Ok(s9pk.as_manifest().id.clone()));
|
||||
ctx.services
|
||||
.install(ctx.clone(), s9pk, None::<Never>)
|
||||
.await?
|
||||
.await?
|
||||
.await?;
|
||||
file.delete().await
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = id_send.send(Err(e.clone_output()));
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
let key = ctx.db.peek().await.into_private().into_compat_s9pk_key();
|
||||
|
||||
ctx.services
|
||||
.install(
|
||||
ctx.clone(),
|
||||
|| crate::s9pk::load(file.clone(), || Ok(key.de()?.0), Some(&progress_tracker)),
|
||||
None::<Never>,
|
||||
Some(progress_tracker.clone()),
|
||||
)
|
||||
.await?
|
||||
.await?
|
||||
.await?;
|
||||
file.delete().await
|
||||
}
|
||||
.await
|
||||
{
|
||||
@@ -311,10 +266,16 @@ pub async fn sideload(
|
||||
Ok(SideloadResponse { upload, progress })
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser)]
|
||||
pub struct QueryPackageParams {
|
||||
id: PackageId,
|
||||
version: Option<VersionRange>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum CliInstallParams {
|
||||
Marketplace(InstallParams),
|
||||
Marketplace(QueryPackageParams),
|
||||
Sideload(PathBuf),
|
||||
}
|
||||
impl CommandFactory for CliInstallParams {
|
||||
@@ -328,14 +289,19 @@ impl CommandFactory for CliInstallParams {
|
||||
.required_unless_present("id")
|
||||
.value_parser(value_parser!(PathBuf)),
|
||||
)
|
||||
.args(InstallParams::command().get_arguments().cloned().map(|a| {
|
||||
if a.get_id() == "id" {
|
||||
a.required(false).required_unless_present("sideload")
|
||||
} else {
|
||||
a
|
||||
}
|
||||
.conflicts_with("sideload")
|
||||
}))
|
||||
.args(
|
||||
QueryPackageParams::command()
|
||||
.get_arguments()
|
||||
.cloned()
|
||||
.map(|a| {
|
||||
if a.get_id() == "id" {
|
||||
a.required(false).required_unless_present("sideload")
|
||||
} else {
|
||||
a
|
||||
}
|
||||
.conflicts_with("sideload")
|
||||
}),
|
||||
)
|
||||
}
|
||||
fn command_for_update() -> clap::Command {
|
||||
Self::command()
|
||||
@@ -346,7 +312,9 @@ impl FromArgMatches for CliInstallParams {
|
||||
if let Some(sideload) = matches.get_one::<PathBuf>("sideload") {
|
||||
Ok(Self::Sideload(sideload.clone()))
|
||||
} else {
|
||||
Ok(Self::Marketplace(InstallParams::from_arg_matches(matches)?))
|
||||
Ok(Self::Marketplace(QueryPackageParams::from_arg_matches(
|
||||
matches,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> {
|
||||
@@ -355,6 +323,35 @@ impl FromArgMatches for CliInstallParams {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
#[ts(export)]
|
||||
pub struct InstalledVersionParams {
|
||||
id: PackageId,
|
||||
}
|
||||
|
||||
pub async fn installed_version(
|
||||
ctx: RpcContext,
|
||||
InstalledVersionParams { id }: InstalledVersionParams,
|
||||
) -> Result<Option<VersionString>, Error> {
|
||||
if let Some(pde) = ctx
|
||||
.db
|
||||
.peek()
|
||||
.await
|
||||
.into_public()
|
||||
.into_package_data()
|
||||
.into_idx(&id)
|
||||
{
|
||||
Ok(Some(
|
||||
pde.into_state_info()
|
||||
.as_manifest(ManifestPreference::Old)
|
||||
.as_version()
|
||||
.de()?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn cli_install(
|
||||
HandlerArgs {
|
||||
@@ -368,7 +365,7 @@ pub async fn cli_install(
|
||||
let method = parent_method.into_iter().chain(method).collect_vec();
|
||||
match params {
|
||||
CliInstallParams::Sideload(path) => {
|
||||
let file = crate::s9pk::load(&ctx, path).await?;
|
||||
let file = open_file(path).await?;
|
||||
|
||||
// rpc call remote sideload
|
||||
let SideloadResponse { upload, progress } = from_value::<SideloadResponse>(
|
||||
@@ -435,9 +432,70 @@ pub async fn cli_install(
|
||||
progress?;
|
||||
upload?;
|
||||
}
|
||||
CliInstallParams::Marketplace(params) => {
|
||||
ctx.call_remote::<RpcContext>(&method.join("."), to_value(¶ms)?)
|
||||
.await?;
|
||||
CliInstallParams::Marketplace(QueryPackageParams { id, version }) => {
|
||||
let source_version: Option<VersionString> = from_value(
|
||||
ctx.call_remote::<RpcContext>("package.installed-version", json!({ "id": &id }))
|
||||
.await?,
|
||||
)?;
|
||||
let mut packages: GetPackageResponse = from_value(
|
||||
ctx.call_remote::<RegistryContext>(
|
||||
"package.get",
|
||||
json!({ "id": &id, "version": version, "sourceVersion": source_version }),
|
||||
)
|
||||
.await?,
|
||||
)?;
|
||||
let version = if packages.best.len() == 1 {
|
||||
packages.best.pop_first().map(|(k, _)| k).unwrap()
|
||||
} else {
|
||||
println!("Multiple flavors of {id} found. Please select one of the following versions to install:");
|
||||
let version;
|
||||
loop {
|
||||
let (mut read, mut output) = rustyline_async::Readline::new("> ".into())
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
for (idx, version) in packages.best.keys().enumerate() {
|
||||
output
|
||||
.write_all(format!(" {}) {}\n", idx + 1, version).as_bytes())
|
||||
.await?;
|
||||
read.add_history_entry(version.to_string());
|
||||
}
|
||||
if let ReadlineEvent::Line(line) = read.readline().await? {
|
||||
let trimmed = line.trim();
|
||||
match trimmed.parse() {
|
||||
Ok(v) => {
|
||||
if let Some((k, _)) = packages.best.remove_entry(&v) {
|
||||
version = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(_) => match trimmed.parse::<usize>() {
|
||||
Ok(i) if (1..=packages.best.len()).contains(&i) => {
|
||||
version = packages.best.keys().nth(i - 1).unwrap().clone();
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
eprintln!("invalid selection: {trimmed}");
|
||||
println!("Please select one of the following versions to install:");
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("Could not determine precise version to install"),
|
||||
ErrorKind::InvalidRequest,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
version
|
||||
};
|
||||
ctx.call_remote::<RpcContext>(
|
||||
&method.join("."),
|
||||
to_value(&InstallParams {
|
||||
id,
|
||||
registry: ctx.registry_url.clone().or_not_found("--registry")?,
|
||||
version,
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub const DEFAULT_MARKETPLACE: &str = "https://registry.start9.com";
|
||||
pub const DEFAULT_REGISTRY: &str = "https://registry.start9.com";
|
||||
// pub const COMMUNITY_MARKETPLACE: &str = "https://community-registry.start9.com";
|
||||
pub const HOST_IP: [u8; 4] = [172, 18, 0, 1];
|
||||
pub use std::env::consts::ARCH;
|
||||
@@ -115,7 +115,7 @@ impl std::fmt::Display for ApiState {
|
||||
}
|
||||
|
||||
pub fn main_api<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
let api = ParentHandler::new()
|
||||
.subcommand::<C, _>("git-info", from_fn(version::git_info))
|
||||
.subcommand(
|
||||
"echo",
|
||||
@@ -146,9 +146,11 @@ pub fn main_api<C: Context>() -> ParentHandler<C> {
|
||||
)
|
||||
.no_cli(),
|
||||
)
|
||||
.subcommand("lxc", lxc::lxc::<C>())
|
||||
.subcommand("s9pk", s9pk::rpc::s9pk())
|
||||
.subcommand("util", util::rpc::util::<C>())
|
||||
.subcommand("util", util::rpc::util::<C>());
|
||||
#[cfg(feature = "dev")]
|
||||
let api = api.subcommand("lxc", lxc::dev::lxc::<C>());
|
||||
api
|
||||
}
|
||||
|
||||
pub fn server<C: Context>() -> ParentHandler<C> {
|
||||
@@ -261,6 +263,12 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
||||
.with_display_serializable()
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"installed-version",
|
||||
from_fn_async(install::installed_version)
|
||||
.with_display_serializable()
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand("config", config::config::<C>())
|
||||
.subcommand(
|
||||
"start",
|
||||
|
||||
112
core/startos/src/lxc/dev.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use clap::Parser;
|
||||
use rpc_toolkit::{
|
||||
from_fn_async, CallRemoteHandler, Context, Empty, HandlerArgs, HandlerExt, HandlerFor,
|
||||
ParentHandler,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::lxc::{ContainerId, LxcConfig};
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::Guid;
|
||||
|
||||
pub fn lxc<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"create",
|
||||
from_fn_async(create).with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"list",
|
||||
from_fn_async(list)
|
||||
.with_custom_display_fn(|_, res| {
|
||||
use prettytable::*;
|
||||
let mut table = table!([bc => "GUID"]);
|
||||
for guid in res {
|
||||
table.add_row(row![&*guid]);
|
||||
}
|
||||
table.printstd();
|
||||
Ok(())
|
||||
})
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"remove",
|
||||
from_fn_async(remove)
|
||||
.no_display()
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand("connect", from_fn_async(connect_rpc).no_cli())
|
||||
.subcommand("connect", from_fn_async(connect_rpc_cli).no_display())
|
||||
}
|
||||
|
||||
pub async fn create(ctx: RpcContext) -> Result<ContainerId, Error> {
|
||||
let container = ctx.lxc_manager.create(None, LxcConfig::default()).await?;
|
||||
let guid = container.guid.deref().clone();
|
||||
ctx.dev.lxc.lock().await.insert(guid.clone(), container);
|
||||
Ok(guid)
|
||||
}
|
||||
|
||||
pub async fn list(ctx: RpcContext) -> Result<Vec<ContainerId>, Error> {
|
||||
Ok(ctx.dev.lxc.lock().await.keys().cloned().collect())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
pub struct RemoveParams {
|
||||
#[ts(type = "string")]
|
||||
pub guid: ContainerId,
|
||||
}
|
||||
|
||||
pub async fn remove(ctx: RpcContext, RemoveParams { guid }: RemoveParams) -> Result<(), Error> {
|
||||
if let Some(container) = ctx.dev.lxc.lock().await.remove(&guid) {
|
||||
container.exit().await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
pub struct ConnectParams {
|
||||
#[ts(type = "string")]
|
||||
pub guid: ContainerId,
|
||||
}
|
||||
|
||||
pub async fn connect_rpc(
|
||||
ctx: RpcContext,
|
||||
ConnectParams { guid }: ConnectParams,
|
||||
) -> Result<Guid, Error> {
|
||||
super::connect(
|
||||
&ctx,
|
||||
ctx.dev.lxc.lock().await.get(&guid).ok_or_else(|| {
|
||||
Error::new(eyre!("No container with guid: {guid}"), ErrorKind::NotFound)
|
||||
})?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn connect_rpc_cli(
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgs<CliContext, ConnectParams>,
|
||||
) -> Result<(), Error> {
|
||||
let ctx = context.clone();
|
||||
let guid = CallRemoteHandler::<CliContext, _, _>::new(from_fn_async(connect_rpc))
|
||||
.handle_async(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params: rpc_toolkit::util::Flat(params, Empty {}),
|
||||
inherited_params,
|
||||
raw_params,
|
||||
})
|
||||
.await?;
|
||||
|
||||
super::connect_cli(&ctx, guid).await
|
||||
}
|
||||
@@ -1,23 +1,17 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::builder::ValueParserFactory;
|
||||
use clap::Parser;
|
||||
use futures::{AsyncWriteExt, StreamExt};
|
||||
use imbl_value::{InOMap, InternedString};
|
||||
use models::InvalidId;
|
||||
use rpc_toolkit::yajrc::{RpcError, RpcResponse};
|
||||
use rpc_toolkit::{
|
||||
from_fn_async, CallRemoteHandler, Context, Empty, GenericRpcMethod, HandlerArgs, HandlerExt,
|
||||
HandlerFor, ParentHandler, RpcRequest,
|
||||
};
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use rpc_toolkit::{GenericRpcMethod, RpcRequest, RpcResponse};
|
||||
use rustyline_async::{ReadlineEvent, SharedWriter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use tokio::process::Command;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -35,9 +29,13 @@ use crate::disk::mount::util::unmount;
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||
use crate::util::clap::FromStrParser;
|
||||
use crate::util::io::open_file;
|
||||
use crate::util::rpc_client::UnixRpcClient;
|
||||
use crate::util::{new_guid, Invoke};
|
||||
|
||||
#[cfg(feature = "dev")]
|
||||
pub mod dev;
|
||||
|
||||
const LXC_CONTAINER_DIR: &str = "/var/lib/lxc";
|
||||
const RPC_DIR: &str = "media/startos/rpc"; // must not be absolute path
|
||||
pub const CONTAINER_RPC_SERVER_SOCKET: &str = "service.sock"; // must not be absolute path
|
||||
@@ -344,7 +342,7 @@ impl Drop for LxcContainer {
|
||||
if let Err(e) = async {
|
||||
let err_path = rootfs.path().join("var/log/containerRuntime.err");
|
||||
if tokio::fs::metadata(&err_path).await.is_ok() {
|
||||
let mut lines = BufReader::new(File::open(&err_path).await?).lines();
|
||||
let mut lines = BufReader::new(open_file(&err_path).await?).lines();
|
||||
while let Some(line) = lines.next_line().await? {
|
||||
let container = &**guid;
|
||||
tracing::error!(container, "{}", line);
|
||||
@@ -373,80 +371,6 @@ impl Drop for LxcContainer {
|
||||
|
||||
#[derive(Default, Serialize)]
|
||||
pub struct LxcConfig {}
|
||||
|
||||
pub fn lxc<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand(
|
||||
"create",
|
||||
from_fn_async(create).with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"list",
|
||||
from_fn_async(list)
|
||||
.with_custom_display_fn(|_, res| {
|
||||
use prettytable::*;
|
||||
let mut table = table!([bc => "GUID"]);
|
||||
for guid in res {
|
||||
table.add_row(row![&*guid]);
|
||||
}
|
||||
table.printstd();
|
||||
Ok(())
|
||||
})
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"remove",
|
||||
from_fn_async(remove)
|
||||
.no_display()
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand("connect", from_fn_async(connect_rpc).no_cli())
|
||||
.subcommand("connect", from_fn_async(connect_rpc_cli).no_display())
|
||||
}
|
||||
|
||||
pub async fn create(ctx: RpcContext) -> Result<ContainerId, Error> {
|
||||
let container = ctx.lxc_manager.create(None, LxcConfig::default()).await?;
|
||||
let guid = container.guid.deref().clone();
|
||||
ctx.dev.lxc.lock().await.insert(guid.clone(), container);
|
||||
Ok(guid)
|
||||
}
|
||||
|
||||
pub async fn list(ctx: RpcContext) -> Result<Vec<ContainerId>, Error> {
|
||||
Ok(ctx.dev.lxc.lock().await.keys().cloned().collect())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
pub struct RemoveParams {
|
||||
#[ts(type = "string")]
|
||||
pub guid: ContainerId,
|
||||
}
|
||||
|
||||
pub async fn remove(ctx: RpcContext, RemoveParams { guid }: RemoveParams) -> Result<(), Error> {
|
||||
if let Some(container) = ctx.dev.lxc.lock().await.remove(&guid) {
|
||||
container.exit().await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||
pub struct ConnectParams {
|
||||
#[ts(type = "string")]
|
||||
pub guid: ContainerId,
|
||||
}
|
||||
|
||||
pub async fn connect_rpc(
|
||||
ctx: RpcContext,
|
||||
ConnectParams { guid }: ConnectParams,
|
||||
) -> Result<Guid, Error> {
|
||||
connect(
|
||||
&ctx,
|
||||
ctx.dev.lxc.lock().await.get(&guid).ok_or_else(|| {
|
||||
Error::new(eyre!("No container with guid: {guid}"), ErrorKind::NotFound)
|
||||
})?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn connect(ctx: &RpcContext, container: &LxcContainer) -> Result<Guid, Error> {
|
||||
use axum::extract::ws::Message;
|
||||
|
||||
@@ -476,11 +400,8 @@ pub async fn connect(ctx: &RpcContext, container: &LxcContainer) -> Result<Guid,
|
||||
}
|
||||
.await;
|
||||
ws.send(Message::Text(
|
||||
serde_json::to_string(&RpcResponse::<GenericRpcMethod> {
|
||||
id,
|
||||
result,
|
||||
})
|
||||
.with_kind(ErrorKind::Serialization)?,
|
||||
serde_json::to_string(&RpcResponse { id, result })
|
||||
.with_kind(ErrorKind::Serialization)?,
|
||||
))
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
@@ -614,28 +535,3 @@ pub async fn connect_cli(ctx: &CliContext, guid: Guid) -> Result<(), Error> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn connect_rpc_cli(
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgs<CliContext, ConnectParams>,
|
||||
) -> Result<(), Error> {
|
||||
let ctx = context.clone();
|
||||
let guid = CallRemoteHandler::<CliContext, _, _>::new(from_fn_async(connect_rpc))
|
||||
.handle_async(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params: rpc_toolkit::util::Flat(params, Empty {}),
|
||||
inherited_params,
|
||||
raw_params,
|
||||
})
|
||||
.await?;
|
||||
|
||||
connect_cli(&ctx, guid).await
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ use models::{HostId, ServiceInterfaceId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::net::host::address::HostAddress;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
@@ -19,7 +19,6 @@ use new_mime_guess::MimeGuess;
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::x509::X509;
|
||||
use rpc_toolkit::{Context, HttpServer, Server};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::BufReader;
|
||||
use tokio_util::io::ReaderStream;
|
||||
|
||||
@@ -29,6 +28,7 @@ use crate::middleware::auth::{Auth, HasValidSession};
|
||||
use crate::middleware::cors::Cors;
|
||||
use crate::middleware::db::SyncDb;
|
||||
use crate::rpc_continuations::{Guid, RpcContinuations};
|
||||
use crate::util::io::open_file;
|
||||
use crate::{
|
||||
diagnostic_api, init_api, install_api, main_api, setup_api, Error, ErrorKind, ResultExt,
|
||||
};
|
||||
@@ -44,8 +44,6 @@ const EMBEDDED_UIS: Dir<'_> =
|
||||
#[cfg(not(all(feature = "daemon", not(feature = "test"))))]
|
||||
const EMBEDDED_UIS: Dir<'_> = Dir::new("", &[]);
|
||||
|
||||
const PROXY_STRIP_HEADERS: &[&str] = &["cookie", "host", "origin", "referer", "user-agent"];
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum UiMode {
|
||||
Setup,
|
||||
@@ -340,9 +338,8 @@ impl FileData {
|
||||
.any(|e| e == "gzip")
|
||||
.then_some("gzip");
|
||||
|
||||
let file = File::open(path)
|
||||
.await
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, path.display().to_string()))?;
|
||||
let file = open_file(path)
|
||||
.await?;
|
||||
let metadata = file
|
||||
.metadata()
|
||||
.await
|
||||
|
||||
@@ -112,24 +112,6 @@ pub async fn find_eth_iface() -> Result<String, Error> {
|
||||
))
|
||||
}
|
||||
|
||||
#[pin_project::pin_project]
|
||||
pub struct SingleAccept<T>(Option<T>);
|
||||
impl<T> SingleAccept<T> {
|
||||
pub fn new(conn: T) -> Self {
|
||||
Self(Some(conn))
|
||||
}
|
||||
}
|
||||
// impl<T> axum_server::accept::Accept for SingleAccept<T> {
|
||||
// type Conn = T;
|
||||
// type Error = Infallible;
|
||||
// fn poll_accept(
|
||||
// self: std::pin::Pin<&mut Self>,
|
||||
// _cx: &mut std::task::Context<'_>,
|
||||
// ) -> std::task::Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||
// std::task::Poll::Ready(self.project().0.take().map(Ok))
|
||||
// }
|
||||
// }
|
||||
|
||||
pub struct TcpListeners {
|
||||
listeners: Vec<TcpListener>,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::extract::Request;
|
||||
use axum::response::Response;
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
use http::Uri;
|
||||
use imbl_value::InternedString;
|
||||
use models::ResultExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -20,8 +25,9 @@ use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::db::model::Database;
|
||||
use crate::net::static_server::server_error;
|
||||
use crate::prelude::*;
|
||||
use crate::util::io::{BackTrackingReader, TimeoutStream};
|
||||
use crate::util::io::BackTrackingReader;
|
||||
use crate::util::serde::MaybeUtf8String;
|
||||
|
||||
// not allowed: <=1024, >=32768, 5355, 5432, 9050, 6010, 9051, 5353
|
||||
@@ -113,8 +119,16 @@ impl VHostServer {
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
Ok((stream, _)) => {
|
||||
let stream =
|
||||
Box::pin(TimeoutStream::new(stream, Duration::from_secs(300)));
|
||||
if let Err(e) = socket2::SockRef::from(&stream).set_tcp_keepalive(
|
||||
&socket2::TcpKeepalive::new()
|
||||
.with_time(Duration::from_secs(900))
|
||||
.with_interval(Duration::from_secs(60))
|
||||
.with_retries(5),
|
||||
) {
|
||||
tracing::error!("Failed to set tcp keepalive: {e}");
|
||||
tracing::debug!("{e:?}");
|
||||
}
|
||||
|
||||
let mut stream = BackTrackingReader::new(stream);
|
||||
stream.start_buffering();
|
||||
let mapping = mapping.clone();
|
||||
@@ -129,38 +143,39 @@ impl VHostServer {
|
||||
{
|
||||
Ok(a) => a,
|
||||
Err(_) => {
|
||||
// stream.rewind();
|
||||
// return hyper::server::Server::builder(
|
||||
// SingleAccept::new(stream),
|
||||
// )
|
||||
// .serve(make_service_fn(|_| async {
|
||||
// Ok::<_, Infallible>(service_fn(|req| async move {
|
||||
// let host = req
|
||||
// .headers()
|
||||
// .get(http::header::HOST)
|
||||
// .and_then(|host| host.to_str().ok());
|
||||
// let uri = Uri::from_parts({
|
||||
// let mut parts =
|
||||
// req.uri().to_owned().into_parts();
|
||||
// parts.authority = host
|
||||
// .map(FromStr::from_str)
|
||||
// .transpose()?;
|
||||
// parts
|
||||
// })?;
|
||||
// Response::builder()
|
||||
// .status(
|
||||
// http::StatusCode::TEMPORARY_REDIRECT,
|
||||
// )
|
||||
// .header(
|
||||
// http::header::LOCATION,
|
||||
// uri.to_string(),
|
||||
// )
|
||||
// .body(Body::default())
|
||||
// }))
|
||||
// }))
|
||||
// .await
|
||||
// .with_kind(crate::ErrorKind::Network);
|
||||
todo!()
|
||||
stream.rewind();
|
||||
return hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new())
|
||||
.serve_connection(
|
||||
hyper_util::rt::TokioIo::new(stream),
|
||||
hyper_util::service::TowerToHyperService::new(axum::Router::new().fallback(
|
||||
axum::routing::method_routing::any(move |req: Request| async move {
|
||||
match async move {
|
||||
let host = req
|
||||
.headers()
|
||||
.get(http::header::HOST)
|
||||
.and_then(|host| host.to_str().ok());
|
||||
let uri = Uri::from_parts({
|
||||
let mut parts = req.uri().to_owned().into_parts();
|
||||
parts.authority = host.map(FromStr::from_str).transpose()?;
|
||||
parts
|
||||
})?;
|
||||
Response::builder()
|
||||
.status(http::StatusCode::TEMPORARY_REDIRECT)
|
||||
.header(http::header::LOCATION, uri.to_string())
|
||||
.body(Body::default())
|
||||
}.await {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
tracing::warn!("Error redirecting http request on ssl port: {e}");
|
||||
tracing::error!("{e:?}");
|
||||
server_error(Error::new(e, ErrorKind::Network))
|
||||
}
|
||||
}
|
||||
}),
|
||||
)),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Error::new(color_eyre::eyre::Report::msg(e), ErrorKind::Network));
|
||||
}
|
||||
};
|
||||
let target_name =
|
||||
|
||||
@@ -74,6 +74,7 @@ pub async fn list(
|
||||
.as_notifications()
|
||||
.as_entries()?
|
||||
.into_iter()
|
||||
.rev()
|
||||
.take(limit);
|
||||
let notifs = records
|
||||
.into_iter()
|
||||
@@ -97,6 +98,7 @@ pub async fn list(
|
||||
.as_entries()?
|
||||
.into_iter()
|
||||
.filter(|(id, _)| *id < before)
|
||||
.rev()
|
||||
.take(limit);
|
||||
records
|
||||
.into_iter()
|
||||
|
||||
@@ -21,7 +21,7 @@ use crate::disk::OsPartitionInfo;
|
||||
use crate::net::utils::find_eth_iface;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||
use crate::util::io::TmpDir;
|
||||
use crate::util::io::{open_file, TmpDir};
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::util::Invoke;
|
||||
use crate::ARCH;
|
||||
@@ -241,12 +241,10 @@ pub async fn execute<C: Context>(
|
||||
tokio::fs::create_dir_all(&images_path).await?;
|
||||
let image_path = images_path
|
||||
.join(hex::encode(
|
||||
&MultiCursorFile::from(
|
||||
tokio::fs::File::open("/run/live/medium/live/filesystem.squashfs").await?,
|
||||
)
|
||||
.blake3_mmap()
|
||||
.await?
|
||||
.as_bytes()[..16],
|
||||
&MultiCursorFile::from(open_file("/run/live/medium/live/filesystem.squashfs").await?)
|
||||
.blake3_mmap()
|
||||
.await?
|
||||
.as_bytes()[..16],
|
||||
))
|
||||
.with_extension("rootfs");
|
||||
tokio::fs::copy("/run/live/medium/live/filesystem.squashfs", &image_path).await?;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -7,10 +8,13 @@ use ts_rs::TS;
|
||||
use url::Url;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||
use crate::registry::signer::commitment::{Commitment, Digestable};
|
||||
use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey};
|
||||
use crate::registry::signer::AcceptSigners;
|
||||
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
||||
use crate::s9pk::merkle_archive::source::Section;
|
||||
use crate::s9pk::S9pk;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -52,3 +56,15 @@ impl<C: for<'a> Commitment<&'a HttpSource>> RegistryAsset<C> {
|
||||
.await
|
||||
}
|
||||
}
|
||||
impl RegistryAsset<MerkleArchiveCommitment> {
|
||||
pub async fn deserialize_s9pk(
|
||||
&self,
|
||||
client: Client,
|
||||
) -> Result<S9pk<Section<Arc<HttpSource>>>, Error> {
|
||||
S9pk::deserialize(
|
||||
&Arc::new(HttpSource::new(client, self.url.clone()).await?),
|
||||
Some(&self.commitment),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use reqwest::{Client, Proxy};
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use rpc_toolkit::{CallRemote, Context, Empty};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
use tracing::instrument;
|
||||
use url::Url;
|
||||
@@ -32,17 +33,22 @@ pub struct RegistryConfig {
|
||||
#[arg(short = 'l', long = "listen")]
|
||||
pub listen: Option<SocketAddr>,
|
||||
#[arg(short = 'h', long = "hostname")]
|
||||
pub hostname: InternedString,
|
||||
#[arg(short = 'p', long = "proxy")]
|
||||
pub hostname: Option<InternedString>,
|
||||
#[arg(short = 'p', long = "tor-proxy")]
|
||||
pub tor_proxy: Option<Url>,
|
||||
#[arg(short = 'd', long = "datadir")]
|
||||
pub datadir: Option<PathBuf>,
|
||||
#[arg(short = 'u', long = "pg-connection-url")]
|
||||
pub pg_connection_url: Option<String>,
|
||||
}
|
||||
impl ContextConfig for RegistryConfig {
|
||||
fn next(&mut self) -> Option<PathBuf> {
|
||||
self.config.take()
|
||||
}
|
||||
fn merge_with(&mut self, other: Self) {
|
||||
self.listen = self.listen.take().or(other.listen);
|
||||
self.hostname = self.hostname.take().or(other.hostname);
|
||||
self.tor_proxy = self.tor_proxy.take().or(other.tor_proxy);
|
||||
self.datadir = self.datadir.take().or(other.datadir);
|
||||
}
|
||||
}
|
||||
@@ -64,6 +70,7 @@ pub struct RegistryContextSeed {
|
||||
pub rpc_continuations: RpcContinuations,
|
||||
pub client: Client,
|
||||
pub shutdown: Sender<()>,
|
||||
pub pool: Option<PgPool>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -91,8 +98,24 @@ impl RegistryContext {
|
||||
.clone()
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| "socks5h://localhost:9050".parse())?;
|
||||
let pool: Option<PgPool> = match &config.pg_connection_url {
|
||||
Some(url) => match PgPool::connect(url.as_str()).await {
|
||||
Ok(pool) => Some(pool),
|
||||
Err(_) => None,
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
Ok(Self(Arc::new(RegistryContextSeed {
|
||||
hostname: config.hostname.clone(),
|
||||
hostname: config
|
||||
.hostname
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("missing required configuration: hostname"),
|
||||
ErrorKind::NotFound,
|
||||
)
|
||||
})?
|
||||
.clone(),
|
||||
listen: config
|
||||
.listen
|
||||
.unwrap_or(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 5959)),
|
||||
@@ -110,6 +133,7 @@ impl RegistryContext {
|
||||
.build()
|
||||
.with_kind(crate::ErrorKind::ParseUrl)?,
|
||||
shutdown,
|
||||
pool,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::ops::Deref;
|
||||
|
||||
use axum::extract::Request;
|
||||
use axum::response::Response;
|
||||
use emver::{Version, VersionRange};
|
||||
use exver::{Version, VersionRange};
|
||||
use http::HeaderValue;
|
||||
use imbl_value::InternedString;
|
||||
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
||||
|
||||
25
core/startos/src/registry/metrics-db/registry-sqlx-data.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
TMP_DIR=$(mktemp -d)
|
||||
mkdir $TMP_DIR/pgdata
|
||||
docker run -d --rm --name=tmp_postgres -e POSTGRES_PASSWORD=password -v $TMP_DIR/pgdata:/var/lib/postgresql/data postgres
|
||||
|
||||
(
|
||||
set -e
|
||||
ctr=0
|
||||
until docker exec tmp_postgres psql -U postgres 2> /dev/null || [ $ctr -ge 5 ]; do
|
||||
ctr=$[ctr + 1]
|
||||
sleep 5;
|
||||
done
|
||||
|
||||
PG_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' tmp_postgres)
|
||||
|
||||
cat "./registry_schema.sql" | docker exec -i tmp_postgres psql -U postgres -d postgres -f-
|
||||
cd ../../..
|
||||
DATABASE_URL=postgres://postgres:password@$PG_IP/postgres PLATFORM=$(uname -m) cargo sqlx prepare -- --lib --profile=test --workspace
|
||||
echo "Subscript Complete"
|
||||
)
|
||||
|
||||
docker stop tmp_postgres
|
||||
sudo rm -rf $TMP_DIR
|
||||
828
core/startos/src/registry/metrics-db/registry_schema.sql
Normal file
@@ -0,0 +1,828 @@
|
||||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
-- Dumped from database version 14.12 (Ubuntu 14.12-0ubuntu0.22.04.1)
|
||||
-- Dumped by pg_dump version 14.12 (Ubuntu 14.12-0ubuntu0.22.04.1)
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_table_access_method = heap;
|
||||
|
||||
--
|
||||
-- Name: admin; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.admin (
|
||||
id character varying NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
pass_hash character varying NOT NULL,
|
||||
deleted_at timestamp with time zone
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.admin OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: admin_pkgs; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.admin_pkgs (
|
||||
id bigint NOT NULL,
|
||||
admin character varying NOT NULL,
|
||||
pkg_id character varying NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.admin_pkgs OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: admin_pkgs_id_seq; Type: SEQUENCE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.admin_pkgs_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.admin_pkgs_id_seq OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: admin_pkgs_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.admin_pkgs_id_seq OWNED BY public.admin_pkgs.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: category; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.category (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
description character varying NOT NULL,
|
||||
priority bigint DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.category OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: category_id_seq; Type: SEQUENCE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.category_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.category_id_seq OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: category_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.category_id_seq OWNED BY public.category.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: eos_hash; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.eos_hash (
|
||||
id bigint NOT NULL,
|
||||
version character varying NOT NULL,
|
||||
hash character varying NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.eos_hash OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: eos_hash_id_seq; Type: SEQUENCE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.eos_hash_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.eos_hash_id_seq OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: eos_hash_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.eos_hash_id_seq OWNED BY public.eos_hash.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: error_log_record; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.error_log_record (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
epoch character varying NOT NULL,
|
||||
commit_hash character varying NOT NULL,
|
||||
source_file character varying NOT NULL,
|
||||
line bigint NOT NULL,
|
||||
target character varying NOT NULL,
|
||||
level character varying NOT NULL,
|
||||
message character varying NOT NULL,
|
||||
incidents bigint NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.error_log_record OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: error_log_record_id_seq; Type: SEQUENCE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.error_log_record_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.error_log_record_id_seq OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: error_log_record_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.error_log_record_id_seq OWNED BY public.error_log_record.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: metric; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.metric (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
version character varying NOT NULL,
|
||||
pkg_id character varying NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.metric OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: metric_id_seq; Type: SEQUENCE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.metric_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.metric_id_seq OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: metric_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.metric_id_seq OWNED BY public.metric.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: os_version; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.os_version (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
number character varying NOT NULL,
|
||||
headline character varying NOT NULL,
|
||||
release_notes character varying NOT NULL,
|
||||
arch character varying
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.os_version OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: os_version_id_seq; Type: SEQUENCE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.os_version_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.os_version_id_seq OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: os_version_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.os_version_id_seq OWNED BY public.os_version.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: persistent_migration; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.persistent_migration (
|
||||
id integer NOT NULL,
|
||||
version integer NOT NULL,
|
||||
label character varying,
|
||||
"timestamp" timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.persistent_migration OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: persistent_migration_id_seq; Type: SEQUENCE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.persistent_migration_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.persistent_migration_id_seq OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: persistent_migration_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.persistent_migration_id_seq OWNED BY public.persistent_migration.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: pkg_category; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.pkg_category (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
category_id bigint NOT NULL,
|
||||
pkg_id character varying NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.pkg_category OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: pkg_dependency; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.pkg_dependency (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
pkg_id character varying NOT NULL,
|
||||
pkg_version character varying NOT NULL,
|
||||
dep_id character varying NOT NULL,
|
||||
dep_version_range character varying NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.pkg_dependency OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: pkg_dependency_id_seq; Type: SEQUENCE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.pkg_dependency_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.pkg_dependency_id_seq OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: pkg_dependency_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.pkg_dependency_id_seq OWNED BY public.pkg_dependency.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: pkg_record; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.pkg_record (
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone,
|
||||
pkg_id character varying NOT NULL,
|
||||
hidden boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.pkg_record OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: service_category_id_seq; Type: SEQUENCE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.service_category_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.service_category_id_seq OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: service_category_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.service_category_id_seq OWNED BY public.pkg_category.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: upload; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.upload (
|
||||
id bigint NOT NULL,
|
||||
uploader character varying NOT NULL,
|
||||
pkg_id character varying NOT NULL,
|
||||
pkg_version character varying NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.upload OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: upload_id_seq; Type: SEQUENCE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.upload_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.upload_id_seq OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: upload_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.upload_id_seq OWNED BY public.upload.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_activity; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.user_activity (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
server_id character varying NOT NULL,
|
||||
os_version character varying,
|
||||
arch character varying
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.user_activity OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: user_activity_id_seq; Type: SEQUENCE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.user_activity_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.user_activity_id_seq OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: user_activity_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.user_activity_id_seq OWNED BY public.user_activity.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: version; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.version (
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone,
|
||||
number character varying NOT NULL,
|
||||
release_notes character varying NOT NULL,
|
||||
os_version character varying NOT NULL,
|
||||
pkg_id character varying NOT NULL,
|
||||
title character varying NOT NULL,
|
||||
desc_short character varying NOT NULL,
|
||||
desc_long character varying NOT NULL,
|
||||
icon_type character varying NOT NULL,
|
||||
deprecated_at timestamp with time zone
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.version OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: version_platform; Type: TABLE; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE TABLE public.version_platform (
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone,
|
||||
pkg_id character varying NOT NULL,
|
||||
version_number character varying NOT NULL,
|
||||
arch character varying NOT NULL,
|
||||
ram bigint,
|
||||
device jsonb
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.version_platform OWNER TO alpha_admin;
|
||||
|
||||
--
|
||||
-- Name: admin_pkgs id; Type: DEFAULT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.admin_pkgs ALTER COLUMN id SET DEFAULT nextval('public.admin_pkgs_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: category id; Type: DEFAULT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.category ALTER COLUMN id SET DEFAULT nextval('public.category_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: eos_hash id; Type: DEFAULT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.eos_hash ALTER COLUMN id SET DEFAULT nextval('public.eos_hash_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: error_log_record id; Type: DEFAULT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.error_log_record ALTER COLUMN id SET DEFAULT nextval('public.error_log_record_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: metric id; Type: DEFAULT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.metric ALTER COLUMN id SET DEFAULT nextval('public.metric_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: os_version id; Type: DEFAULT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.os_version ALTER COLUMN id SET DEFAULT nextval('public.os_version_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: persistent_migration id; Type: DEFAULT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.persistent_migration ALTER COLUMN id SET DEFAULT nextval('public.persistent_migration_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: pkg_category id; Type: DEFAULT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.pkg_category ALTER COLUMN id SET DEFAULT nextval('public.service_category_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: pkg_dependency id; Type: DEFAULT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.pkg_dependency ALTER COLUMN id SET DEFAULT nextval('public.pkg_dependency_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: upload id; Type: DEFAULT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.upload ALTER COLUMN id SET DEFAULT nextval('public.upload_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_activity id; Type: DEFAULT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.user_activity ALTER COLUMN id SET DEFAULT nextval('public.user_activity_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: admin admin_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.admin
|
||||
ADD CONSTRAINT admin_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: admin_pkgs admin_pkgs_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.admin_pkgs
|
||||
ADD CONSTRAINT admin_pkgs_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: category category_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.category
|
||||
ADD CONSTRAINT category_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: eos_hash eos_hash_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.eos_hash
|
||||
ADD CONSTRAINT eos_hash_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: error_log_record error_log_record_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.error_log_record
|
||||
ADD CONSTRAINT error_log_record_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: metric metric_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.metric
|
||||
ADD CONSTRAINT metric_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: os_version os_version_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.os_version
|
||||
ADD CONSTRAINT os_version_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: persistent_migration persistent_migration_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.persistent_migration
|
||||
ADD CONSTRAINT persistent_migration_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: pkg_category pkg_category_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.pkg_category
|
||||
ADD CONSTRAINT pkg_category_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: pkg_dependency pkg_dependency_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.pkg_dependency
|
||||
ADD CONSTRAINT pkg_dependency_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: admin_pkgs unique_admin_pkg; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.admin_pkgs
|
||||
ADD CONSTRAINT unique_admin_pkg UNIQUE (pkg_id, admin);
|
||||
|
||||
|
||||
--
|
||||
-- Name: error_log_record unique_log_record; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.error_log_record
|
||||
ADD CONSTRAINT unique_log_record UNIQUE (epoch, commit_hash, source_file, line, target, level, message);
|
||||
|
||||
|
||||
--
|
||||
-- Name: category unique_name; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.category
|
||||
ADD CONSTRAINT unique_name UNIQUE (name);
|
||||
|
||||
|
||||
--
|
||||
-- Name: pkg_category unique_pkg_category; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.pkg_category
|
||||
ADD CONSTRAINT unique_pkg_category UNIQUE (pkg_id, category_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: pkg_dependency unique_pkg_dep_version; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.pkg_dependency
|
||||
ADD CONSTRAINT unique_pkg_dep_version UNIQUE (pkg_id, pkg_version, dep_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: eos_hash unique_version; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.eos_hash
|
||||
ADD CONSTRAINT unique_version UNIQUE (version);
|
||||
|
||||
|
||||
--
|
||||
-- Name: upload upload_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.upload
|
||||
ADD CONSTRAINT upload_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_activity user_activity_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.user_activity
|
||||
ADD CONSTRAINT user_activity_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: version version_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.version
|
||||
ADD CONSTRAINT version_pkey PRIMARY KEY (pkg_id, number);
|
||||
|
||||
|
||||
--
|
||||
-- Name: version_platform version_platform_pkey; Type: CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.version_platform
|
||||
ADD CONSTRAINT version_platform_pkey PRIMARY KEY (pkg_id, version_number, arch);
|
||||
|
||||
|
||||
--
|
||||
-- Name: category_name_idx; Type: INDEX; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX category_name_idx ON public.category USING btree (name);
|
||||
|
||||
|
||||
--
|
||||
-- Name: pkg_record_pkg_id_idx; Type: INDEX; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX pkg_record_pkg_id_idx ON public.pkg_record USING btree (pkg_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: version_number_idx; Type: INDEX; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
CREATE INDEX version_number_idx ON public.version USING btree (number);
|
||||
|
||||
|
||||
--
|
||||
-- Name: admin_pkgs admin_pkgs_admin_fkey; Type: FK CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.admin_pkgs
|
||||
ADD CONSTRAINT admin_pkgs_admin_fkey FOREIGN KEY (admin) REFERENCES public.admin(id) ON UPDATE RESTRICT ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: metric metric_pkg_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.metric
|
||||
ADD CONSTRAINT metric_pkg_id_fkey FOREIGN KEY (pkg_id) REFERENCES public.pkg_record(pkg_id) ON UPDATE RESTRICT ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: pkg_category pkg_category_category_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.pkg_category
|
||||
ADD CONSTRAINT pkg_category_category_id_fkey FOREIGN KEY (category_id) REFERENCES public.category(id) ON UPDATE RESTRICT ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: pkg_category pkg_category_pkg_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.pkg_category
|
||||
ADD CONSTRAINT pkg_category_pkg_id_fkey FOREIGN KEY (pkg_id) REFERENCES public.pkg_record(pkg_id) ON UPDATE RESTRICT ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: pkg_dependency pkg_dependency_dep_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.pkg_dependency
|
||||
ADD CONSTRAINT pkg_dependency_dep_id_fkey FOREIGN KEY (dep_id) REFERENCES public.pkg_record(pkg_id) ON UPDATE RESTRICT ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: pkg_dependency pkg_dependency_pkg_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.pkg_dependency
|
||||
ADD CONSTRAINT pkg_dependency_pkg_id_fkey FOREIGN KEY (pkg_id) REFERENCES public.pkg_record(pkg_id) ON UPDATE RESTRICT ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: upload upload_pkg_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.upload
|
||||
ADD CONSTRAINT upload_pkg_id_fkey FOREIGN KEY (pkg_id) REFERENCES public.pkg_record(pkg_id) ON UPDATE RESTRICT ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: upload upload_uploader_fkey; Type: FK CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.upload
|
||||
ADD CONSTRAINT upload_uploader_fkey FOREIGN KEY (uploader) REFERENCES public.admin(id) ON UPDATE RESTRICT ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: version version_pkg_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.version
|
||||
ADD CONSTRAINT version_pkg_id_fkey FOREIGN KEY (pkg_id) REFERENCES public.pkg_record(pkg_id) ON UPDATE RESTRICT ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: version_platform version_platform_pkg_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: alpha_admin
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.version_platform
|
||||
ADD CONSTRAINT version_platform_pkg_id_fkey FOREIGN KEY (pkg_id) REFERENCES public.pkg_record(pkg_id) ON UPDATE RESTRICT ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use axum::Router;
|
||||
use futures::future::ready;
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::panic::UnwindSafe;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
use imbl_value::InternedString;
|
||||
use itertools::Itertools;
|
||||
use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler};
|
||||
@@ -13,7 +12,7 @@ use url::Url;
|
||||
|
||||
use crate::context::CliContext;
|
||||
use crate::prelude::*;
|
||||
use crate::progress::{FullProgressTracker, PhasedProgressBar};
|
||||
use crate::progress::{FullProgressTracker};
|
||||
use crate::registry::asset::RegistryAsset;
|
||||
use crate::registry::context::RegistryContext;
|
||||
use crate::registry::os::index::OsVersionInfo;
|
||||
@@ -25,6 +24,7 @@ use crate::s9pk::merkle_archive::hash::VerifyingWriter;
|
||||
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
||||
use crate::util::io::open_file;
|
||||
use crate::util::serde::Base64;
|
||||
use crate::util::VersionString;
|
||||
|
||||
@@ -184,7 +184,7 @@ pub async fn cli_add_asset(
|
||||
}
|
||||
};
|
||||
|
||||
let file = MultiCursorFile::from(tokio::fs::File::open(&path).await?);
|
||||
let file = MultiCursorFile::from(open_file(&path).await?);
|
||||
|
||||
let progress = FullProgressTracker::new();
|
||||
let mut sign_phase = progress.add_phase(InternedString::intern("Signing File"), Some(10));
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::registry::os::SIG_CONTEXT;
|
||||
use crate::registry::signer::commitment::blake3::Blake3Commitment;
|
||||
use crate::registry::signer::commitment::Commitment;
|
||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||
use crate::util::io::open_file;
|
||||
use crate::util::VersionString;
|
||||
|
||||
pub fn get_api<C: Context>() -> ParentHandler<C> {
|
||||
@@ -158,9 +159,7 @@ async fn cli_get_os_asset(
|
||||
if let Some(mut reverify_phase) = reverify_phase {
|
||||
reverify_phase.start();
|
||||
res.commitment
|
||||
.check(&MultiCursorFile::from(
|
||||
tokio::fs::File::open(download).await?,
|
||||
))
|
||||
.check(&MultiCursorFile::from(open_file(download).await?))
|
||||
.await?;
|
||||
reverify_phase.complete();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::registry::signer::sign::ed25519::Ed25519;
|
||||
use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey, SignatureScheme};
|
||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
||||
use crate::util::io::open_file;
|
||||
use crate::util::serde::Base64;
|
||||
use crate::util::VersionString;
|
||||
|
||||
@@ -166,7 +167,7 @@ pub async fn cli_sign_asset(
|
||||
}
|
||||
};
|
||||
|
||||
let file = MultiCursorFile::from(tokio::fs::File::open(&path).await?);
|
||||
let file = MultiCursorFile::from(open_file(&path).await?);
|
||||
|
||||
let progress = FullProgressTracker::new();
|
||||
let mut sign_phase = progress.add_phase(InternedString::intern("Signing File"), Some(10));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use emver::VersionRange;
|
||||
use exver::VersionRange;
|
||||
use imbl_value::InternedString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||