Compare commits

...

73 Commits

Author SHA1 Message Date
Aiden McClelland
dd28ad20ef use port instead of pidof to detect tor going down (#2320)
* use port instead of pidof to detect tor going down

* fix errors

* healthcheck timeout
2023-06-23 13:06:00 -06:00
Aiden McClelland
ef416ef60b prevent tor from spinning if a service is in a crash loop (#2316) 2023-06-22 18:09:59 +00:00
Aiden McClelland
95b3b55971 fix rootflags for btrfs update (#2315) 2023-06-21 15:26:27 +00:00
Aiden McClelland
b3f32ae03e don't use cp when over cifs 2023-06-21 00:36:36 +00:00
Aiden McClelland
c7472174e5 fix btrfs rootflags 2023-06-21 00:36:36 +00:00
gStart9
2ad749354d Add qemu-guest-agent for advanced VM shutdown options (#2309) 2023-06-21 00:36:36 +00:00
Aiden McClelland
4ed9d2ea22 add grub-common to build 2023-06-21 00:36:36 +00:00
Lucy Cifferello
280eb47de7 update marketplace project to include mime type pipe for icons 2023-06-21 00:36:36 +00:00
Aiden McClelland
324a12b0ff reset config after pg_upgrade 2023-06-21 00:36:36 +00:00
Aiden McClelland
a2543ccddc trim fs name 2023-06-21 00:36:36 +00:00
Aiden McClelland
22666412c3 use fsck instead of e2fsck 2023-06-21 00:36:36 +00:00
Aiden McClelland
dd58044cdf fix build 2023-06-21 00:36:36 +00:00
Aiden McClelland
10312d89d7 fix ipv6 2023-06-21 00:36:36 +00:00
Aiden McClelland
b4c0d877cb fix postgres migration 2023-06-21 00:36:36 +00:00
Aiden McClelland
e95d56a5d0 fix update-grub2 2023-06-21 00:36:36 +00:00
Aiden McClelland
90424e8329 install fixes 2023-06-21 00:36:36 +00:00
Aiden McClelland
1bfeb42a06 force btrfs creation 2023-06-21 00:36:36 +00:00
Aiden McClelland
a936f92954 use postgres user 2023-06-21 00:36:36 +00:00
Aiden McClelland
0bc514ec17 include old pg 2023-06-21 00:36:36 +00:00
Aiden McClelland
a2cf4001af improve invoke error reporting 2023-06-21 00:36:36 +00:00
Aiden McClelland
cb4e12a68c fix build 2023-06-21 00:36:36 +00:00
Aiden McClelland
a7f5124dfe postgresql migration 2023-06-21 00:36:36 +00:00
Aiden McClelland
ccbf71c5e7 fix ipv6 2023-06-21 00:36:36 +00:00
Aiden McClelland
04bf5f58d9 fix tor listener bug 2023-06-21 00:36:36 +00:00
Aiden McClelland
ab3f5956d4 ipv6 2023-06-21 00:36:36 +00:00
Aiden McClelland
c1fe8e583f backup target mount/umount 2023-06-21 00:36:36 +00:00
Lucy Cifferello
fd166c4433 do not load array buffer into memory 2023-06-21 00:36:36 +00:00
Aiden McClelland
f29c7ba4f2 don't wait for install to complete on sideload 2023-06-21 00:36:36 +00:00
Aiden McClelland
88869e9710 gpu acceleration 2023-06-21 00:36:36 +00:00
Aiden McClelland
f8404ab043 btrfs 2023-06-21 00:36:36 +00:00
Aiden McClelland
9fa5d1ff9e suite independent 2023-06-21 00:36:36 +00:00
Aiden McClelland
483f353fd0 backup luks headers 2023-06-21 00:36:36 +00:00
Aiden McClelland
a11bf5b5c7 bookworm 2023-06-21 00:36:36 +00:00
Aiden McClelland
d4113ff753 re-add server version and version range 2023-06-21 00:36:36 +00:00
Aiden McClelland
1969f036fa deser full server info 2023-06-21 00:36:36 +00:00
Matt Hill
8c90e01016 hide range ip addresses, update release notes 2023-06-15 13:20:37 -06:00
Matt Hill
756c5c9b99 small spelling mistake 2023-06-11 15:04:59 -06:00
Lucy Cifferello
ee54b355af fix compliation error on widgets page 2023-06-11 15:04:59 -06:00
Lucy Cifferello
26cbbc0c56 adjust start9 registry icon 2023-06-11 15:04:59 -06:00
Aiden McClelland
f4f719d52a misc fixes 2023-06-11 15:04:59 -06:00
Aiden McClelland
f2071d8b7e update zram bool 2023-06-11 15:04:59 -06:00
Aiden McClelland
df88a55784 v0.3.4.3 2023-06-11 15:04:59 -06:00
Matt Hill
3ccbc626ff experimental features for zram and reset tor (#2299)
* experimental features for zram and reset tor

* zram backend

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
2023-06-11 15:04:59 -06:00
Aiden McClelland
71a15cf222 add diskUsage effect (#2297) 2023-06-11 15:04:59 -06:00
Aiden McClelland
26ddf769b1 remove overload restart rule 2023-06-11 15:04:59 -06:00
Aiden McClelland
3137387c0c only set static hostname 2023-06-11 15:04:59 -06:00
Aiden McClelland
fc142cfde8 reset tor (#2296)
* reset tor

* Update tor.rs

* timeout connect

* handle stuck bootstrapping
2023-06-11 15:04:59 -06:00
Aiden McClelland
b0503fa507 Bugfix/incoherent (#2293)
* debug incoherent error

* fix incoherent error

* use new debspawn
2023-06-11 15:04:59 -06:00
Matt Hill
b86a97c9c0 add resetTor to rpc client 2023-06-11 15:04:59 -06:00
Lucy Cifferello
eb6cd23772 update registry icon 2023-06-11 15:04:59 -06:00
Matt Hill
efae1e7e6c add Tor logs to UI 2023-06-11 15:04:59 -06:00
Lucy Cifferello
19d55b840e add registry icon to preloader 2023-06-11 15:04:59 -06:00
Lucy Cifferello
cc0c1d05ab update frontend to 0.3.4.3 2023-06-11 15:04:59 -06:00
Lucy Cifferello
f088f65d5a update branding 2023-06-11 15:04:59 -06:00
Lucy Cifferello
5441b5a06b add missing items to preloader 2023-06-11 15:04:59 -06:00
gStart9
efc56c0a88 Add crda to build/lib/depends (#2283) 2023-05-24 15:54:33 -07:00
kn0wmad
321fca2c0a Replace some user-facing Embassy language (#2281) 2023-05-22 13:23:20 -06:00
Matt Hill
bbd66e9cb0 fix nav link (#2279) 2023-05-18 18:11:27 -06:00
Aiden McClelland
eb0277146c wait for tor (#2278) 2023-05-17 22:17:27 -06:00
Aiden McClelland
10ee32ec48 always generate snake-oil (#2277) 2023-05-17 15:09:27 -06:00
Aiden McClelland
bdb4be89ff Bugfix/pi config (#2276)
* move some install scripts to init

* fix pi config.txt

* move some image stuff to the squashfs build

* no need to clean up fake-apt

* use max temp
2023-05-16 16:06:25 -06:00
Aiden McClelland
61445e0b56 build fixes (#2275)
* move some install scripts to init

* handle fake-apt in init

* rename
2023-05-15 16:34:30 -06:00
Aiden McClelland
f15a010e0e Update build badge (#2274)
Update README.md
2023-05-14 00:01:58 -06:00
Lucy C
58747004fe Fix/misc frontend (#2273)
* update pwa icon to official latest

* fix bug if icon is null in assets

* dismiss modal when connecting to a new registry
2023-05-12 14:48:16 -06:00
Lucy C
e7ff1eb66b display icons based on mime type (#2271)
* display icons based on mime type

* Update frontend/projects/marketplace/src/pipes/mime-type.pipe.ts

Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>

* fixes

---------

Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
2023-05-12 12:20:05 -06:00
Matt Hill
4a00bd4797 ensure lan address present before getting cert name (#2272) 2023-05-12 12:18:39 -06:00
Aiden McClelland
2e6fc7e4a0 v0.3.4.2 (#2269) 2023-05-12 00:35:50 -06:00
Aiden McClelland
4a8f323be7 external rename (#2265)
* backend rename

* rename embassy and closes #2179

* update root ca name on disk

* update MOTD

* update readmes

* your server typo

* another tiny typo

* fix png name

* Update backend/src/net/wifi.rs

Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>

* changes needed due to rebase

---------

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com>
Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>
2023-05-11 16:48:52 -06:00
Aiden McClelland
c7d82102ed Bugfix/gpt reflash (#2266)
* debug entry

* update magic numbers

* remove dbg

* fix hostname

* fix reinstall logic
2023-05-11 14:16:19 -06:00
Aiden McClelland
068b861edc overhaul OS build (#2244)
* create init resize for pi

* wip

* defer to OS_ARCH env var

* enable password auth in live image

* use correct live image path

* reorder dependencies

* add grub-common as dependency

* add more depends

* reorder grub

* include systemd-resolved

* misc fixes

* remove grub from dependencies

* imports

* ssh and raspi builds

* fix resolvectl

* generate snake-oil on install

* update raspi build process

* script fixes

* fix resize and config

* add psmisc

* new workflows

* include img

* pass through OS_ARCH env var

* require OS_ARCH

* allow dispatching production builds

* configurable environment

* pass through OS_ARCH on compat build

* fix syntax error

* crossbuild dependencies

* include libavahi-client for cross builds

* reorder add-arch

* add ports

* switch existing repos to amd64

* explicitly install libc6

* add more bullshit

* fix some errors

* use ignored shlibs

* remove ubuntu ports

* platform deb

* Update depends

* Update startos-iso.yaml

* Update startos-iso.yaml

* require pi-beep

* add bios boot, fix environment

* Update startos-iso.yaml

* inline deb

* Update startos-iso.yaml

* allow ssh password auth in live build

* sync hostname on livecd

* require curl
2023-05-05 00:54:09 -06:00
kn0wmad
3c908c6a09 Update README.md (#2261)
Minor typo fix
2023-05-02 06:26:54 -06:00
Lucy C
ba3805786c Feature/pwa (#2246)
* setup ui project with pwa configurations

* enable service worker config to work with ionic livereload

* fix service worker key placement

* update webmanifest names

* cleanup

* shrink logo size

* fix package build

* build fix

* fix icon size in webmanifest
2023-04-11 10:36:25 -06:00
Aiden McClelland
70afb197f1 don't attempt docker load if s9pk corrupted (#2236) 2023-03-21 11:23:44 -06:00
220 changed files with 3999 additions and 2479 deletions

View File

@@ -1,6 +1,6 @@
name: 🐛 Bug Report
description: Create a report to help us improve embassyOS
title: '[bug]: '
description: Create a report to help us improve StartOS
title: "[bug]: "
labels: [Bug, Needs Triage]
assignees:
- MattDHill
@@ -10,19 +10,19 @@ body:
label: Prerequisites
description: Please confirm you have completed the following.
options:
- label: I have searched for [existing issues](https://github.com/start9labs/embassy-os/issues) that already report this problem.
- label: I have searched for [existing issues](https://github.com/start9labs/start-os/issues) that already report this problem.
required: true
- type: input
attributes:
label: embassyOS Version
description: What version of embassyOS are you running?
placeholder: e.g. 0.3.0
label: StartOS Version
description: What version of StartOS are you running?
placeholder: e.g. 0.3.4.2
validations:
required: true
- type: dropdown
attributes:
label: Device
description: What device are you using to connect to Embassy?
description: What device are you using to connect to your server?
options:
- Phone/tablet
- Laptop/Desktop
@@ -52,7 +52,7 @@ body:
- type: dropdown
attributes:
label: Browser
description: What browser are you using to connect to Embassy?
description: What browser are you using to connect to your server?
options:
- Firefox
- Brave

View File

@@ -1,6 +1,6 @@
name: 💡 Feature Request
description: Suggest an idea for embassyOS
title: '[feat]: '
description: Suggest an idea for StartOS
title: "[feat]: "
labels: [Enhancement]
assignees:
- MattDHill
@@ -10,7 +10,7 @@ body:
label: Prerequisites
description: Please confirm you have completed the following.
options:
- label: I have searched for [existing issues](https://github.com/start9labs/embassy-os/issues) that already suggest this feature.
- label: I have searched for [existing issues](https://github.com/start9labs/start-os/issues) that already suggest this feature.
required: true
- type: textarea
attributes:
@@ -27,7 +27,7 @@ body:
- type: textarea
attributes:
label: Describe Preferred Solution
description: How you want this feature added to embassyOS?
description: How you want this feature added to StartOS?
- type: textarea
attributes:
label: Describe Alternatives

View File

@@ -1,63 +0,0 @@
name: Debian Package
on:
workflow_call:
workflow_dispatch:
env:
NODEJS_VERSION: '16.11.0'
ENVIRONMENT: "dev"
jobs:
dpkg:
name: Build dpkg
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
repository: Start9Labs/embassy-os-deb
- uses: actions/checkout@v3
with:
submodules: recursive
path: embassyos-0.3.x
- run: |
cp -r debian embassyos-0.3.x/
VERSION=0.3.x ./control.sh
cp embassyos-0.3.x/backend/embassyd.service embassyos-0.3.x/debian/embassyos.embassyd.service
cp embassyos-0.3.x/backend/embassy-init.service embassyos-0.3.x/debian/embassyos.embassy-init.service
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODEJS_VERSION }}
- name: Get npm cache directory
id: npm-cache-dir
run: |
echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
id: npm-cache
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install debmake debhelper-compat
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Run build
run: "make VERSION=0.3.x TAG=${{ github.ref_name }}"
- uses: actions/upload-artifact@v3
with:
name: deb
path: embassyos_0.3.x-1_amd64.deb

View File

@@ -5,7 +5,7 @@ on:
workflow_dispatch:
env:
NODEJS_VERSION: '16.11.0'
NODEJS_VERSION: '18.15.0'
ENVIRONMENT: "dev"
jobs:

View File

@@ -1,129 +0,0 @@
name: Build Pipeline
on:
workflow_dispatch:
push:
branches:
- master
- next
pull_request:
branches:
- master
- next
env:
ENVIRONMENT: "dev"
jobs:
compat:
uses: ./.github/workflows/reusable-workflow.yaml
with:
build_command: make system-images/compat/docker-images/aarch64.tar
artifact_name: compat.tar
artifact_path: system-images/compat/docker-images/aarch64.tar
utils:
uses: ./.github/workflows/reusable-workflow.yaml
with:
build_command: make system-images/utils/docker-images/aarch64.tar
artifact_name: utils.tar
artifact_path: system-images/utils/docker-images/aarch64.tar
binfmt:
uses: ./.github/workflows/reusable-workflow.yaml
with:
build_command: make system-images/binfmt/docker-images/aarch64.tar
artifact_name: binfmt.tar
artifact_path: system-images/binfmt/docker-images/aarch64.tar
backend:
uses: ./.github/workflows/backend.yaml
frontend:
uses: ./.github/workflows/frontend.yaml
image:
name: Build image
runs-on: ubuntu-latest
timeout-minutes: 60
needs: [compat,utils,binfmt,backend,frontend]
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Download compat.tar artifact
uses: actions/download-artifact@v3
with:
name: compat.tar
path: system-images/compat/docker-images/
- name: Download utils.tar artifact
uses: actions/download-artifact@v3
with:
name: utils.tar
path: system-images/utils/docker-images/
- name: Download binfmt.tar artifact
uses: actions/download-artifact@v3
with:
name: binfmt.tar
path: system-images/binfmt/docker-images/
- name: Download js_snapshot artifact
uses: actions/download-artifact@v3
with:
name: js_snapshot
path: libs/js_engine/src/artifacts/
- name: Download arm_js_snapshot artifact
uses: actions/download-artifact@v3
with:
name: arm_js_snapshot
path: libs/js_engine/src/artifacts/
- name: Download backend artifact
uses: actions/download-artifact@v3
with:
name: backend-aarch64
- name: 'Extract backend'
run:
tar -mxvf backend-aarch64.tar
- name: Download frontend artifact
uses: actions/download-artifact@v3
with:
name: frontend
- name: Skip frontend build
run: |
mkdir frontend/node_modules
mkdir frontend/dist
mkdir patch-db/client/node_modules
mkdir patch-db/client/dist
- name: 'Extract frontend'
run: |
tar -mxvf frontend.tar frontend/config.json
tar -mxvf frontend.tar frontend/dist
tar -xvf frontend.tar GIT_HASH.txt
tar -xvf frontend.tar ENVIRONMENT.txt
tar -xvf frontend.tar VERSION.txt
rm frontend.tar
- name: Cache raspiOS
id: cache-raspios
uses: actions/cache@v3
with:
path: raspios.img
key: cache-raspios
- name: Build image
run: |
make V=1 eos_raspberrypi-uninit.img --debug
- uses: actions/upload-artifact@v3
with:
name: image
path: eos_raspberrypi-uninit.img

View File

@@ -1,70 +0,0 @@
name: PureOS Based ISO
on:
workflow_call:
workflow_dispatch:
push:
branches:
- master
- next
pull_request:
branches:
- master
- next
env:
ENVIRONMENT: "dev"
jobs:
dpkg:
uses: ./.github/workflows/debian.yaml
iso:
name: Build iso
runs-on: ubuntu-22.04
needs: [dpkg]
steps:
- uses: actions/checkout@v3
with:
repository: Start9Labs/eos-image-recipes
- name: Install dependencies
run: |
sudo apt update
wget http://ftp.us.debian.org/debian/pool/main/d/debspawn/debspawn_0.6.1-1_all.deb
sha256sum ./debspawn_0.6.1-1_all.deb | grep fb8a3f588438ff9ef51e713ec1d83306db893f0aa97447565e28bbba9c6e90c6
sudo apt-get install -y ./debspawn_0.6.1-1_all.deb
wget https://repo.pureos.net/pureos/pool/main/d/debootstrap/debootstrap_1.0.125pureos1_all.deb
sudo apt-get install -y --allow-downgrades ./debootstrap_1.0.125pureos1_all.deb
wget https://repo.pureos.net/pureos/pool/main/p/pureos-archive-keyring/pureos-archive-keyring_2021.11.0_all.deb
sudo apt-get install -y ./pureos-archive-keyring_2021.11.0_all.deb
- name: Configure debspawn
run: |
sudo mkdir -p /etc/debspawn/
echo "AllowUnsafePermissions=true" | sudo tee /etc/debspawn/global.toml
- uses: actions/cache@v3
with:
path: /var/lib/debspawn
key: ${{ runner.os }}-debspawn-init-byzantium
- name: Make build container
run: "debspawn list | grep byzantium || debspawn create --with-init byzantium"
- run: "mkdir -p overlays/vendor/root"
- name: Download dpkg
uses: actions/download-artifact@v3
with:
name: deb
path: overlays/vendor/root
- name: Run build
run: |
./run-local-build.sh --no-fakemachine byzantium none custom "" true
- uses: actions/upload-artifact@v3
with:
name: iso
path: results/*.iso

170
.github/workflows/startos-iso.yaml vendored Normal file
View File

@@ -0,0 +1,170 @@
name: Debian-based ISO and SquashFS
on:
workflow_call:
workflow_dispatch:
inputs:
environment:
type: choice
description: Environment
options:
- "<NONE>"
- dev
- unstable
- dev-unstable
push:
branches:
- master
- next
pull_request:
branches:
- master
- next
env:
NODEJS_VERSION: "18.15.0"
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''<NONE>''] }}'
jobs:
dpkg:
name: Build dpkg
strategy:
fail-fast: false
matrix:
platform:
[x86_64, x86_64-nonfree, aarch64, aarch64-nonfree, raspberrypi]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
repository: Start9Labs/embassy-os-deb
- uses: actions/checkout@v3
with:
submodules: recursive
path: embassyos-0.3.x
- run: |
cp -r debian embassyos-0.3.x/
VERSION=0.3.x ./control.sh
cp embassyos-0.3.x/backend/embassyd.service embassyos-0.3.x/debian/embassyos.embassyd.service
cp embassyos-0.3.x/backend/embassy-init.service embassyos-0.3.x/debian/embassyos.embassy-init.service
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODEJS_VERSION }}
- name: Get npm cache directory
id: npm-cache-dir
run: |
echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
id: npm-cache
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install \
debmake \
debhelper-compat \
crossbuild-essential-arm64
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Run build
run: "make VERSION=0.3.x TAG=${{ github.ref_name }}"
env:
OS_ARCH: ${{ matrix.platform }}
- uses: actions/upload-artifact@v3
with:
name: ${{ matrix.platform }}.deb
path: embassyos_0.3.x-1_*.deb
iso:
name: Build iso
strategy:
fail-fast: false
matrix:
platform:
[x86_64, x86_64-nonfree, aarch64, aarch64-nonfree, raspberrypi]
runs-on: ubuntu-22.04
needs: [dpkg]
steps:
- uses: actions/checkout@v3
with:
repository: Start9Labs/startos-image-recipes
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y qemu-user-static
wget https://deb.debian.org/debian/pool/main/d/debspawn/debspawn_0.6.2-1_all.deb
sha256sum ./debspawn_0.6.2-1_all.deb | grep 37ef27458cb1e35e8bce4d4f639b06b4b3866fc0b9191ec6b9bd157afd06a817
sudo apt-get install -y ./debspawn_0.6.2-1_all.deb
- name: Configure debspawn
run: |
sudo mkdir -p /etc/debspawn/
echo "AllowUnsafePermissions=true" | sudo tee /etc/debspawn/global.toml
- uses: actions/cache@v3
with:
path: /var/lib/debspawn
key: ${{ runner.os }}-debspawn-init
- run: "mkdir -p overlays/deb"
- name: Download dpkg
uses: actions/download-artifact@v3
with:
name: ${{ matrix.platform }}.deb
path: overlays/deb
- name: Run build
run: |
./run-local-build.sh ${{ matrix.platform }}
- uses: actions/upload-artifact@v3
with:
name: ${{ matrix.platform }}.squashfs
path: results/*.squashfs
- uses: actions/upload-artifact@v3
with:
name: ${{ matrix.platform }}.iso
path: results/*.iso
if: ${{ matrix.platform != 'raspberrypi' }}
image:
name: Build image
runs-on: ubuntu-22.04
timeout-minutes: 60
needs: [iso]
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Download raspberrypi.squashfs artifact
uses: actions/download-artifact@v3
with:
name: raspberrypi.squashfs
- run: mv startos-*_raspberrypi.squashfs startos.raspberrypi.squashfs
- name: Build image
run: make startos_raspberrypi.img
- uses: actions/upload-artifact@v3
with:
name: raspberrypi.img
path: startos-*_raspberrypi.img

View File

@@ -1,6 +1,6 @@
<!-- omit in toc -->
# Contributing to Embassy OS
# Contributing to StartOS
First off, thanks for taking the time to contribute! ❤️
@@ -19,7 +19,7 @@ forward to your contributions. 🎉
> - Tweet about it
> - Refer this project in your project's readme
> - Mention the project at local meetups and tell your friends/colleagues
> - Buy an [Embassy](https://start9labs.com)
> - Buy a [Start9 server](https://start9.com)
<!-- omit in toc -->
@@ -49,7 +49,7 @@ forward to your contributions. 🎉
> [Documentation](https://docs.start9labs.com).
Before you ask a question, it is best to search for existing
[Issues](https://github.com/Start9Labs/embassy-os/issues) that might help you.
[Issues](https://github.com/Start9Labs/start-os/issues) that might help you.
In case you have found a suitable issue and still need clarification, you can
write your question in this issue. It is also advisable to search the internet
for answers first.
@@ -57,7 +57,7 @@ for answers first.
If you then still feel the need to ask a question and need clarification, we
recommend the following:
- Open an [Issue](https://github.com/Start9Labs/embassy-os/issues/new).
- Open an [Issue](https://github.com/Start9Labs/start-os/issues/new).
- Provide as much context as you can about what you're running into.
- Provide project and platform versions, depending on what seems relevant.
@@ -105,7 +105,7 @@ steps in advance to help us fix any potential bug as fast as possible.
- To see if other users have experienced (and potentially already solved) the
same issue you are having, check if there is not already a bug report existing
for your bug or error in the
[bug tracker](https://github.com/Start9Labs/embassy-os/issues?q=label%3Abug).
[bug tracker](https://github.com/Start9Labs/start-os/issues?q=label%3Abug).
- Also make sure to search the internet (including Stack Overflow) to see if
users outside of the GitHub community have discussed the issue.
- Collect information about the bug:
@@ -131,7 +131,7 @@ steps in advance to help us fix any potential bug as fast as possible.
We use GitHub issues to track bugs and errors. If you run into an issue with the
project:
- Open an [Issue](https://github.com/Start9Labs/embassy-os/issues/new/choose)
- Open an [Issue](https://github.com/Start9Labs/start-os/issues/new/choose)
selecting the appropriate type.
- Explain the behavior you would expect and the actual behavior.
- Please provide as much context as possible and describe the _reproduction
@@ -155,8 +155,7 @@ Once it's filed:
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for Embassy
OS, **including completely new features and minor improvements to existing
This section guides you through submitting an enhancement suggestion for StartOS, **including completely new features and minor improvements to existing
functionality**. Following these guidelines will help maintainers and the
community to understand your suggestion and find related suggestions.
@@ -168,7 +167,7 @@ community to understand your suggestion and find related suggestions.
- Read the [documentation](https://start9.com/latest/user-manual) carefully and
find out if the functionality is already covered, maybe by an individual
configuration.
- Perform a [search](https://github.com/Start9Labs/embassy-os/issues) to see if
- Perform a [search](https://github.com/Start9Labs/start-os/issues) to see if
the enhancement has already been suggested. If it has, add a comment to the
existing issue instead of opening a new one.
- Find out whether your idea fits with the scope and aims of the project. It's
@@ -182,7 +181,7 @@ community to understand your suggestion and find related suggestions.
#### How Do I Submit a Good Enhancement Suggestion?
Enhancement suggestions are tracked as
[GitHub issues](https://github.com/Start9Labs/embassy-os/issues).
[GitHub issues](https://github.com/Start9Labs/start-os/issues).
- Use a **clear and descriptive title** for the issue to identify the
suggestion.
@@ -197,7 +196,7 @@ Enhancement suggestions are tracked as
macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast)
or [this tool](https://github.com/GNOME/byzanz) on Linux.
<!-- this should only be included if the project has a GUI -->
- **Explain why this enhancement would be useful** to most Embassy OS users. You
- **Explain why this enhancement would be useful** to most StartOS users. You
may also want to point out the other projects that solved it better and which
could serve as inspiration.
@@ -205,24 +204,24 @@ Enhancement suggestions are tracked as
### Project Structure
embassyOS is composed of the following components. Please visit the README for
StartOS is composed of the following components. Please visit the README for
each component to understand the dependency requirements and installation
instructions.
- [`backend`](backend/README.md) (Rust) is a command line utility, daemon, and
software development kit that sets up and manages services and their
environments, provides the interface for the ui, manages system state, and
provides utilities for packaging services for embassyOS.
provides utilities for packaging services for StartOS.
- [`build`](build/README.md) contains scripts and necessary for deploying
embassyOS to a debian/raspbian system.
StartOS to a debian/raspbian system.
- [`frontend`](frontend/README.md) (Typescript Ionic Angular) is the code that
is deployed to the browser to provide the user interface for embassyOS.
- `projects/ui` - Code for the user interface that is displayed when embassyOS
is deployed to the browser to provide the user interface for StartOS.
- `projects/ui` - Code for the user interface that is displayed when StartOS
is running normally.
- `projects/setup-wizard`(frontend/README.md) - Code for the user interface
that is displayed during the setup and recovery process for embassyOS.
that is displayed during the setup and recovery process for StartOS.
- `projects/diagnostic-ui` - Code for the user interface that is displayed
when something has gone wrong with starting up embassyOS, which provides
when something has gone wrong with starting up StartOS, which provides
helpful debugging tools.
- `libs` (Rust) is a set of standalone crates that were separated out of
`backend` for the purpose of portability
@@ -232,18 +231,18 @@ instructions.
[client](https://github.com/Start9Labs/patch-db/tree/master/client) with its
own dependency and installation requirements.
- `system-images` - (Docker, Rust) A suite of utility Docker images that are
preloaded with embassyOS to assist with functions relating to services (eg.
preloaded with StartOS to assist with functions relating to services (eg.
configuration, backups, health checks).
### Your First Code Contribution
#### Setting Up Your Development Environment
First, clone the embassyOS repository and from the project root, pull in the
First, clone the StartOS repository and from the project root, pull in the
submodules for dependent libraries.
```sh
git clone https://github.com/Start9Labs/embassy-os.git
git clone https://github.com/Start9Labs/start-os.git
git submodule update --init --recursive
```
@@ -254,7 +253,7 @@ to, follow the installation requirements listed in that component's README
#### Building The Raspberry Pi Image
This step is for setting up an environment in which to test your code changes if
you do not yet have a embassyOS.
you do not yet have a StartOS.
- Requirements
- `ext4fs` (available if running on the Linux kernel)
@@ -262,7 +261,7 @@ you do not yet have a embassyOS.
- GNU Make
- Building
- see setup instructions [here](build/README.md)
- run `make embassyos-raspi.img ARCH=aarch64` from the project root
- run `make startos-raspi.img ARCH=aarch64` from the project root
### Improving The Documentation
@@ -286,7 +285,7 @@ seamless and intuitive experience.
### Formatting
Each component of embassyOS contains its own style guide. Code must be formatted
Each component of StartOS contains its own style guide. Code must be formatted
with the formatter designated for each component. These are outlined within each
component folder's README.
@@ -306,7 +305,7 @@ component. i.e. `backend: update to tokio v0.3`.
The body of a pull request should contain sufficient description of what the
changes do, as well as a justification. You should include references to any
relevant [issues](https://github.com/Start9Labs/embassy-os/issues).
relevant [issues](https://github.com/Start9Labs/start-os/issues).
### Rebasing Changes

View File

@@ -1,6 +1,5 @@
RASPI_TARGETS := eos_raspberrypi-uninit.img eos_raspberrypi-uninit.tar.gz
OS_ARCH := $(shell if echo $(RASPI_TARGETS) | grep -qw "$(MAKECMDGOALS)"; then echo raspberrypi; else uname -m; fi)
ARCH := $(shell if [ "$(OS_ARCH)" = "raspberrypi" ]; then echo aarch64; else echo $(OS_ARCH); fi)
OS_ARCH := $(shell echo "${OS_ARCH}")
ARCH := $(shell if [ "$(OS_ARCH)" = "raspberrypi" ]; then echo aarch64; else echo $(OS_ARCH) | sed 's/-nonfree$$//g'; fi)
ENVIRONMENT_FILE = $(shell ./check-environment.sh)
GIT_HASH_FILE = $(shell ./check-git-hash.sh)
VERSION_FILE = $(shell ./check-version.sh)
@@ -19,7 +18,7 @@ FRONTEND_DIAGNOSTIC_UI_SRC := $(shell find frontend/projects/diagnostic-ui)
FRONTEND_INSTALL_WIZARD_SRC := $(shell find frontend/projects/install-wizard)
PATCH_DB_CLIENT_SRC := $(shell find patch-db/client -not -path patch-db/client/dist)
GZIP_BIN := $(shell which pigz || which gzip)
ALL_TARGETS := $(EMBASSY_BINS) system-images/compat/docker-images/$(ARCH).tar system-images/utils/docker-images/$(ARCH).tar system-images/binfmt/docker-images/$(ARCH).tar $(EMBASSY_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE)
ALL_TARGETS := $(EMBASSY_BINS) system-images/compat/docker-images/$(ARCH).tar system-images/utils/docker-images/$(ARCH).tar system-images/binfmt/docker-images/$(ARCH).tar $(EMBASSY_SRC) $(shell if [ "$(OS_ARCH)" = "raspberrypi" ]; then echo cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep; fi) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE)
ifeq ($(REMOTE),)
mkdir = mkdir -p $1
@@ -35,7 +34,7 @@ endif
.DELETE_ON_ERROR:
.PHONY: all gzip install clean format sdk snapshots frontends ui backend reflash eos_raspberrypi.img sudo
.PHONY: all gzip install clean format sdk snapshots frontends ui backend reflash startos_raspberrypi.img sudo
all: $(ALL_TARGETS)
@@ -43,12 +42,6 @@ sudo:
sudo true
clean:
rm -f 2022-01-28-raspios-bullseye-arm64-lite.zip
rm -f raspios.img
rm -f eos_raspberrypi-uninit.img
rm -f eos_raspberrypi-uninit.tar.gz
rm -f ubuntu.img
rm -f product_key.txt
rm -f system-images/**/*.tar
rm -rf system-images/compat/target
rm -rf backend/target
@@ -72,17 +65,8 @@ format:
sdk:
cd backend/ && ./install-sdk.sh
eos_raspberrypi-uninit.img: $(ALL_TARGETS) raspios.img cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep | sudo
! test -f eos_raspberrypi-uninit.img || rm eos_raspberrypi-uninit.img
./build/raspberry-pi/make-image.sh
lite-upgrade.img: raspios.img cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep $(BUILD_SRC) eos.raspberrypi.squashfs
! test -f lite-upgrade.img || rm lite-upgrade.img
./build/raspberry-pi/make-upgrade-image.sh
eos_raspberrypi.img: raspios.img $(BUILD_SRC) eos.raspberrypi.squashfs $(VERSION_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) | sudo
! test -f eos_raspberrypi.img || rm eos_raspberrypi.img
./build/raspberry-pi/make-initialized-image.sh
startos_raspberrypi.img: $(BUILD_SRC) startos.raspberrypi.squashfs $(VERSION_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep | sudo
./build/raspberrypi/make-image.sh
# For creating os images. DO NOT USE
install: $(ALL_TARGETS)
@@ -91,6 +75,7 @@ install: $(ALL_TARGETS)
$(call cp,backend/target/$(ARCH)-unknown-linux-gnu/release/embassyd,$(DESTDIR)/usr/bin/embassyd)
$(call cp,backend/target/$(ARCH)-unknown-linux-gnu/release/embassy-cli,$(DESTDIR)/usr/bin/embassy-cli)
$(call cp,backend/target/$(ARCH)-unknown-linux-gnu/release/avahi-alias,$(DESTDIR)/usr/bin/avahi-alias)
if [ "$(OS_ARCH)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi
$(call mkdir,$(DESTDIR)/usr/lib)
$(call rm,$(DESTDIR)/usr/lib/embassy)
@@ -183,7 +168,7 @@ frontend/config.json: $(GIT_HASH_FILE) frontend/config-sample.json
npm --prefix frontend run-script build-config
frontend/patchdb-ui-seed.json: frontend/package.json
jq '."ack-welcome" = "$(shell yq '.version' frontend/package.json)"' frontend/patchdb-ui-seed.json > ui-seed.tmp
jq '."ack-welcome" = $(shell yq '.version' frontend/package.json)' frontend/patchdb-ui-seed.json > ui-seed.tmp
mv ui-seed.tmp frontend/patchdb-ui-seed.json
patch-db/client/node_modules: patch-db/client/package.json

View File

@@ -1,6 +1,6 @@
# embassyOS
[![version](https://img.shields.io/github/v/tag/Start9Labs/embassy-os?color=success)](https://github.com/Start9Labs/embassy-os/releases)
[![build](https://github.com/Start9Labs/embassy-os/actions/workflows/product.yaml/badge.svg)](https://github.com/Start9Labs/embassy-os/actions/workflows/product.yaml)
# StartOS
[![version](https://img.shields.io/github/v/tag/Start9Labs/start-os?color=success)](https://github.com/Start9Labs/start-os/releases)
[![build](https://github.com/Start9Labs/start-os/actions/workflows/startos-iso.yaml/badge.svg)](https://github.com/Start9Labs/start-os/actions/workflows/startos-iso.yaml)
[![community](https://img.shields.io/badge/community-matrix-yellow)](https://matrix.to/#/#community:matrix.start9labs.com)
[![community](https://img.shields.io/badge/community-telegram-informational)](https://t.me/start9_labs)
[![support](https://img.shields.io/badge/support-docs-important)](https://docs.start9.com)
@@ -12,16 +12,16 @@
### _Welcome to the era of Sovereign Computing_ ###
embassyOS is a browser-based, graphical operating system for a personal server. embassyOS facilitates the discovery, installation, network configuration, service configuration, data backup, dependency management, and health monitoring of self-hosted software services. It is the most advanced, secure, reliable, and user friendly personal server OS in the world.
StartOS is a browser-based, graphical operating system for a personal server. StartOS facilitates the discovery, installation, network configuration, service configuration, data backup, dependency management, and health monitoring of self-hosted software services. It is the most advanced, secure, reliable, and user friendly personal server OS in the world.
## Running embassyOS
There are multiple ways to get your hands on embassyOS.
## Running StartOS
There are multiple ways to get your hands on StartOS.
### :moneybag: Buy an Embassy
This is the most convenient option. Simply [buy an Embassy](https://start9.com) from Start9 and plug it in. Depending on where you live, shipping costs and import duties will vary.
### :moneybag: Buy a Start9 server
This is the most convenient option. Simply [buy a server](https://start9.com) from Start9 and plug it in. Depending on where you live, shipping costs and import duties will vary.
### :construction_worker: Build your own Embassy
While not as convenient as buying an Embassy, this option is easier than you might imagine, and there are 4 reasons why you might prefer it:
### :construction_worker: Build your own server
This option is easier than you might imagine, and there are 4 reasons why you might prefer it:
1. You already have your own hardware.
1. You want to save on shipping costs.
1. You prefer not to divulge your physical address.
@@ -29,23 +29,23 @@ While not as convenient as buying an Embassy, this option is easier than you mig
To pursue this option, follow one of our [DIY guides](https://start9.com/latest/diy).
### :hammer_and_wrench: Build embassyOS from Source
### :hammer_and_wrench: Build StartOS from Source
embassyOS can be built from source, for personal use, for free.
A detailed guide for doing so can be found [here](https://github.com/Start9Labs/embassy-os/blob/master/build/README.md).
StartOS can be built from source, for personal use, for free.
A detailed guide for doing so can be found [here](https://github.com/Start9Labs/start-os/blob/master/build/README.md).
## :heart: Contributing
There are multiple ways to contribute: work directly on embassyOS, package a service for the marketplace, or help with documentation and guides. To learn more about contributing, see [here](https://docs.start9.com/latest/contribute/) or [here](https://github.com/Start9Labs/embassy-os/blob/master/CONTRIBUTING.md).
There are multiple ways to contribute: work directly on StartOS, package a service for the marketplace, or help with documentation and guides. To learn more about contributing, see [here](https://docs.start9.com/latest/contribute/) or [here](https://github.com/Start9Labs/start-os/blob/master/CONTRIBUTING.md).
## UI Screenshots
<p align="center">
<img src="assets/embassyOS.png" alt="embassyOS" width="85%">
<img src="assets/StartOS.png" alt="StartOS" width="85%">
</p>
<p align="center">
<img src="assets/eOS-preferences.png" alt="Embassy Preferences" width="49%">
<img src="assets/eOS-ghost.png" alt="Embassy Ghost Service" width="49%">
<img src="assets/preferences.png" alt="StartOS Preferences" width="49%">
<img src="assets/ghost.png" alt="StartOS Ghost Service" width="49%">
</p>
<p align="center">
<img src="assets/eOS-synapse-health-check.png" alt="Embassy Synapse Health Checks" width="49%">
<img src="assets/eOS-sideload.png" alt="Embassy Sideload Service" width="49%">
<img src="assets/synapse-health-check.png" alt="StartOS Synapse Health Checks" width="49%">
<img src="assets/sideload.png" alt="StartOS Sideload Service" width="49%">
</p>

View File

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 191 KiB

View File

Before

Width:  |  Height:  |  Size: 281 KiB

After

Width:  |  Height:  |  Size: 281 KiB

View File

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 266 KiB

View File

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

View File

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 213 KiB

4
backend/Cargo.lock generated
View File

@@ -1354,7 +1354,7 @@ dependencies = [
[[package]]
name = "embassy-os"
version = "0.3.4-rev.1"
version = "0.3.4-rev.3"
dependencies = [
"aes",
"async-compression",
@@ -1442,6 +1442,7 @@ dependencies = [
"thiserror",
"tokio",
"tokio-rustls",
"tokio-socks",
"tokio-stream",
"tokio-tar",
"tokio-tungstenite",
@@ -2438,6 +2439,7 @@ dependencies = [
"dprint-swc-ext",
"embassy_container_init",
"helpers",
"itertools 0.10.5",
"models",
"reqwest",
"serde",

View File

@@ -1,6 +1,6 @@
[package]
authors = ["Aiden McClelland <me@drbonez.dev>"]
description = "The core of the Start9 Embassy Operating System"
description = "The core of StartOS"
documentation = "https://docs.rs/embassy-os"
edition = "2021"
keywords = [
@@ -13,8 +13,8 @@ keywords = [
]
name = "embassy-os"
readme = "README.md"
repository = "https://github.com/Start9Labs/embassy-os"
version = "0.3.4-rev.1"
repository = "https://github.com/Start9Labs/start-os"
version = "0.3.4-rev.3"
[lib]
name = "embassy"
@@ -152,6 +152,7 @@ tokio-stream = { version = "0.1.11", features = ["io-util", "sync", "net"] }
tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" }
tokio-tungstenite = { version = "0.17.1", features = ["native-tls"] }
tokio-rustls = "0.23.4"
tokio-socks = "0.5.1"
tokio-util = { version = "0.7.3", features = ["io"] }
torut = "0.2.1"
tracing = "0.1.35"

View File

@@ -1,27 +1,27 @@
# embassyOS Backend
# StartOS Backend
- Requirements:
- [Install Rust](https://rustup.rs)
- Recommended: [rust-analyzer](https://rust-analyzer.github.io/)
- [Docker](https://docs.docker.com/get-docker/)
- [Rust ARM64 Build Container](https://github.com/Start9Labs/rust-arm-builder)
- Scripts (run withing the `./backend` directory)
- Scripts (run within the `./backend` directory)
- `build-prod.sh` - compiles a release build of the artifacts for running on
ARM64
- A Linux computer or VM
## Structure
The embassyOS backend is broken up into 4 different binaries:
The StartOS backend is broken up into 4 different binaries:
- embassyd: This is the main workhorse of embassyOS - any new functionality you
- embassyd: This is the main workhorse of StartOS - any new functionality you
want will likely go here
- embassy-init: This is the component responsible for allowing you to set up
your device, and handles system initialization on startup
- embassy-cli: This is a CLI tool that will allow you to issue commands to
embassyd and control it similarly to the UI
- embassy-sdk: This is a CLI tool that aids in building and packaging services
you wish to deploy to the Embassy
you wish to deploy to StartOS
Finally there is a library `embassy` that supports all four of these tools.
@@ -30,7 +30,7 @@ See [here](/backend/Cargo.toml) for details.
## Building
You can build the entire operating system image using `make` from the root of
the embassyOS project. This will subsequently invoke the build scripts above to
the StartOS project. This will subsequently invoke the build scripts above to
actually create the requisite binaries and put them onto the final operating
system image.

View File

@@ -3,6 +3,11 @@
set -e
shopt -s expand_aliases
if [ -z "$OS_ARCH" ]; then
>&2 echo '$OS_ARCH is required'
exit 1
fi
if [ -z "$ARCH" ]; then
ARCH=$(uname -m)
fi
@@ -17,8 +22,8 @@ if tty -s; then
USE_TTY="-it"
fi
alias 'rust-gnu-builder'='docker run $USE_TTY --rm -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src -P start9/rust-arm-cross:aarch64'
alias 'rust-musl-builder'='docker run $USE_TTY --rm -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
alias 'rust-gnu-builder'='docker run $USE_TTY --rm -e "OS_ARCH=$OS_ARCH" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src -P start9/rust-arm-cross:aarch64'
alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "OS_ARCH=$OS_ARCH" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
cd ..
FLAGS=""

View File

@@ -1,5 +1,5 @@
use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use chrono::Utc;
use clap::ArgMatches;
@@ -8,6 +8,7 @@ use helpers::AtomicFile;
use patch_db::{DbHandle, LockType, PatchDbHandle};
use rpc_toolkit::command;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
use tracing::instrument;
use super::target::BackupTargetId;
@@ -23,8 +24,9 @@ use crate::disk::mount::guard::TmpMountGuard;
use crate::notifications::NotificationLevel;
use crate::s9pk::manifest::PackageId;
use crate::status::MainStatus;
use crate::util::display_none;
use crate::util::io::dir_copy;
use crate::util::serde::IoFormat;
use crate::util::{display_none, Invoke};
use crate::version::VersionT;
use crate::{Error, ErrorKind, ResultExt};
@@ -358,6 +360,19 @@ async fn perform_backup<Db: DbHandle>(
.await
.with_kind(ErrorKind::Filesystem)?;
let luks_folder_old = backup_guard.as_ref().join("luks.old");
if tokio::fs::metadata(&luks_folder_old).await.is_ok() {
tokio::fs::remove_dir_all(&luks_folder_old).await?;
}
let luks_folder_bak = backup_guard.as_ref().join("luks");
if tokio::fs::metadata(&luks_folder_bak).await.is_ok() {
tokio::fs::rename(&luks_folder_bak, &luks_folder_old).await?;
}
let luks_folder = Path::new("/media/embassy/config/luks");
if tokio::fs::metadata(&luks_folder).await.is_ok() {
dir_copy(&luks_folder, &luks_folder_bak).await?;
}
let timestamp = Some(Utc::now());
backup_guard.unencrypted_metadata.version = crate::version::Current::new().semver().into();

View File

@@ -1,12 +1,13 @@
use openssl::pkey::PKey;
use openssl::x509::X509;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::account::AccountInfo;
use crate::hostname::{generate_hostname, generate_id, Hostname};
use crate::net::keys::Key;
use crate::util::serde::Base64;
use crate::Error;
use openssl::pkey::PKey;
use openssl::x509::X509;
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub struct OsBackup {
pub account: AccountInfo,

View File

@@ -6,8 +6,8 @@ use std::time::Duration;
use clap::ArgMatches;
use color_eyre::eyre::eyre;
use futures::{future::BoxFuture, stream};
use futures::{FutureExt, StreamExt};
use futures::future::BoxFuture;
use futures::{stream, FutureExt, StreamExt};
use openssl::x509::X509;
use patch_db::{DbHandle, PatchDbHandle};
use rpc_toolkit::command;
@@ -443,7 +443,7 @@ async fn restore_package<'a>(
Ok((
progress.clone(),
async move {
download_install_s9pk(&ctx, &manifest, None, progress, file).await?;
download_install_s9pk(&ctx, &manifest, None, progress, file, None).await?;
guard.unmount().await?;

View File

@@ -7,10 +7,12 @@ use clap::ArgMatches;
use color_eyre::eyre::eyre;
use digest::generic_array::GenericArray;
use digest::OutputSizeUser;
use lazy_static::lazy_static;
use rpc_toolkit::command;
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use sqlx::{Executor, Postgres};
use tokio::sync::Mutex;
use tracing::instrument;
use self::cifs::CifsBackupTarget;
@@ -23,7 +25,7 @@ use crate::disk::mount::guard::TmpMountGuard;
use crate::disk::util::PartitionInfo;
use crate::s9pk::manifest::PackageId;
use crate::util::serde::{deserialize_from_str, display_serializable, serialize_display};
use crate::util::Version;
use crate::util::{display_none, Version};
use crate::Error;
pub mod cifs;
@@ -42,7 +44,7 @@ pub enum BackupTarget {
Cifs(CifsBackupTarget),
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum BackupTargetId {
Disk { logicalname: PathBuf },
Cifs { id: i32 },
@@ -129,7 +131,7 @@ impl FileSystem for BackupTargetFS {
}
}
#[command(subcommands(cifs::cifs, list, info))]
#[command(subcommands(cifs::cifs, list, info, mount, umount))]
pub fn target() -> Result<(), Error> {
Ok(())
}
@@ -247,3 +249,61 @@ pub async fn info(
Ok(res)
}
lazy_static! {
static ref USER_MOUNTS: Mutex<BTreeMap<BackupTargetId, BackupMountGuard<TmpMountGuard>>> =
Mutex::new(BTreeMap::new());
}
#[command]
#[instrument(skip_all)]
pub async fn mount(
#[context] ctx: RpcContext,
#[arg(rename = "target-id")] target_id: BackupTargetId,
#[arg] password: String,
) -> Result<String, Error> {
let mut mounts = USER_MOUNTS.lock().await;
if let Some(existing) = mounts.get(&target_id) {
return Ok(existing.as_ref().display().to_string());
}
let guard = BackupMountGuard::mount(
TmpMountGuard::mount(
&target_id
.clone()
.load(&mut ctx.secret_store.acquire().await?)
.await?,
ReadWrite,
)
.await?,
&password,
)
.await?;
let res = guard.as_ref().display().to_string();
mounts.insert(target_id, guard);
Ok(res)
}
#[command(display(display_none))]
#[instrument(skip_all)]
pub async fn umount(
#[context] ctx: RpcContext,
#[arg(rename = "target-id")] target_id: Option<BackupTargetId>,
) -> Result<(), Error> {
let mut mounts = USER_MOUNTS.lock().await;
if let Some(target_id) = target_id {
if let Some(existing) = mounts.remove(&target_id) {
existing.unmount().await?;
}
} else {
for (_, existing) in std::mem::take(&mut *mounts) {
existing.unmount().await?;
}
}
Ok(())
}

View File

@@ -1,3 +1,4 @@
use std::net::{Ipv6Addr, SocketAddr};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
@@ -13,16 +14,57 @@ use embassy::shutdown::Shutdown;
use embassy::sound::CHIME;
use embassy::util::logger::EmbassyLogger;
use embassy::util::Invoke;
use embassy::{Error, ErrorKind, ResultExt, IS_RASPBERRY_PI};
use embassy::{Error, ErrorKind, ResultExt, OS_ARCH};
use tokio::process::Command;
use tracing::instrument;
#[instrument(skip_all)]
async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<(), Error> {
if tokio::fs::metadata("/cdrom").await.is_ok() {
Command::new("ln")
.arg("-sf")
.arg("/usr/lib/embassy/scripts/fake-apt")
.arg("/usr/local/bin/apt")
.invoke(crate::ErrorKind::OpenSsh)
.await?;
Command::new("ln")
.arg("-sf")
.arg("/usr/lib/embassy/scripts/fake-apt")
.arg("/usr/local/bin/apt-get")
.invoke(crate::ErrorKind::OpenSsh)
.await?;
Command::new("ln")
.arg("-sf")
.arg("/usr/lib/embassy/scripts/fake-apt")
.arg("/usr/local/bin/aptitude")
.invoke(crate::ErrorKind::OpenSsh)
.await?;
Command::new("make-ssl-cert")
.arg("generate-default-snakeoil")
.arg("--force-overwrite")
.invoke(crate::ErrorKind::OpenSsl)
.await?;
if tokio::fs::metadata("/run/live/medium").await.is_ok() {
Command::new("sed")
.arg("-i")
.arg("s/PasswordAuthentication no/PasswordAuthentication yes/g")
.arg("/etc/ssh/sshd_config")
.invoke(crate::ErrorKind::Filesystem)
.await?;
Command::new("systemctl")
.arg("reload")
.arg("ssh")
.invoke(crate::ErrorKind::OpenSsh)
.await?;
let ctx = InstallContext::init(cfg_path).await?;
let server = WebServer::install(([0, 0, 0, 0], 80).into(), ctx.clone()).await?;
let server = WebServer::install(
SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 80),
ctx.clone(),
)
.await?;
tokio::time::sleep(Duration::from_secs(1)).await; // let the record state that I hate this
CHIME.play().await?;
@@ -44,7 +86,11 @@ async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<(), Error> {
{
let ctx = SetupContext::init(cfg_path).await?;
let server = WebServer::setup(([0, 0, 0, 0], 80).into(), ctx.clone()).await?;
let server = WebServer::setup(
SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 80),
ctx.clone(),
)
.await?;
tokio::time::sleep(Duration::from_secs(1)).await; // let the record state that I hate this
CHIME.play().await?;
@@ -119,7 +165,7 @@ async fn run_script_if_exists<P: AsRef<Path>>(path: P) {
#[instrument(skip_all)]
async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error> {
if *IS_RASPBERRY_PI && tokio::fs::metadata(STANDBY_MODE_PATH).await.is_ok() {
if OS_ARCH == "raspberrypi" && tokio::fs::metadata(STANDBY_MODE_PATH).await.is_ok() {
tokio::fs::remove_file(STANDBY_MODE_PATH).await?;
Command::new("sync").invoke(ErrorKind::Filesystem).await?;
embassy::sound::SHUTDOWN.play().await?;
@@ -155,7 +201,11 @@ async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error
)
.await?;
let server = WebServer::diagnostic(([0, 0, 0, 0], 80).into(), ctx.clone()).await?;
let server = WebServer::diagnostic(
SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 80),
ctx.clone(),
)
.await?;
let shutdown = ctx.shutdown.subscribe().recv().await.unwrap();

View File

@@ -1,3 +1,4 @@
use std::net::{Ipv6Addr, SocketAddr};
use std::path::{Path, PathBuf};
use std::sync::Arc;
@@ -25,8 +26,12 @@ async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error
),
)
.await?;
embassy::hostname::sync_hostname(&*rpc_ctx.account.read().await).await?;
let server = WebServer::main(([0, 0, 0, 0], 80).into(), rpc_ctx.clone()).await?;
embassy::hostname::sync_hostname(&rpc_ctx.account.read().await.hostname).await?;
let server = WebServer::main(
SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 80),
rpc_ctx.clone(),
)
.await?;
let mut shutdown_recv = rpc_ctx.shutdown.subscribe();
@@ -141,8 +146,11 @@ fn main() {
)
.await?;
let server =
WebServer::diagnostic(([0, 0, 0, 0], 80).into(), ctx.clone()).await?;
let server = WebServer::diagnostic(
SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 80),
ctx.clone(),
)
.await?;
let mut shutdown = ctx.shutdown.subscribe();

View File

@@ -503,19 +503,27 @@ pub fn configure_rec<'a, Db: DbHandle>(
.config_actions
.get(db, id)
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(id))?;
let dependencies = receipts
.dependencies
.get(db, id)
.await?
.ok_or_else(not_found)?;
let volumes = receipts.volumes.get(db, id).await?.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(id))?;
let volumes = receipts
.volumes
.get(db, id)
.await?
.ok_or_else(|| not_found!(id))?;
let is_needs_config = !receipts
.configured
.get(db, id)
.await?
.ok_or_else(not_found)?;
let version = receipts.version.get(db, id).await?.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(id))?;
let version = receipts
.version
.get(db, id)
.await?
.ok_or_else(|| not_found!(id))?;
// get current config and current spec
let ConfigRes {
@@ -530,7 +538,11 @@ pub fn configure_rec<'a, Db: DbHandle>(
spec.gen(&mut rand::rngs::StdRng::from_entropy(), timeout)?
};
let manifest = receipts.manifest.get(db, id).await?.ok_or_else(not_found)?;
let manifest = receipts
.manifest
.get(db, id)
.await?
.ok_or_else(|| not_found!(id))?;
spec.validate(&manifest)?;
spec.matches(&config)?; // check that new config matches spec
@@ -549,7 +561,7 @@ pub fn configure_rec<'a, Db: DbHandle>(
.system_pointers
.get(db, &id)
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(id))?;
sys.truncate(0);
let mut current_dependencies: CurrentDependencies = CurrentDependencies(
dependencies
@@ -655,7 +667,7 @@ pub fn configure_rec<'a, Db: DbHandle>(
.dependency_errors
.get(db, &id)
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(id))?;
tracing::warn!("Dependency Errors: {:?}", errs);
let errs = DependencyErrors::init(
ctx,
@@ -675,7 +687,7 @@ pub fn configure_rec<'a, Db: DbHandle>(
.current_dependents
.get(db, id)
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(id))?;
let prev = if is_needs_config { None } else { old_config }
.map(Value::Object)
.unwrap_or_default();
@@ -693,7 +705,7 @@ pub fn configure_rec<'a, Db: DbHandle>(
.manifest
.get(db, &dependent)
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(id))?;
if let Err(error) = cfg
.check(
ctx,
@@ -771,10 +783,16 @@ pub fn configure_rec<'a, Db: DbHandle>(
}
.boxed()
}
#[instrument(skip_all)]
pub fn not_found() -> Error {
Error::new(eyre!("Could not find"), crate::ErrorKind::Incoherent)
macro_rules! not_found {
($x:expr) => {
crate::Error::new(
color_eyre::eyre::eyre!("Could not find {} at {}:{}", $x, module_path!(), line!()),
crate::ErrorKind::Incoherent,
)
};
}
pub(crate) use not_found;
/// We want to have a double check that the paths are what we expect them to be.
/// Found that earlier the paths where not what we expected them to be.

View File

@@ -17,12 +17,11 @@ use rpc_toolkit::Context;
use serde::Deserialize;
use tracing::instrument;
use super::setup::CURRENT_SECRET;
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
use crate::util::config::{load_config_from_paths, local_config_path};
use crate::ResultExt;
use super::setup::CURRENT_SECRET;
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct CliContextConfig {

View File

@@ -19,11 +19,12 @@ use sqlx::PgPool;
use tokio::sync::{broadcast, oneshot, Mutex, RwLock};
use tracing::instrument;
use super::setup::CURRENT_SECRET;
use crate::account::AccountInfo;
use crate::core::rpc_continuations::{RequestGuid, RestHandler, RpcContinuation};
use crate::db::model::{Database, InstalledPackageDataEntry, PackageDataEntry};
use crate::db::model::{CurrentDependents, Database, InstalledPackageDataEntry, PackageDataEntry};
use crate::disk::OsPartitionInfo;
use crate::init::{init_postgres, pgloader};
use crate::init::init_postgres;
use crate::install::cleanup::{cleanup_failed, uninstall, CleanupFailedReceipts};
use crate::manager::ManagerMap;
use crate::middleware::auth::HashSessionToken;
@@ -36,8 +37,6 @@ use crate::status::{MainStatus, Status};
use crate::util::config::load_config_from_paths;
use crate::{Error, ErrorKind, ResultExt};
use super::setup::CURRENT_SECRET;
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct RpcContextConfig {
@@ -96,15 +95,6 @@ impl RpcContextConfig {
.run(&secret_store)
.await
.with_kind(crate::ErrorKind::Database)?;
let old_db_path = self.datadir().join("main/secrets.db");
if tokio::fs::metadata(&old_db_path).await.is_ok() {
pgloader(
&old_db_path,
self.migration_batch_rows.unwrap_or(25000),
self.migration_prefetch_rows.unwrap_or(100_000),
)
.await?;
}
Ok(secret_store)
}
}
@@ -197,6 +187,7 @@ impl RpcContext {
NetController::init(
base.tor_control
.unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))),
tor_proxy,
base.dns_bind
.as_ref()
.map(|v| v.as_slice())
@@ -345,6 +336,31 @@ impl RpcContext {
tracing::debug!("{:?}", e);
}
}
let mut current_dependents = BTreeMap::new();
for (package_id, package) in receipts.packages.get(&mut db).await?.0 {
for (k, v) in package
.into_installed()
.into_iter()
.flat_map(|i| i.current_dependencies.0)
{
let mut entry: BTreeMap<_, _> = current_dependents.remove(&k).unwrap_or_default();
entry.insert(package_id.clone(), v);
current_dependents.insert(k, entry);
}
}
for (package_id, current_dependents) in current_dependents {
if let Some(deps) = crate::db::DatabaseModel::new()
.package_data()
.idx_model(&package_id)
.and_then(|pde| pde.installed())
.map::<_, CurrentDependents>(|i| i.current_dependents())
.check(&mut db)
.await?
{
deps.put(&mut db, &CurrentDependents(current_dependents))
.await?;
}
}
Ok(())
}

View File

@@ -17,7 +17,7 @@ use tracing::instrument;
use crate::account::AccountInfo;
use crate::db::model::Database;
use crate::disk::OsPartitionInfo;
use crate::init::{init_postgres, pgloader};
use crate::init::init_postgres;
use crate::setup::SetupStatus;
use crate::util::config::load_config_from_paths;
use crate::{Error, ResultExt};
@@ -132,15 +132,6 @@ impl SetupContext {
.run(&secret_store)
.await
.with_kind(crate::ErrorKind::Database)?;
let old_db_path = self.datadir.join("main/secrets.db");
if tokio::fs::metadata(&old_db_path).await.is_ok() {
pgloader(
&old_db_path,
self.migration_batch_rows,
self.migration_prefetch_rows,
)
.await?;
}
Ok(secret_store)
}
}

View File

@@ -80,6 +80,7 @@ impl Database {
.map(|x| format!("{x:X}"))
.join(":"),
system_start_time: Utc::now().to_rfc3339(),
zram: false,
},
package_data: AllPackageData::default(),
ui: serde_json::from_str(include_str!("../../../frontend/patchdb-ui-seed.json"))
@@ -117,6 +118,8 @@ pub struct ServerInfo {
pub pubkey: String,
pub ca_fingerprint: String,
pub system_start_time: String,
#[serde(default)]
pub zram: bool,
}
#[derive(Debug, Deserialize, Serialize, HasModel)]

View File

@@ -237,13 +237,16 @@ impl DependencyError {
}
}
DependencyError::ConfigUnsatisfied { .. } => {
let dependent_manifest =
receipts.manifest.get(db, id).await?.ok_or_else(not_found)?;
let dependent_manifest = receipts
.manifest
.get(db, id)
.await?
.ok_or_else(|| not_found!(id))?;
let dependency_manifest = receipts
.manifest
.get(db, dependency)
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(dependency))?;
let dependency_config = if let Some(cfg) = dependency_config.take() {
cfg
@@ -294,7 +297,7 @@ impl DependencyError {
.status
.get(db, dependency)
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(dependency))?;
if status.main.running() {
DependencyError::HealthChecksFailed {
failures: BTreeMap::new(),
@@ -310,7 +313,7 @@ impl DependencyError {
.status
.get(db, dependency)
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(dependency))?;
match status.main {
MainStatus::BackingUp {
started: Some(_),
@@ -324,7 +327,7 @@ impl DependencyError {
.current_dependencies
.get(db, id)
.await?
.ok_or_else(not_found)?
.ok_or_else(|| not_found!(id))?
.get(dependency)
.map(|x| x.health_checks.contains(&check))
.unwrap_or(false)
@@ -934,7 +937,7 @@ pub fn break_transitive<'a, Db: DbHandle>(
.dependency_errors
.get(&mut tx, id)
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(id))?;
let old = dependency_errors.0.remove(dependency);
let newly_broken = if let Some(e) = &old {
@@ -997,7 +1000,7 @@ pub async fn heal_all_dependents_transitive<'a, Db: DbHandle>(
.current_dependents
.get(db, id)
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(id))?;
for dependent in dependents.0.keys().filter(|dependent| id != *dependent) {
heal_transitive(ctx, db, dependent, id, locks).await?;
}
@@ -1013,7 +1016,11 @@ pub fn heal_transitive<'a, Db: DbHandle>(
receipts: &'a DependencyReceipt,
) -> BoxFuture<'a, Result<(), Error>> {
async move {
let mut status = receipts.status.get(db, id).await?.ok_or_else(not_found)?;
let mut status = receipts
.status
.get(db, id)
.await?
.ok_or_else(|| not_found!(id))?;
let old = status.dependency_errors.0.remove(dependency);
@@ -1022,7 +1029,7 @@ pub fn heal_transitive<'a, Db: DbHandle>(
.dependency
.get(db, (id, dependency))
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(format!("{id}'s dependency: {dependency}")))?;
if let Some(new) = old
.try_heal(ctx, db, id, dependency, None, &info, &receipts.try_heal)
.await?

View File

@@ -0,0 +1,31 @@
use std::path::Path;
use tokio::process::Command;
use tracing::instrument;
use crate::disk::fsck::RequiresReboot;
use crate::util::Invoke;
use crate::Error;
#[instrument(skip_all)]
pub async fn btrfs_check_readonly(logicalname: impl AsRef<Path>) -> Result<RequiresReboot, Error> {
Command::new("btrfs")
.arg("check")
.arg("--readonly")
.arg(logicalname.as_ref())
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Ok(RequiresReboot(false))
}
pub async fn btrfs_check_repair(logicalname: impl AsRef<Path>) -> Result<RequiresReboot, Error> {
Command::new("btrfs")
.arg("check")
.arg("--repair")
.arg(logicalname.as_ref())
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Ok(RequiresReboot(false))
}

View File

@@ -7,34 +7,9 @@ use futures::FutureExt;
use tokio::process::Command;
use tracing::instrument;
use crate::disk::fsck::RequiresReboot;
use crate::Error;
#[derive(Debug, Clone, Copy)]
#[must_use]
pub struct RequiresReboot(pub bool);
impl std::ops::BitOrAssign for RequiresReboot {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0
}
}
#[derive(Debug, Clone, Copy)]
pub enum RepairStrategy {
Preen,
Aggressive,
}
impl RepairStrategy {
pub async fn e2fsck(
&self,
logicalname: impl AsRef<Path> + std::fmt::Debug,
) -> Result<RequiresReboot, Error> {
match self {
RepairStrategy::Preen => e2fsck_preen(logicalname).await,
RepairStrategy::Aggressive => e2fsck_aggressive(logicalname).await,
}
}
}
#[instrument(skip_all)]
pub async fn e2fsck_preen(
logicalname: impl AsRef<Path> + std::fmt::Debug,

View File

@@ -0,0 +1,70 @@
use std::path::Path;
use color_eyre::eyre::eyre;
use tokio::process::Command;
use crate::disk::fsck::btrfs::{btrfs_check_readonly, btrfs_check_repair};
use crate::disk::fsck::ext4::{e2fsck_aggressive, e2fsck_preen};
use crate::util::Invoke;
use crate::Error;
pub mod btrfs;
pub mod ext4;
#[derive(Debug, Clone, Copy)]
#[must_use]
pub struct RequiresReboot(pub bool);
impl std::ops::BitOrAssign for RequiresReboot {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0
}
}
#[derive(Debug, Clone, Copy)]
pub enum RepairStrategy {
Preen,
Aggressive,
}
impl RepairStrategy {
pub async fn fsck(
&self,
logicalname: impl AsRef<Path> + std::fmt::Debug,
) -> Result<RequiresReboot, Error> {
match &*String::from_utf8(
Command::new("grub-probe")
.arg("-d")
.arg(logicalname.as_ref())
.invoke(crate::ErrorKind::DiskManagement)
.await?,
)?
.trim()
{
"ext2" => self.e2fsck(logicalname).await,
"btrfs" => self.btrfs_check(logicalname).await,
fs => {
return Err(Error::new(
eyre!("Unknown filesystem {fs}"),
crate::ErrorKind::DiskManagement,
))
}
}
}
pub async fn e2fsck(
&self,
logicalname: impl AsRef<Path> + std::fmt::Debug,
) -> Result<RequiresReboot, Error> {
match self {
RepairStrategy::Preen => e2fsck_preen(logicalname).await,
RepairStrategy::Aggressive => e2fsck_aggressive(logicalname).await,
}
}
pub async fn btrfs_check(
&self,
logicalname: impl AsRef<Path> + std::fmt::Debug,
) -> Result<RequiresReboot, Error> {
match self {
RepairStrategy::Preen => btrfs_check_readonly(logicalname).await,
RepairStrategy::Aggressive => btrfs_check_repair(logicalname).await,
}
}
}

View File

@@ -106,12 +106,13 @@ pub async fn create_fs<P: AsRef<Path>>(
.arg(guid)
.invoke(crate::ErrorKind::DiskManagement)
.await?;
let crypt_path = Path::new("/dev").join(guid).join(name);
Command::new("cryptsetup")
.arg("-q")
.arg("luksFormat")
.arg(format!("--key-file={}", PASSWORD_PATH))
.arg(format!("--keyfile-size={}", password.len()))
.arg(Path::new("/dev").join(guid).join(name))
.arg(&crypt_path)
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Command::new("cryptsetup")
@@ -119,11 +120,11 @@ pub async fn create_fs<P: AsRef<Path>>(
.arg("luksOpen")
.arg(format!("--key-file={}", PASSWORD_PATH))
.arg(format!("--keyfile-size={}", password.len()))
.arg(Path::new("/dev").join(guid).join(name))
.arg(&crypt_path)
.arg(format!("{}_{}", guid, name))
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Command::new("mkfs.ext4")
Command::new("mkfs.btrfs")
.arg(Path::new("/dev/mapper").join(format!("{}_{}", guid, name)))
.invoke(crate::ErrorKind::DiskManagement)
.await?;
@@ -213,7 +214,7 @@ pub async fn import<P: AsRef<Path>>(
.is_none()
{
return Err(Error::new(
eyre!("Embassy disk not found."),
eyre!("StartOS disk not found."),
crate::ErrorKind::DiskNotAvailable,
));
}
@@ -223,7 +224,7 @@ pub async fn import<P: AsRef<Path>>(
.any(|id| id == guid)
{
return Err(Error::new(
eyre!("An Embassy disk was found, but it is not the correct disk for this device."),
eyre!("A StartOS disk was found, but it is not the correct disk for this device."),
crate::ErrorKind::IncorrectDisk,
));
}
@@ -265,17 +266,36 @@ pub async fn mount_fs<P: AsRef<Path>>(
tokio::fs::write(PASSWORD_PATH, password)
.await
.with_ctx(|_| (crate::ErrorKind::Filesystem, PASSWORD_PATH))?;
let crypt_path = Path::new("/dev").join(guid).join(name);
let full_name = format!("{}_{}", guid, name);
Command::new("cryptsetup")
.arg("-q")
.arg("luksOpen")
.arg(format!("--key-file={}", PASSWORD_PATH))
.arg(format!("--keyfile-size={}", password.len()))
.arg(Path::new("/dev").join(guid).join(name))
.arg(format!("{}_{}", guid, name))
.arg(&crypt_path)
.arg(&full_name)
.invoke(crate::ErrorKind::DiskManagement)
.await?;
let mapper_path = Path::new("/dev/mapper").join(format!("{}_{}", guid, name));
let reboot = repair.e2fsck(&mapper_path).await?;
let mapper_path = Path::new("/dev/mapper").join(&full_name);
let reboot = repair.fsck(&mapper_path).await?;
// Backup LUKS header if e2fsck succeeded
let luks_folder = Path::new("/media/embassy/config/luks");
tokio::fs::create_dir_all(luks_folder).await?;
let tmp_luks_bak = luks_folder.join(format!(".{full_name}.luks.bak.tmp"));
if tokio::fs::metadata(&tmp_luks_bak).await.is_ok() {
tokio::fs::remove_file(&tmp_luks_bak).await?;
}
let luks_bak = luks_folder.join(format!("{full_name}.luks.bak"));
Command::new("cryptsetup")
.arg("-q")
.arg("luksHeaderBackup")
.arg("--header-backup-file")
.arg(&tmp_luks_bak)
.arg(&crypt_path)
.invoke(crate::ErrorKind::DiskManagement)
.await?;
tokio::fs::rename(&tmp_luks_bak, &luks_bak).await?;
mount(&mapper_path, datadir.as_ref().join(name), ReadWrite).await?;
tokio::fs::remove_file(PASSWORD_PATH)

View File

@@ -22,6 +22,7 @@ pub const REPAIR_DISK_PATH: &str = "/media/embassy/config/repair-disk";
#[serde(rename_all = "kebab-case")]
pub struct OsPartitionInfo {
pub efi: Option<PathBuf>,
pub bios: Option<PathBuf>,
pub boot: PathBuf,
pub root: PathBuf,
}
@@ -31,6 +32,11 @@ impl OsPartitionInfo {
.as_ref()
.map(|p| p == logicalname.as_ref())
.unwrap_or(false)
|| self
.bios
.as_ref()
.map(|p| p == logicalname.as_ref())
.unwrap_or(false)
|| &*self.boot == logicalname.as_ref()
|| &*self.root == logicalname.as_ref()
}

View File

@@ -1,4 +1,3 @@
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use async_trait::async_trait;

View File

@@ -1,4 +1,4 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
use color_eyre::eyre::{self, eyre};
@@ -251,7 +251,7 @@ pub async fn recovery_info(
#[instrument(skip_all)]
pub async fn list(os: &OsPartitionInfo) -> Result<Vec<DiskInfo>, Error> {
struct DiskIndex {
parts: IndexSet<PathBuf>,
parts: BTreeSet<PathBuf>,
internal: bool,
}
let disk_guids = pvscan().await?;
@@ -301,7 +301,7 @@ pub async fn list(os: &OsPartitionInfo) -> Result<Vec<DiskInfo>, Error> {
disks.insert(
disk.clone(),
DiskIndex {
parts: IndexSet::new(),
parts: BTreeSet::new(),
internal: false,
},
);

View File

@@ -56,7 +56,8 @@ pub async fn get_current_hostname() -> Result<Hostname, Error> {
#[instrument(skip_all)]
pub async fn set_hostname(hostname: &Hostname) -> Result<(), Error> {
let hostname: &String = &hostname.0;
let _out = Command::new("hostnamectl")
Command::new("hostnamectl")
.arg("--static")
.arg("set-hostname")
.arg(hostname)
.invoke(ErrorKind::ParseSysInfo)
@@ -65,8 +66,8 @@ pub async fn set_hostname(hostname: &Hostname) -> Result<(), Error> {
}
#[instrument(skip_all)]
pub async fn sync_hostname(account: &AccountInfo) -> Result<(), Error> {
set_hostname(&account.hostname).await?;
pub async fn sync_hostname(hostname: &Hostname) -> Result<(), Error> {
set_hostname(hostname).await?;
Command::new("systemctl")
.arg("restart")
.arg("avahi-daemon")

View File

@@ -1,8 +1,7 @@
use std::collections::{BTreeMap, HashMap};
use std::collections::HashMap;
use std::fs::Permissions;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::process::Stdio;
use std::time::Duration;
use color_eyre::eyre::eyre;
@@ -15,7 +14,8 @@ use tokio::process::Command;
use crate::account::AccountInfo;
use crate::context::rpc::RpcContextConfig;
use crate::db::model::{IpInfo, ServerStatus};
use crate::db::model::{ServerInfo, ServerStatus};
use crate::disk::mount::util::unmount;
use crate::install::PKG_ARCHIVE_DIR;
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
use crate::sound::BEP;
@@ -40,17 +40,18 @@ pub async fn check_time_is_synchronized() -> Result<bool, Error> {
}
pub struct InitReceipts {
pub server_info: LockReceipt<ServerInfo, ()>,
pub server_version: LockReceipt<crate::util::Version, ()>,
pub version_range: LockReceipt<emver::VersionRange, ()>,
pub last_wifi_region: LockReceipt<Option<isocountry::CountryCode>, ()>,
pub status_info: LockReceipt<ServerStatus, ()>,
pub ip_info: LockReceipt<BTreeMap<String, IpInfo>, ()>,
pub system_start_time: LockReceipt<String, ()>,
}
impl InitReceipts {
pub async fn new(db: &mut impl DbHandle) -> Result<Self, Error> {
let mut locks = Vec::new();
let server_info = crate::db::DatabaseModel::new()
.server_info()
.make_locker(LockType::Write)
.add_to_keys(&mut locks);
let server_version = crate::db::DatabaseModel::new()
.server_info()
.version()
@@ -61,112 +62,29 @@ impl InitReceipts {
.eos_version_compat()
.make_locker(LockType::Write)
.add_to_keys(&mut locks);
let last_wifi_region = crate::db::DatabaseModel::new()
.server_info()
.last_wifi_region()
.make_locker(LockType::Write)
.add_to_keys(&mut locks);
let ip_info = crate::db::DatabaseModel::new()
.server_info()
.ip_info()
.make_locker(LockType::Write)
.add_to_keys(&mut locks);
let status_info = crate::db::DatabaseModel::new()
.server_info()
.status_info()
.into_model()
.make_locker(LockType::Write)
.add_to_keys(&mut locks);
let system_start_time = crate::db::DatabaseModel::new()
.server_info()
.system_start_time()
.make_locker(LockType::Write)
.add_to_keys(&mut locks);
let skeleton_key = db.lock_all(locks).await?;
Ok(Self {
server_info: server_info.verify(&skeleton_key)?,
server_version: server_version.verify(&skeleton_key)?,
version_range: version_range.verify(&skeleton_key)?,
ip_info: ip_info.verify(&skeleton_key)?,
status_info: status_info.verify(&skeleton_key)?,
last_wifi_region: last_wifi_region.verify(&skeleton_key)?,
system_start_time: system_start_time.verify(&skeleton_key)?,
})
}
}
pub async fn pgloader(
old_db_path: impl AsRef<Path>,
batch_rows: usize,
prefetch_rows: usize,
) -> Result<(), Error> {
tokio::fs::write(
"/etc/embassy/migrate.load",
format!(
include_str!("migrate.load"),
sqlite_path = old_db_path.as_ref().display(),
batch_rows = batch_rows,
prefetch_rows = prefetch_rows
),
)
.await?;
match tokio::fs::remove_dir_all("/tmp/pgloader").await {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
a => a,
}?;
tracing::info!("Running pgloader");
let out = Command::new("pgloader")
.arg("-v")
.arg("/etc/embassy/migrate.load")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.await?;
let stdout = String::from_utf8(out.stdout)?;
for line in stdout.lines() {
tracing::debug!("pgloader: {}", line);
}
let stderr = String::from_utf8(out.stderr)?;
for line in stderr.lines() {
tracing::debug!("pgloader err: {}", line);
}
tracing::debug!("pgloader exited with code {:?}", out.status);
if let Some(err) = stdout.lines().chain(stderr.lines()).find_map(|l| {
if l.split_ascii_whitespace()
.any(|word| word == "ERROR" || word == "FATAL")
{
Some(l)
} else {
None
}
}) {
return Err(Error::new(
eyre!("pgloader error: {}", err),
crate::ErrorKind::Database,
));
}
tokio::fs::rename(
old_db_path.as_ref(),
old_db_path.as_ref().with_extension("bak"),
)
.await?;
Ok(())
}
// must be idempotent
pub async fn init_postgres(datadir: impl AsRef<Path>) -> Result<(), Error> {
let db_dir = datadir.as_ref().join("main/postgresql");
let is_mountpoint = || async {
Ok::<_, Error>(
tokio::process::Command::new("mountpoint")
.arg("/var/lib/postgresql")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.await?
.success(),
)
};
if tokio::process::Command::new("mountpoint")
.arg("/var/lib/postgresql")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.await?
.success()
{
unmount("/var/lib/postgresql").await?;
}
let exists = tokio::fs::metadata(&db_dir).await.is_ok();
if !exists {
Command::new("cp")
@@ -176,18 +94,73 @@ pub async fn init_postgres(datadir: impl AsRef<Path>) -> Result<(), Error> {
.invoke(crate::ErrorKind::Filesystem)
.await?;
}
if !is_mountpoint().await? {
crate::disk::mount::util::bind(&db_dir, "/var/lib/postgresql", false).await?;
}
Command::new("chown")
.arg("-R")
.arg("postgres")
.arg("/var/lib/postgresql")
.arg("postgres:postgres")
.arg(&db_dir)
.invoke(crate::ErrorKind::Database)
.await?;
let mut pg_paths = tokio::fs::read_dir("/usr/lib/postgresql").await?;
let mut pg_version = None;
while let Some(pg_path) = pg_paths.next_entry().await? {
let pg_path_version = pg_path
.file_name()
.to_str()
.map(|v| v.parse())
.transpose()?
.unwrap_or(0);
if pg_path_version > pg_version.unwrap_or(0) {
pg_version = Some(pg_path_version)
}
}
let pg_version = pg_version.ok_or_else(|| {
Error::new(
eyre!("could not determine postgresql version"),
crate::ErrorKind::Database,
)
})?;
crate::disk::mount::util::bind(&db_dir, "/var/lib/postgresql", false).await?;
let pg_version_string = pg_version.to_string();
let pg_version_path = db_dir.join(&pg_version_string);
if tokio::fs::metadata(&pg_version_path).await.is_err() {
let conf_dir = Path::new("/etc/postgresql").join(pg_version.to_string());
let conf_dir_tmp = {
let mut tmp = conf_dir.clone();
tmp.set_extension("tmp");
tmp
};
if tokio::fs::metadata(&conf_dir).await.is_ok() {
tokio::fs::rename(&conf_dir, &conf_dir_tmp).await?;
}
let mut old_version = pg_version;
while old_version > 13
/* oldest pg version included in startos */
{
old_version -= 1;
let old_datadir = db_dir.join(old_version.to_string());
if tokio::fs::metadata(&old_datadir).await.is_ok() {
Command::new("pg_upgradecluster")
.arg(old_version.to_string())
.arg("main")
.invoke(crate::ErrorKind::Database)
.await?;
break;
}
}
if tokio::fs::metadata(&conf_dir).await.is_ok() {
if tokio::fs::metadata(&conf_dir).await.is_ok() {
tokio::fs::remove_dir_all(&conf_dir).await?;
}
tokio::fs::rename(&conf_dir_tmp, &conf_dir).await?;
}
}
Command::new("systemctl")
.arg("start")
.arg("postgresql")
.arg(format!("postgresql@{pg_version}-main.service"))
.invoke(crate::ErrorKind::Database)
.await?;
if !exists {
@@ -208,6 +181,7 @@ pub async fn init_postgres(datadir: impl AsRef<Path>) -> Result<(), Error> {
.invoke(crate::ErrorKind::Database)
.await?;
}
Ok(())
}
@@ -250,15 +224,15 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
let db = cfg.db(&account).await?;
tracing::info!("Opened PatchDB");
let mut handle = db.handle();
crate::db::DatabaseModel::new()
let mut server_info = crate::db::DatabaseModel::new()
.server_info()
.lock(&mut handle, LockType::Write)
.get_mut(&mut handle)
.await?;
let receipts = InitReceipts::new(&mut handle).await?;
// write to ca cert store
tokio::fs::write(
"/usr/local/share/ca-certificates/embassy-root-ca.crt",
"/usr/local/share/ca-certificates/startos-root-ca.crt",
account.root_ca_cert.to_pem()?,
)
.await?;
@@ -270,17 +244,15 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
crate::net::wifi::synchronize_wpa_supplicant_conf(
&cfg.datadir().join("main"),
wifi_interface,
&receipts.last_wifi_region.get(&mut handle).await?,
&server_info.last_wifi_region,
)
.await?;
tracing::info!("Synchronized WiFi");
}
let should_rebuild = tokio::fs::metadata(SYSTEM_REBUILD_PATH).await.is_ok()
|| &*receipts.server_version.get(&mut handle).await? < &emver::Version::new(0, 3, 2, 0)
|| (*ARCH == "x86_64"
&& &*receipts.server_version.get(&mut handle).await?
< &emver::Version::new(0, 3, 4, 0));
|| &*server_info.version < &emver::Version::new(0, 3, 2, 0)
|| (*ARCH == "x86_64" && &*server_info.version < &emver::Version::new(0, 3, 4, 0));
let song = if should_rebuild {
Some(NonDetachingJoinHandle::from(tokio::spawn(async {
@@ -398,32 +370,19 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
tracing::info!("Syncronized system clock");
}
Command::new("systemctl")
.arg("start")
.arg("tor")
.invoke(crate::ErrorKind::Tor)
.await?;
if server_info.zram {
crate::system::enable_zram().await?
}
server_info.ip_info = crate::net::dhcp::init_ips().await?;
server_info.status_info = ServerStatus {
updated: false,
update_progress: None,
backup_progress: None,
};
receipts
.ip_info
.set(&mut handle, crate::net::dhcp::init_ips().await?)
.await?;
receipts
.status_info
.set(
&mut handle,
ServerStatus {
updated: false,
update_progress: None,
backup_progress: None,
},
)
.await?;
server_info.system_start_time = time().await?;
receipts
.system_start_time
.set(&mut handle, time().await?)
.await?;
server_info.save(&mut handle).await?;
crate::version::init(&mut handle, &secret_store, &receipts).await?;

View File

@@ -82,7 +82,7 @@ pub async fn update_dependency_errors_of_dependents<'a, Db: DbHandle>(
.dependency_errors
.get(db, dep)
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(dep))?;
errs.0.insert(id.clone(), e);
receipts.dependency_errors.set(db, errs, dep).await?
} else {
@@ -90,7 +90,7 @@ pub async fn update_dependency_errors_of_dependents<'a, Db: DbHandle>(
.dependency_errors
.get(db, dep)
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(dep))?;
errs.0.remove(id);
receipts.dependency_errors.set(db, errs, dep).await?
}
@@ -215,7 +215,7 @@ pub async fn cleanup_failed<Db: DbHandle>(
.package_data_entry
.get(db, id)
.await?
.ok_or_else(not_found)?;
.ok_or_else(|| not_found!(id))?;
if let Some(manifest) = match &pde {
PackageDataEntry::Installing { manifest, .. }
| PackageDataEntry::Restoring { manifest, .. } => Some(manifest),

View File

@@ -21,6 +21,7 @@ use rpc_toolkit::yajrc::RpcError;
use tokio::fs::{File, OpenOptions};
use tokio::io::{AsyncRead, AsyncSeek, AsyncSeekExt};
use tokio::process::Command;
use tokio::sync::oneshot;
use tokio_stream::wrappers::ReadDirStream;
use tracing::instrument;
@@ -45,7 +46,7 @@ use crate::s9pk::reader::S9pkReader;
use crate::status::{MainStatus, Status};
use crate::util::io::{copy_and_shutdown, response_to_reader};
use crate::util::serde::{display_serializable, Port};
use crate::util::{assure_send, display_none, AsyncFileExt, Version};
use crate::util::{display_none, AsyncFileExt, Version};
use crate::version::{Current, VersionT};
use crate::volume::{asset_dir, script_dir};
use crate::{Error, ErrorKind, ResultExt};
@@ -297,6 +298,7 @@ pub async fn install(
Some(marketplace_url),
InstallProgress::new(s9pk.content_length()),
response_to_reader(s9pk),
None,
)
.await
{
@@ -425,52 +427,64 @@ pub async fn sideload(
pde.save(&mut tx).await?;
tx.commit().await?;
if let Err(e) = download_install_s9pk(
&new_ctx,
&manifest,
None,
progress,
tokio_util::io::StreamReader::new(req.into_body().map_err(|e| {
std::io::Error::new(
match &e {
e if e.is_connect() => std::io::ErrorKind::ConnectionRefused,
e if e.is_timeout() => std::io::ErrorKind::TimedOut,
_ => std::io::ErrorKind::Other,
},
e,
)
})),
)
.await
{
let err_str = format!(
"Install of {}@{} Failed: {}",
manifest.id, manifest.version, e
);
tracing::error!("{}", err_str);
tracing::debug!("{:?}", e);
if let Err(e) = new_ctx
.notification_manager
.notify(
&mut hdl,
Some(manifest.id),
NotificationLevel::Error,
String::from("Install Failed"),
err_str,
(),
None,
)
.await
{
tracing::error!("Failed to issue Notification: {}", e);
tracing::debug!("{:?}", e);
}
}
let (send, recv) = oneshot::channel();
Response::builder()
.status(StatusCode::OK)
.body(Body::empty())
.with_kind(ErrorKind::Network)
tokio::spawn(async move {
if let Err(e) = download_install_s9pk(
&new_ctx,
&manifest,
None,
progress,
tokio_util::io::StreamReader::new(req.into_body().map_err(|e| {
std::io::Error::new(
match &e {
e if e.is_connect() => std::io::ErrorKind::ConnectionRefused,
e if e.is_timeout() => std::io::ErrorKind::TimedOut,
_ => std::io::ErrorKind::Other,
},
e,
)
})),
Some(send),
)
.await
{
let err_str = format!(
"Install of {}@{} Failed: {}",
manifest.id, manifest.version, e
);
tracing::error!("{}", err_str);
tracing::debug!("{:?}", e);
if let Err(e) = new_ctx
.notification_manager
.notify(
&mut hdl,
Some(manifest.id),
NotificationLevel::Error,
String::from("Install Failed"),
err_str,
(),
None,
)
.await
{
tracing::error!("Failed to issue Notification: {}", e);
tracing::debug!("{:?}", e);
}
}
});
if let Ok(_) = recv.await {
Response::builder()
.status(StatusCode::OK)
.body(Body::empty())
.with_kind(ErrorKind::Network)
} else {
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from("installation aborted before upload completed"))
.with_kind(ErrorKind::Network)
}
}
.boxed()
});
@@ -707,6 +721,7 @@ pub async fn download_install_s9pk(
marketplace_url: Option<Url>,
progress: Arc<InstallProgress>,
mut s9pk: impl AsyncRead + Unpin,
download_complete: Option<oneshot::Sender<()>>,
) -> Result<(), Error> {
let pkg_id = &temp_manifest.id;
let version = &temp_manifest.version;
@@ -799,6 +814,9 @@ pub async fn download_install_s9pk(
let mut progress_writer = InstallProgressTracker::new(&mut dst, progress.clone());
tokio::io::copy(&mut s9pk, &mut progress_writer).await?;
progress.download_complete();
if let Some(complete) = download_complete {
complete.send(()).unwrap_or_default();
}
Ok(())
})
.await?;
@@ -1279,6 +1297,14 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
migration.or(prev_migration)
};
remove_from_current_dependents_lists(
&mut tx,
pkg_id,
&prev.current_dependencies,
&receipts.config.current_dependents,
)
.await?; // remove previous
let configured = if let Some(f) = viable_migration {
f.await?.configured && prev_is_configured
} else {
@@ -1298,13 +1324,6 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
)
.await?;
} else {
remove_from_current_dependents_lists(
&mut tx,
pkg_id,
&prev.current_dependencies,
&receipts.config.current_dependents,
)
.await?; // remove previous
add_dependent_to_current_dependents_lists(
&mut tx,
pkg_id,
@@ -1449,6 +1468,7 @@ pub fn load_images<'a, P: AsRef<Path> + 'a + Send + Sync>(
"Error loading docker images from s9pk: {e}"
);
tracing::debug!("{e:?}");
return Ok(());
}
},
_ => unreachable!(),

View File

@@ -76,7 +76,7 @@ pub async fn dry(
.current_dependents
.get(&mut tx, &id)
.await?
.ok_or_else(not_found)?
.ok_or_else(|| not_found!(id))?
.0
.keys()
.into_iter()

View File

@@ -5,14 +5,12 @@ pub const DEFAULT_MARKETPLACE: &str = "https://registry.start9.com";
pub const BUFFER_SIZE: usize = 1024;
pub const HOST_IP: [u8; 4] = [172, 18, 0, 1];
pub const TARGET: &str = current_platform::CURRENT_PLATFORM;
pub const OS_ARCH: &str = env!("OS_ARCH");
lazy_static::lazy_static! {
pub static ref ARCH: &'static str = {
let (arch, _) = TARGET.split_once("-").unwrap();
arch
};
pub static ref IS_RASPBERRY_PI: bool = {
*ARCH == "aarch64"
};
}
pub mod account;
@@ -87,6 +85,7 @@ pub fn main_api() -> Result<(), RpcError> {
#[command(subcommands(
system::time,
system::experimental,
system::logs,
system::kernel_logs,
system::metrics,

View File

@@ -33,7 +33,7 @@ use crate::util::serde::Reversible;
use crate::{Error, ErrorKind};
#[pin_project::pin_project]
struct LogStream {
pub struct LogStream {
_child: Child,
#[pin]
entries: BoxStream<'static, Result<JournalctlEntry, Error>>,
@@ -141,14 +141,14 @@ impl std::fmt::Display for LogEntry {
}
#[derive(Serialize, Deserialize, Debug)]
struct JournalctlEntry {
pub struct JournalctlEntry {
#[serde(rename = "__REALTIME_TIMESTAMP")]
timestamp: String,
pub timestamp: String,
#[serde(rename = "MESSAGE")]
#[serde(deserialize_with = "deserialize_string_or_utf8_array")]
message: String,
pub message: String,
#[serde(rename = "__CURSOR")]
cursor: String,
pub cursor: String,
}
impl JournalctlEntry {
fn log_entry(self) -> Result<(String, LogEntry), Error> {
@@ -344,7 +344,7 @@ pub async fn cli_logs_generic_follow(
Ok(())
}
async fn journalctl(
pub async fn journalctl(
id: LogSource,
limit: usize,
cursor: Option<&str>,

View File

@@ -7,4 +7,4 @@ prompt = no
[req_distinguished_name]
CN = {hostname}.local
O = Start9 Labs
OU = Embassy
OU = StartOS

View File

@@ -11,6 +11,7 @@ use models::PackageId;
use tokio::net::{TcpListener, UdpSocket};
use tokio::process::Command;
use tokio::sync::RwLock;
use tracing::instrument;
use trust_dns_server::authority::MessageResponseBuilder;
use trust_dns_server::client::op::{Header, ResponseCode};
use trust_dns_server::client::rr::{Name, Record, RecordType};
@@ -147,6 +148,7 @@ impl RequestHandler for Resolver {
}
impl DnsController {
#[instrument(skip_all)]
pub async fn init(bind: &[SocketAddr]) -> Result<Self, Error> {
let services = Arc::new(RwLock::new(BTreeMap::new()));
@@ -161,10 +163,16 @@ impl DnsController {
);
server.register_socket(UdpSocket::bind(bind).await.with_kind(ErrorKind::Network)?);
Command::new("systemd-resolve")
.arg("--set-dns=127.0.0.1")
.arg("--interface=br-start9")
.arg("--set-domain=embassy")
Command::new("resolvectl")
.arg("dns")
.arg("br-start9")
.arg("127.0.0.1")
.invoke(ErrorKind::Network)
.await?;
Command::new("resolvectl")
.arg("domain")
.arg("br-start9")
.arg("embassy")
.invoke(ErrorKind::Network)
.await?;

View File

@@ -5,6 +5,7 @@ use std::sync::{Arc, Weak};
use color_eyre::eyre::eyre;
use tokio::process::{Child, Command};
use tokio::sync::Mutex;
use tracing::instrument;
use crate::util::Invoke;
use crate::{Error, ResultExt};
@@ -51,6 +52,7 @@ pub struct MdnsControllerInner {
}
impl MdnsControllerInner {
#[instrument(skip_all)]
async fn init() -> Result<Self, Error> {
let mut res = MdnsControllerInner {
alias_cmd: None,
@@ -59,6 +61,7 @@ impl MdnsControllerInner {
res.sync().await?;
Ok(res)
}
#[instrument(skip_all)]
async fn sync(&mut self) -> Result<(), Error> {
if let Some(mut cmd) = self.alias_cmd.take() {
cmd.kill().await.with_kind(crate::ErrorKind::Network)?;

View File

@@ -34,6 +34,7 @@ impl NetController {
#[instrument(skip_all)]
pub async fn init(
tor_control: SocketAddr,
tor_socks: SocketAddr,
dns_bind: &[SocketAddr],
ssl: SslManager,
hostname: &Hostname,
@@ -41,7 +42,7 @@ impl NetController {
) -> Result<Self, Error> {
let ssl = Arc::new(ssl);
let mut res = Self {
tor: TorController::init(tor_control).await?,
tor: TorController::new(tor_control, tor_socks),
#[cfg(feature = "avahi")]
mdns: MdnsController::init().await?,
vhost: VHostController::new(ssl.clone()),
@@ -114,7 +115,7 @@ impl NetController {
// Tor (http)
self.os_bindings.push(
self.tor
.add(&key.tor_key(), 80, ([127, 0, 0, 1], 80).into())
.add(key.tor_key(), 80, ([127, 0, 0, 1], 80).into())
.await?,
);
@@ -132,7 +133,7 @@ impl NetController {
);
self.os_bindings.push(
self.tor
.add(&key.tor_key(), 443, ([127, 0, 0, 1], 443).into())
.add(key.tor_key(), 443, ([127, 0, 0, 1], 443).into())
.await?,
);
@@ -164,13 +165,13 @@ impl NetController {
target: SocketAddr,
) -> Result<Vec<Arc<()>>, Error> {
let mut rcs = Vec::with_capacity(1);
rcs.push(self.tor.add(&key.tor_key(), external, target).await?);
rcs.push(self.tor.add(key.tor_key(), external, target).await?);
Ok(rcs)
}
async fn remove_tor(&self, key: &Key, external: u16, rcs: Vec<Arc<()>>) -> Result<(), Error> {
drop(rcs);
self.tor.gc(&key.tor_key(), external).await
self.tor.gc(Some(key.tor_key()), Some(external)).await
}
async fn add_lan(

View File

@@ -173,7 +173,7 @@ pub fn make_root_cert(root_key: &PKey<Private>, hostname: &Hostname) -> Result<X
let mut subject_name_builder = X509NameBuilder::new()?;
subject_name_builder.append_entry_by_text("CN", &format!("{} Local Root CA", &*hostname.0))?;
subject_name_builder.append_entry_by_text("O", "Start9")?;
subject_name_builder.append_entry_by_text("OU", "Embassy")?;
subject_name_builder.append_entry_by_text("OU", "StartOS")?;
let subject_name = subject_name_builder.build();
builder.set_subject_name(&subject_name)?;
@@ -225,9 +225,9 @@ pub fn make_int_cert(
builder.set_serial_number(&*rand_serial()?)?;
let mut subject_name_builder = X509NameBuilder::new()?;
subject_name_builder.append_entry_by_text("CN", "Embassy Local Intermediate CA")?;
subject_name_builder.append_entry_by_text("CN", "StartOS Local Intermediate CA")?;
subject_name_builder.append_entry_by_text("O", "Start9")?;
subject_name_builder.append_entry_by_text("OU", "Embassy")?;
subject_name_builder.append_entry_by_text("OU", "StartOS")?;
let subject_name = subject_name_builder.build();
builder.set_subject_name(&subject_name)?;
@@ -370,7 +370,7 @@ pub fn make_leaf_cert(
.unwrap_or("localhost"),
)?;
subject_name_builder.append_entry_by_text("O", "Start9")?;
subject_name_builder.append_entry_by_text("OU", "Embassy")?;
subject_name_builder.append_entry_by_text("OU", "StartOS")?;
let subject_name = subject_name_builder.build();
builder.set_subject_name(&subject_name)?;

View File

@@ -3,13 +3,11 @@ use std::path::Path;
use std::sync::Arc;
use std::time::UNIX_EPOCH;
use async_compression::tokio::bufread::BrotliEncoder;
use async_compression::tokio::bufread::GzipEncoder;
use async_compression::tokio::bufread::{BrotliEncoder, GzipEncoder};
use color_eyre::eyre::eyre;
use digest::Digest;
use futures::FutureExt;
use http::header::ACCEPT_ENCODING;
use http::header::CONTENT_ENCODING;
use http::header::{ACCEPT_ENCODING, CONTENT_ENCODING};
use http::request::Parts as RequestParts;
use http::response::Builder;
use hyper::{Body, Method, Request, Response, StatusCode};

View File

@@ -1,32 +1,80 @@
use std::collections::BTreeMap;
use std::net::SocketAddr;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Weak};
use std::time::Duration;
use clap::ArgMatches;
use color_eyre::eyre::eyre;
use futures::future::BoxFuture;
use futures::FutureExt;
use futures::{FutureExt, TryStreamExt};
use helpers::NonDetachingJoinHandle;
use itertools::Itertools;
use lazy_static::lazy_static;
use regex::Regex;
use rpc_toolkit::command;
use rpc_toolkit::yajrc::RpcError;
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use tokio::process::Command;
use tokio::sync::{mpsc, oneshot};
use tokio::time::Instant;
use torut::control::{AsyncEvent, AuthenticatedConn, ConnError};
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
use tracing::instrument;
use crate::context::RpcContext;
use crate::context::{CliContext, RpcContext};
use crate::logs::{
cli_logs_generic_follow, cli_logs_generic_nofollow, fetch_logs, follow_logs, journalctl,
LogFollowResponse, LogResponse, LogSource,
};
use crate::util::serde::{display_serializable, IoFormat};
use crate::util::{display_none, Invoke};
use crate::{Error, ErrorKind, ResultExt as _};
pub const SYSTEMD_UNIT: &str = "tor@default";
const STARTING_HEALTH_TIMEOUT: u64 = 120; // 2min
enum ErrorLogSeverity {
Fatal { wipe_state: bool },
Unknown { wipe_state: bool },
}
lazy_static! {
static ref LOG_REGEXES: Vec<(Regex, ErrorLogSeverity)> = vec![(
Regex::new("This could indicate a route manipulation attack, network overload, bad local network connectivity, or a bug\\.").unwrap(),
ErrorLogSeverity::Unknown { wipe_state: true }
),(
Regex::new("died due to an invalid selected path").unwrap(),
ErrorLogSeverity::Fatal { wipe_state: false }
),(
Regex::new("Tor has not observed any network activity for the past").unwrap(),
ErrorLogSeverity::Unknown { wipe_state: false }
)];
static ref PROGRESS_REGEX: Regex = Regex::new("PROGRESS=([0-9]+)").unwrap();
}
#[test]
fn random_key() {
println!("x'{}'", hex::encode(rand::random::<[u8; 32]>()));
}
#[command(subcommands(list_services))]
#[command(subcommands(list_services, logs, reset))]
pub fn tor() -> Result<(), Error> {
Ok(())
}
#[command(display(display_none))]
pub async fn reset(
#[context] ctx: RpcContext,
#[arg(rename = "wipe-state", short = 'w', long = "wipe-state")] wipe_state: bool,
#[arg] reason: String,
) -> Result<(), Error> {
ctx.net_controller
.tor
.reset(wipe_state, Error::new(eyre!("{reason}"), ErrorKind::Tor))
.await
}
fn display_services(services: Vec<OnionAddressV3>, matches: &ArgMatches) {
use prettytable::*;
@@ -52,133 +100,227 @@ pub async fn list_services(
ctx.net_controller.tor.list_services().await
}
#[command(
custom_cli(cli_logs(async, context(CliContext))),
subcommands(self(logs_nofollow(async)), logs_follow),
display(display_none)
)]
pub async fn logs(
#[arg(short = 'l', long = "limit")] limit: Option<usize>,
#[arg(short = 'c', long = "cursor")] cursor: Option<String>,
#[arg(short = 'B', long = "before", default)] before: bool,
#[arg(short = 'f', long = "follow", default)] follow: bool,
) -> Result<(Option<usize>, Option<String>, bool, bool), Error> {
Ok((limit, cursor, before, follow))
}
pub async fn cli_logs(
ctx: CliContext,
(limit, cursor, before, follow): (Option<usize>, Option<String>, bool, bool),
) -> Result<(), RpcError> {
if follow {
if cursor.is_some() {
return Err(RpcError::from(Error::new(
eyre!("The argument '--cursor <cursor>' cannot be used with '--follow'"),
crate::ErrorKind::InvalidRequest,
)));
}
if before {
return Err(RpcError::from(Error::new(
eyre!("The argument '--before' cannot be used with '--follow'"),
crate::ErrorKind::InvalidRequest,
)));
}
cli_logs_generic_follow(ctx, "net.tor.logs.follow", None, limit).await
} else {
cli_logs_generic_nofollow(ctx, "net.tor.logs", None, limit, cursor, before).await
}
}
pub async fn logs_nofollow(
_ctx: (),
(limit, cursor, before, _): (Option<usize>, Option<String>, bool, bool),
) -> Result<LogResponse, Error> {
fetch_logs(LogSource::Service(SYSTEMD_UNIT), limit, cursor, before).await
}
#[command(rpc_only, rename = "follow", display(display_none))]
pub async fn logs_follow(
#[context] ctx: RpcContext,
#[parent_data] (limit, _, _, _): (Option<usize>, Option<String>, bool, bool),
) -> Result<LogFollowResponse, Error> {
follow_logs(ctx, LogSource::Service(SYSTEMD_UNIT), limit).await
}
fn event_handler(_event: AsyncEvent<'static>) -> BoxFuture<'static, Result<(), ConnError>> {
async move { Ok(()) }.boxed()
}
pub struct TorController(Mutex<TorControllerInner>);
pub struct TorController(TorControl);
impl TorController {
pub async fn init(tor_control: SocketAddr) -> Result<Self, Error> {
Ok(TorController(Mutex::new(
TorControllerInner::init(tor_control).await?,
)))
pub fn new(tor_control: SocketAddr, tor_socks: SocketAddr) -> Self {
TorController(TorControl::new(tor_control, tor_socks))
}
pub async fn add(
&self,
key: &TorSecretKeyV3,
key: TorSecretKeyV3,
external: u16,
target: SocketAddr,
) -> Result<Arc<()>, Error> {
self.0.lock().await.add(key, external, target).await
let (reply, res) = oneshot::channel();
self.0
.send
.send(TorCommand::AddOnion {
key,
external,
target,
reply,
})
.ok()
.ok_or_else(|| Error::new(eyre!("TorControl died"), ErrorKind::Tor))?;
res.await
.ok()
.ok_or_else(|| Error::new(eyre!("TorControl died"), ErrorKind::Tor))
}
pub async fn gc(&self, key: &TorSecretKeyV3, external: u16) -> Result<(), Error> {
self.0.lock().await.gc(key, external).await
pub async fn gc(
&self,
key: Option<TorSecretKeyV3>,
external: Option<u16>,
) -> Result<(), Error> {
self.0
.send
.send(TorCommand::GC { key, external })
.ok()
.ok_or_else(|| Error::new(eyre!("TorControl died"), ErrorKind::Tor))
}
pub async fn reset(&self, wipe_state: bool, context: Error) -> Result<(), Error> {
self.0
.send
.send(TorCommand::Reset {
wipe_state,
context,
})
.ok()
.ok_or_else(|| Error::new(eyre!("TorControl died"), ErrorKind::Tor))
}
pub async fn list_services(&self) -> Result<Vec<OnionAddressV3>, Error> {
self.0.lock().await.list_services().await
let (reply, res) = oneshot::channel();
self.0
.send
.send(TorCommand::GetInfo {
query: "onions/current".into(),
reply,
})
.ok()
.ok_or_else(|| Error::new(eyre!("TorControl died"), ErrorKind::Tor))?;
res.await
.ok()
.ok_or_else(|| Error::new(eyre!("TorControl died"), ErrorKind::Tor))??
.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty())
.map(|l| l.parse().with_kind(ErrorKind::Tor))
.collect()
}
}
type AuthenticatedConnection = AuthenticatedConn<
TcpStream,
fn(AsyncEvent<'static>) -> BoxFuture<'static, Result<(), ConnError>>,
Box<dyn Fn(AsyncEvent<'static>) -> BoxFuture<'static, Result<(), ConnError>> + Send + Sync>,
>;
pub struct TorControllerInner {
control_addr: SocketAddr,
connection: AuthenticatedConnection,
services: BTreeMap<String, BTreeMap<u16, BTreeMap<SocketAddr, Weak<()>>>>,
}
impl TorControllerInner {
#[instrument(skip_all)]
async fn add(
&mut self,
key: &TorSecretKeyV3,
enum TorCommand {
AddOnion {
key: TorSecretKeyV3,
external: u16,
target: SocketAddr,
) -> Result<Arc<()>, Error> {
let mut rm_res = Ok(());
let onion_base = key
.public()
.get_onion_address()
.get_address_without_dot_onion();
let mut service = if let Some(service) = self.services.remove(&onion_base) {
rm_res = self.connection.del_onion(&onion_base).await;
service
} else {
BTreeMap::new()
};
let mut binding = service.remove(&external).unwrap_or_default();
let rc = if let Some(rc) = Weak::upgrade(&binding.remove(&target).unwrap_or_default()) {
rc
} else {
Arc::new(())
};
binding.insert(target, Arc::downgrade(&rc));
service.insert(external, binding);
let bindings = service
.iter()
.flat_map(|(ext, int)| {
int.iter()
.find(|(_, rc)| rc.strong_count() > 0)
.map(|(addr, _)| (*ext, SocketAddr::from(*addr)))
})
.collect::<Vec<_>>();
self.services.insert(onion_base, service);
rm_res?;
self.connection
.add_onion_v3(key, false, false, false, None, &mut bindings.iter())
.await?;
Ok(rc)
}
reply: oneshot::Sender<Arc<()>>,
},
GC {
key: Option<TorSecretKeyV3>,
external: Option<u16>,
},
GetInfo {
query: String,
reply: oneshot::Sender<Result<String, Error>>,
},
Reset {
wipe_state: bool,
context: Error,
},
}
#[instrument(skip_all)]
async fn gc(&mut self, key: &TorSecretKeyV3, external: u16) -> Result<(), Error> {
let onion_base = key
.public()
.get_onion_address()
.get_address_without_dot_onion();
if let Some(mut service) = self.services.remove(&onion_base) {
if let Some(mut binding) = service.remove(&external) {
binding = binding
.into_iter()
.filter(|(_, rc)| rc.strong_count() > 0)
.collect();
if !binding.is_empty() {
service.insert(external, binding);
#[instrument(skip_all)]
async fn torctl(
tor_control: SocketAddr,
tor_socks: SocketAddr,
recv: &mut mpsc::UnboundedReceiver<TorCommand>,
services: &mut BTreeMap<[u8; 64], BTreeMap<u16, BTreeMap<SocketAddr, Weak<()>>>>,
wipe_state: &AtomicBool,
health_timeout: &mut Duration,
) -> Result<(), Error> {
let bootstrap = async {
if Command::new("systemctl")
.arg("is-active")
.arg("--quiet")
.arg("tor")
.invoke(ErrorKind::Tor)
.await
.is_ok()
{
Command::new("systemctl")
.arg("stop")
.arg("tor")
.invoke(ErrorKind::Tor)
.await?;
for _ in 0..30 {
if TcpStream::connect(tor_control).await.is_err() {
break;
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
let rm_res = self.connection.del_onion(&onion_base).await;
if !service.is_empty() {
let bindings = service
.iter()
.flat_map(|(ext, int)| {
int.iter()
.find(|(_, rc)| rc.strong_count() > 0)
.map(|(addr, _)| (*ext, SocketAddr::from(*addr)))
})
.collect::<Vec<_>>();
self.services.insert(onion_base, service);
rm_res?;
self.connection
.add_onion_v3(&key, false, false, false, None, &mut bindings.iter())
.await?;
} else {
rm_res?;
if TcpStream::connect(tor_control).await.is_ok() {
return Err(Error::new(
eyre!("Tor is failing to shut down"),
ErrorKind::Tor,
));
}
}
if wipe_state.load(std::sync::atomic::Ordering::SeqCst) {
tokio::fs::remove_dir_all("/var/lib/tor").await?;
wipe_state.store(false, std::sync::atomic::Ordering::SeqCst);
}
tokio::fs::create_dir_all("/var/lib/tor").await?;
Command::new("chown")
.arg("-R")
.arg("debian-tor")
.arg("/var/lib/tor")
.invoke(ErrorKind::Filesystem)
.await?;
Command::new("systemctl")
.arg("start")
.arg("tor")
.invoke(ErrorKind::Tor)
.await?;
Ok(())
}
let logs = journalctl(LogSource::Service(SYSTEMD_UNIT), 0, None, false, true).await?;
#[instrument(skip_all)]
async fn init(tor_control: SocketAddr) -> Result<Self, Error> {
let mut conn = torut::control::UnauthenticatedConn::new(
TcpStream::connect(tor_control).await?, // TODO
);
let mut tcp_stream = None;
for _ in 0..60 {
if let Ok(conn) = TcpStream::connect(tor_control).await {
tcp_stream = Some(conn);
break;
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
let tcp_stream = tcp_stream.ok_or_else(|| {
Error::new(eyre!("Timed out waiting for tor to start"), ErrorKind::Tor)
})?;
tracing::info!("Tor is started");
let mut conn = torut::control::UnauthenticatedConn::new(tcp_stream);
let auth = conn
.load_protocol_info()
.await?
@@ -187,25 +329,356 @@ impl TorControllerInner {
.with_kind(crate::ErrorKind::Tor)?;
conn.authenticate(&auth).await?;
let mut connection: AuthenticatedConnection = conn.into_authenticated().await;
connection.set_async_event_handler(Some(event_handler));
connection.set_async_event_handler(Some(Box::new(|event| event_handler(event))));
Ok(Self {
control_addr: tor_control,
connection,
services: BTreeMap::new(),
})
let mut bootstrapped = false;
let mut last_increment = (String::new(), Instant::now());
for _ in 0..300 {
match connection.get_info("status/bootstrap-phase").await {
Ok(a) => {
if a.contains("TAG=done") {
bootstrapped = true;
break;
}
if let Some(p) = PROGRESS_REGEX.captures(&a) {
if let Some(p) = p.get(1) {
if p.as_str() != &*last_increment.0 {
last_increment = (p.as_str().into(), Instant::now());
}
}
}
}
Err(e) => {
let e = Error::from(e);
tracing::error!("{}", e);
tracing::debug!("{:?}", e);
}
}
if last_increment.1.elapsed() > Duration::from_secs(30) {
return Err(Error::new(
eyre!("Tor stuck bootstrapping at {}%", last_increment.0),
ErrorKind::Tor,
));
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
if !bootstrapped {
return Err(Error::new(
eyre!("Timed out waiting for tor to bootstrap"),
ErrorKind::Tor,
));
}
Ok((connection, logs))
};
let pre_handler = async {
while let Some(command) = recv.recv().await {
match command {
TorCommand::AddOnion {
key,
external,
target,
reply,
} => {
let mut service = if let Some(service) = services.remove(&key.as_bytes()) {
service
} else {
BTreeMap::new()
};
let mut binding = service.remove(&external).unwrap_or_default();
let rc = if let Some(rc) =
Weak::upgrade(&binding.remove(&target).unwrap_or_default())
{
rc
} else {
Arc::new(())
};
binding.insert(target, Arc::downgrade(&rc));
service.insert(external, binding);
services.insert(key.as_bytes(), service);
reply.send(rc).unwrap_or_default();
}
TorCommand::GetInfo { reply, .. } => {
reply
.send(Err(Error::new(
eyre!("Tor has not finished bootstrapping..."),
ErrorKind::Tor,
)))
.unwrap_or_default();
}
TorCommand::GC { .. } => (),
TorCommand::Reset {
wipe_state: new_wipe_state,
context,
} => {
wipe_state.fetch_or(new_wipe_state, std::sync::atomic::Ordering::SeqCst);
return Err(context);
}
}
}
Ok(())
};
let (mut connection, mut logs) = tokio::select! {
res = bootstrap => res?,
res = pre_handler => return res,
};
let hck_key = TorSecretKeyV3::generate();
connection
.add_onion_v3(
&hck_key,
false,
false,
false,
None,
&mut [(80, SocketAddr::from(([127, 0, 0, 1], 80)))].iter(),
)
.await?;
for (key, service) in std::mem::take(services) {
let key = TorSecretKeyV3::from(key);
let bindings = service
.iter()
.flat_map(|(ext, int)| {
int.iter()
.find(|(_, rc)| rc.strong_count() > 0)
.map(|(addr, _)| (*ext, SocketAddr::from(*addr)))
})
.collect::<Vec<_>>();
if !bindings.is_empty() {
services.insert(key.as_bytes(), service);
connection
.add_onion_v3(&key, false, false, false, None, &mut bindings.iter())
.await?;
}
}
#[instrument(skip_all)]
async fn list_services(&mut self) -> Result<Vec<OnionAddressV3>, Error> {
self.connection
.get_info("onions/current")
.await?
.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty())
.map(|l| l.parse().with_kind(ErrorKind::Tor))
.collect()
let handler = async {
while let Some(command) = recv.recv().await {
match command {
TorCommand::AddOnion {
key,
external,
target,
reply,
} => {
let mut rm_res = Ok(());
let onion_base = key
.public()
.get_onion_address()
.get_address_without_dot_onion();
let mut service = if let Some(service) = services.remove(&key.as_bytes()) {
rm_res = connection.del_onion(&onion_base).await;
service
} else {
BTreeMap::new()
};
let mut binding = service.remove(&external).unwrap_or_default();
let rc = if let Some(rc) =
Weak::upgrade(&binding.remove(&target).unwrap_or_default())
{
rc
} else {
Arc::new(())
};
binding.insert(target, Arc::downgrade(&rc));
service.insert(external, binding);
let bindings = service
.iter()
.flat_map(|(ext, int)| {
int.iter()
.find(|(_, rc)| rc.strong_count() > 0)
.map(|(addr, _)| (*ext, SocketAddr::from(*addr)))
})
.collect::<Vec<_>>();
services.insert(key.as_bytes(), service);
reply.send(rc).unwrap_or_default();
rm_res?;
connection
.add_onion_v3(&key, false, false, false, None, &mut bindings.iter())
.await?;
}
TorCommand::GC { key, external } => {
for key in if key.is_some() {
itertools::Either::Left(key.into_iter().map(|k| k.as_bytes()))
} else {
itertools::Either::Right(services.keys().cloned().collect_vec().into_iter())
} {
let key = TorSecretKeyV3::from(key);
let onion_base = key
.public()
.get_onion_address()
.get_address_without_dot_onion();
if let Some(mut service) = services.remove(&key.as_bytes()) {
for external in if external.is_some() {
itertools::Either::Left(external.into_iter())
} else {
itertools::Either::Right(
service.keys().copied().collect_vec().into_iter(),
)
} {
if let Some(mut binding) = service.remove(&external) {
binding = binding
.into_iter()
.filter(|(_, rc)| rc.strong_count() > 0)
.collect();
if !binding.is_empty() {
service.insert(external, binding);
}
}
}
let rm_res = connection.del_onion(&onion_base).await;
if !service.is_empty() {
let bindings = service
.iter()
.flat_map(|(ext, int)| {
int.iter()
.find(|(_, rc)| rc.strong_count() > 0)
.map(|(addr, _)| (*ext, SocketAddr::from(*addr)))
})
.collect::<Vec<_>>();
if !bindings.is_empty() {
services.insert(key.as_bytes(), service);
}
rm_res?;
if !bindings.is_empty() {
connection
.add_onion_v3(
&key,
false,
false,
false,
None,
&mut bindings.iter(),
)
.await?;
}
} else {
rm_res?;
}
}
}
}
TorCommand::GetInfo { query, reply } => {
reply
.send(connection.get_info(&query).await.with_kind(ErrorKind::Tor))
.unwrap_or_default();
}
TorCommand::Reset {
wipe_state: new_wipe_state,
context,
} => {
wipe_state.fetch_or(new_wipe_state, std::sync::atomic::Ordering::SeqCst);
return Err(context);
}
}
}
Ok(())
};
let log_parser = async {
while let Some(log) = logs.try_next().await? {
for (regex, severity) in &*LOG_REGEXES {
if regex.is_match(&log.message) {
let (check, wipe_state) = match severity {
ErrorLogSeverity::Fatal { wipe_state } => (false, *wipe_state),
ErrorLogSeverity::Unknown { wipe_state } => (true, *wipe_state),
};
if !check
|| tokio::time::timeout(
Duration::from_secs(30),
tokio_socks::tcp::Socks5Stream::connect(
tor_socks,
(hck_key.public().get_onion_address().to_string(), 80),
),
)
.await
.map_err(|e| tracing::warn!("Tor is confirmed to be down: {e}"))
.and_then(|a| {
a.map_err(|e| tracing::warn!("Tor is confirmed to be down: {e}"))
})
.is_err()
{
if wipe_state {
Command::new("systemctl")
.arg("stop")
.arg("tor")
.invoke(ErrorKind::Tor)
.await?;
tokio::fs::remove_dir_all("/var/lib/tor").await?;
}
return Err(Error::new(eyre!("{}", log.message), ErrorKind::Tor));
}
}
}
}
Err(Error::new(eyre!("Log stream terminated"), ErrorKind::Tor))
};
let health_checker = async {
let mut last_success = Instant::now();
loop {
tokio::time::sleep(Duration::from_secs(30)).await;
if let Err(e) = tokio::time::timeout(
Duration::from_secs(30),
tokio_socks::tcp::Socks5Stream::connect(
tor_socks,
(hck_key.public().get_onion_address().to_string(), 80),
),
)
.await
.map_err(|e| e.to_string())
.and_then(|e| e.map_err(|e| e.to_string()))
{
if last_success.elapsed() > *health_timeout {
let err = Error::new(eyre!("Tor health check failed for longer than current timeout ({health_timeout:?})"), crate::ErrorKind::Tor);
*health_timeout *= 2;
wipe_state.store(true, std::sync::atomic::Ordering::SeqCst);
return Err(err);
}
} else {
last_success = Instant::now();
}
}
};
tokio::select! {
res = handler => res?,
res = log_parser => res?,
res = health_checker => res?,
}
Ok(())
}
struct TorControl {
_thread: NonDetachingJoinHandle<()>,
send: mpsc::UnboundedSender<TorCommand>,
}
impl TorControl {
pub fn new(tor_control: SocketAddr, tor_socks: SocketAddr) -> Self {
let (send, mut recv) = mpsc::unbounded_channel();
Self {
_thread: tokio::spawn(async move {
let mut services = BTreeMap::new();
let wipe_state = AtomicBool::new(false);
let mut health_timeout = Duration::from_secs(STARTING_HEALTH_TIMEOUT);
while let Err(e) = torctl(
tor_control,
tor_socks,
&mut recv,
&mut services,
&wipe_state,
&mut health_timeout,
)
.await
{
tracing::error!("{e}: Restarting tor");
tracing::debug!("{e:?}");
}
tracing::info!("TorControl is shut down.")
})
.into(),
send,
}
}
}

View File

@@ -1,5 +1,5 @@
use std::convert::Infallible;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
use std::path::Path;
use async_stream::try_stream;
@@ -7,24 +7,29 @@ use color_eyre::eyre::eyre;
use futures::stream::BoxStream;
use futures::{StreamExt, TryStreamExt};
use ipnet::{Ipv4Net, Ipv6Net};
use tokio::net::{TcpListener, TcpStream};
use tokio::process::Command;
use crate::util::Invoke;
use crate::Error;
fn parse_iface_ip(output: &str) -> Result<Option<&str>, Error> {
fn parse_iface_ip(output: &str) -> Result<Vec<&str>, Error> {
let output = output.trim();
if output.is_empty() {
return Ok(None);
return Ok(Vec::new());
}
if let Some(ip) = output.split_ascii_whitespace().nth(3) {
Ok(Some(ip))
} else {
Err(Error::new(
eyre!("malformed output from `ip`"),
crate::ErrorKind::Network,
))
let mut res = Vec::new();
for line in output.lines() {
if let Some(ip) = line.split_ascii_whitespace().nth(3) {
res.push(ip)
} else {
return Err(Error::new(
eyre!("malformed output from `ip`"),
crate::ErrorKind::Network,
));
}
}
Ok(res)
}
pub async fn get_iface_ipv4_addr(iface: &str) -> Result<Option<(Ipv4Addr, Ipv4Net)>, Error> {
@@ -38,7 +43,9 @@ pub async fn get_iface_ipv4_addr(iface: &str) -> Result<Option<(Ipv4Addr, Ipv4Ne
.invoke(crate::ErrorKind::Network)
.await?,
)?)?
.into_iter()
.map(|s| Ok::<_, Error>((s.split("/").next().unwrap().parse()?, s.parse()?)))
.next()
.transpose()?)
}
@@ -53,6 +60,8 @@ pub async fn get_iface_ipv6_addr(iface: &str) -> Result<Option<(Ipv6Addr, Ipv6Ne
.invoke(crate::ErrorKind::Network)
.await?,
)?)?
.into_iter()
.find(|ip| !ip.starts_with("fe80::"))
.map(|s| Ok::<_, Error>((s.split("/").next().unwrap().parse()?, s.parse()?)))
.transpose()?)
}
@@ -121,3 +130,37 @@ impl<T> hyper::server::accept::Accept for SingleAccept<T> {
std::task::Poll::Ready(self.project().0.take().map(Ok))
}
}
pub struct TcpListeners {
listeners: Vec<TcpListener>,
}
impl TcpListeners {
pub fn new(listeners: impl IntoIterator<Item = TcpListener>) -> Self {
Self {
listeners: listeners.into_iter().collect(),
}
}
pub async fn accept(&self) -> std::io::Result<(TcpStream, SocketAddr)> {
futures::future::select_all(self.listeners.iter().map(|l| Box::pin(l.accept())))
.await
.0
}
}
impl hyper::server::accept::Accept for TcpListeners {
type Conn = TcpStream;
type Error = std::io::Error;
fn poll_accept(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Result<Self::Conn, Self::Error>>> {
for listener in self.listeners.iter() {
let poll = listener.poll_accept(cx);
if poll.is_ready() {
return poll.map(|a| a.map(|a| a.0)).map(Some);
}
}
std::task::Poll::Pending
}
}

View File

@@ -1,6 +1,6 @@
use std::collections::BTreeMap;
use std::convert::Infallible;
use std::net::{IpAddr, SocketAddr};
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
use std::str::FromStr;
use std::sync::{Arc, Weak};
@@ -88,7 +88,7 @@ struct VHostServer {
impl VHostServer {
async fn new(port: u16, ssl: Arc<SslManager>) -> Result<Self, Error> {
// check if port allowed
let listener = TcpListener::bind(SocketAddr::new([0, 0, 0, 0].into(), port))
let listener = TcpListener::bind(SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), port))
.await
.with_kind(crate::ErrorKind::Network)?;
let mapping = Arc::new(RwLock::new(BTreeMap::new()));

View File

@@ -173,7 +173,7 @@ pub async fn delete(#[context] ctx: RpcContext, #[arg] ssid: String) -> Result<(
let is_current_removed_and_no_hardwire =
is_current_being_removed && !interface_connected(&ctx.ethernet_interface).await?;
if is_current_removed_and_no_hardwire {
return Err(Error::new(color_eyre::eyre::eyre!("Forbidden: Deleting this Network would make your Embassy Unreachable. Either connect to ethernet or connect to a different WiFi network to remedy this."), ErrorKind::Wifi));
return Err(Error::new(color_eyre::eyre::eyre!("Forbidden: Deleting this network would make your server unreachable. Either connect to ethernet or connect to a different WiFi network to remedy this."), ErrorKind::Wifi));
}
wpa_supplicant

View File

@@ -1,3 +1,5 @@
use std::path::Path;
use color_eyre::eyre::eyre;
use gpt::disk::LogicalBlockSize;
use gpt::GptConfig;
@@ -8,9 +10,10 @@ use crate::os_install::partition_for;
use crate::Error;
pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result<OsPartitionInfo, Error> {
{
let efi = {
let disk = disk.clone();
tokio::task::spawn_blocking(move || {
let use_efi = Path::new("/sys/firmware/efi").exists();
let mut device = Box::new(
std::fs::File::options()
.read(true)
@@ -44,17 +47,15 @@ pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result<OsPartitionIn
.map(|(idx, x)| (idx + 1, x))
{
if let Some(entry) = gpt.partitions().get(&(idx as u32)) {
if entry.first_lba >= 33556480 {
if idx < 3 {
guid_part = Some(entry.clone())
}
break;
}
if part_info.guid.is_some() {
return Err(Error::new(
eyre!("Not enough space before embassy data"),
crate::ErrorKind::InvalidRequest,
));
if entry.first_lba < if use_efi { 33759266 } else { 33570850 } {
return Err(Error::new(
eyre!("Not enough space before embassy data"),
crate::ErrorKind::InvalidRequest,
));
}
guid_part = Some(entry.clone());
break;
}
}
}
@@ -63,7 +64,19 @@ pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result<OsPartitionIn
gpt.update_partitions(Default::default())?;
gpt.add_partition("efi", 100 * 1024 * 1024, gpt::partition_types::EFI, 0, None)?;
let efi = if use_efi {
gpt.add_partition("efi", 100 * 1024 * 1024, gpt::partition_types::EFI, 0, None)?;
true
} else {
gpt.add_partition(
"bios-grub",
8 * 1024 * 1024,
gpt::partition_types::BIOS,
0,
None,
)?;
false
};
gpt.add_partition(
"boot",
1024 * 1024 * 1024,
@@ -108,14 +121,15 @@ pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result<OsPartitionIn
gpt.write()?;
Ok(())
Ok(efi)
})
.await
.unwrap()?;
}
.unwrap()?
};
Ok(OsPartitionInfo {
efi: Some(partition_for(&disk.logicalname, 1)),
efi: efi.then(|| partition_for(&disk.logicalname, 1)),
bios: (!efi).then(|| partition_for(&disk.logicalname, 1)),
boot: partition_for(&disk.logicalname, 2),
root: partition_for(&disk.logicalname, 3),
})

View File

@@ -27,18 +27,14 @@ pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result<OsPartitionIn
.map(|(idx, x)| (idx + 1, x))
{
if let Some(entry) = mbr.get_mut(idx) {
if entry.starting_lba >= 33556480 {
if idx < 3 {
guid_part =
Some(std::mem::replace(entry, MBRPartitionEntry::empty()))
}
break;
}
if part_info.guid.is_some() {
return Err(Error::new(
eyre!("Not enough space before embassy data"),
crate::ErrorKind::InvalidRequest,
));
if entry.starting_lba < 33556480 {
return Err(Error::new(
eyre!("Not enough space before embassy data"),
crate::ErrorKind::InvalidRequest,
));
}
guid_part = Some(std::mem::replace(entry, MBRPartitionEntry::empty()));
}
*entry = MBRPartitionEntry::empty();
}
@@ -85,6 +81,7 @@ pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result<OsPartitionIn
Ok(OsPartitionInfo {
efi: None,
bios: None,
boot: partition_for(&disk.logicalname, 1),
root: partition_for(&disk.logicalname, 2),
})

View File

@@ -10,7 +10,7 @@ use crate::context::InstallContext;
use crate::disk::mount::filesystem::bind::Bind;
use crate::disk::mount::filesystem::block_dev::BlockDev;
use crate::disk::mount::filesystem::efivarfs::EfiVarFs;
use crate::disk::mount::filesystem::ReadWrite;
use crate::disk::mount::filesystem::{MountType, ReadWrite};
use crate::disk::mount::guard::{MountGuard, TmpMountGuard};
use crate::disk::util::{DiskInfo, PartitionTable};
use crate::disk::OsPartitionInfo;
@@ -49,7 +49,7 @@ pub async fn list() -> Result<Vec<DiskInfo>, Error> {
Command::new("grub-probe-default")
.arg("-t")
.arg("disk")
.arg("/cdrom")
.arg("/run/live/medium")
.invoke(crate::ErrorKind::Grub)
.await?,
)?
@@ -93,13 +93,7 @@ pub fn partition_for(disk: impl AsRef<Path>, idx: usize) -> PathBuf {
async fn partition(disk: &mut DiskInfo, overwrite: bool) -> Result<OsPartitionInfo, Error> {
let partition_type = match (overwrite, disk.partition_table) {
(true, _) | (_, None) => {
if tokio::fs::metadata("/sys/firmware/efi").await.is_ok() {
PartitionTable::Gpt
} else {
PartitionTable::Mbr
}
}
(true, _) | (_, None) => PartitionTable::Gpt,
(_, Some(t)) => t,
};
disk.partition_table = Some(partition_type);
@@ -153,18 +147,59 @@ pub async fn execute(
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Command::new("mkfs.ext4")
if !overwrite {
if let Ok(guard) =
TmpMountGuard::mount(&BlockDev::new(part_info.root.clone()), MountType::ReadOnly).await
{
if let Err(e) = async {
// cp -r ${guard}/config /tmp/config
Command::new("cp")
.arg("-r")
.arg(guard.as_ref().join("config"))
.arg("/tmp/config.bak")
.invoke(crate::ErrorKind::Filesystem)
.await?;
if tokio::fs::metadata(guard.as_ref().join("config/upgrade"))
.await
.is_ok()
{
tokio::fs::remove_file(guard.as_ref().join("config/upgrade")).await?;
}
guard.unmount().await
}
.await
{
tracing::error!("Error recovering previous config: {e}");
tracing::debug!("{e:?}");
}
}
}
Command::new("mkfs.btrfs")
.arg("-f")
.arg(&part_info.root)
.invoke(crate::ErrorKind::DiskManagement)
.await?;
Command::new("e2label")
Command::new("btrfs")
.arg("property")
.arg("set")
.arg(&part_info.root)
.arg("label")
.arg("rootfs")
.invoke(crate::ErrorKind::DiskManagement)
.await?;
let rootfs = TmpMountGuard::mount(&BlockDev::new(&part_info.root), ReadWrite).await?;
tokio::fs::create_dir(rootfs.as_ref().join("config")).await?;
if tokio::fs::metadata("/tmp/config.bak").await.is_ok() {
Command::new("cp")
.arg("-r")
.arg("/tmp/config.bak")
.arg(rootfs.as_ref().join("config"))
.invoke(crate::ErrorKind::Filesystem)
.await?;
} else {
tokio::fs::create_dir(rootfs.as_ref().join("config")).await?;
}
tokio::fs::create_dir(rootfs.as_ref().join("next")).await?;
let current = rootfs.as_ref().join("current");
tokio::fs::create_dir(&current).await?;
@@ -188,7 +223,7 @@ pub async fn execute(
.arg("-f")
.arg("-d")
.arg(&current)
.arg("/cdrom/casper/filesystem.squashfs")
.arg("/run/live/medium/live/filesystem.squashfs")
.invoke(crate::ErrorKind::Filesystem)
.await?;
@@ -230,10 +265,16 @@ pub async fn execute(
.invoke(crate::ErrorKind::OpenSsh)
.await?;
let dev = MountGuard::mount(
&Bind::new(rootfs.as_ref()),
current.join("media/embassy/embassyfs"),
MountType::ReadOnly,
)
.await?;
let dev = MountGuard::mount(&Bind::new("/dev"), current.join("dev"), ReadWrite).await?;
let proc = MountGuard::mount(&Bind::new("/proc"), current.join("proc"), ReadWrite).await?;
let sys = MountGuard::mount(&Bind::new("/sys"), current.join("sys"), ReadWrite).await?;
let efivarfs = if let Some(efi) = &part_info.efi {
let efivarfs = if tokio::fs::metadata("/sys/firmware/efi").await.is_ok() {
Some(
MountGuard::mount(
&EfiVarFs,
@@ -246,14 +287,9 @@ pub async fn execute(
None
};
Command::new("chroot")
.arg(&current)
.arg("update-grub")
.invoke(crate::ErrorKind::Grub)
.await?;
let mut install = Command::new("chroot");
install.arg(&current).arg("grub-install");
if part_info.efi.is_none() {
if tokio::fs::metadata("/sys/firmware/efi").await.is_err() {
install.arg("--target=i386-pc");
} else {
match *ARCH {
@@ -267,6 +303,12 @@ pub async fn execute(
.invoke(crate::ErrorKind::Grub)
.await?;
Command::new("chroot")
.arg(&current)
.arg("update-grub2")
.invoke(crate::ErrorKind::Grub)
.await?;
dev.unmount(false).await?;
if let Some(efivarfs) = efivarfs {
efivarfs.unmount(false).await?;

View File

@@ -2,15 +2,17 @@ use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::ffi::{OsStr, OsString};
use std::net::Ipv4Addr;
use std::os::unix::prelude::FileTypeExt;
use std::path::{Path, PathBuf};
use std::time::Duration;
use async_stream::stream;
use bollard::container::RemoveContainerOptions;
use chrono::format::Item;
use color_eyre::eyre::eyre;
use color_eyre::Report;
use futures::future::Either as EitherFuture;
use futures::TryStreamExt;
use futures::future::{BoxFuture, Either as EitherFuture};
use futures::{FutureExt, TryStreamExt};
use helpers::{NonDetachingJoinHandle, UnixRpcClient};
use models::{Id, ImageId};
use nix::sys::signal;
@@ -18,10 +20,8 @@ use nix::unistd::Pid;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tokio::{
io::{AsyncBufRead, AsyncBufReadExt, BufReader},
time::timeout,
};
use tokio::io::{AsyncBufRead, AsyncBufReadExt, BufReader};
use tokio::time::timeout;
use tracing::instrument;
use super::ProcedureName;
@@ -68,6 +68,8 @@ pub struct DockerContainer {
pub sigterm_timeout: Option<SerdeDuration>,
#[serde(default)]
pub system: bool,
#[serde(default)]
pub gpu_acceleration: bool,
}
impl DockerContainer {
@@ -154,6 +156,8 @@ pub struct DockerProcedure {
pub sigterm_timeout: Option<SerdeDuration>,
#[serde(default)]
pub shm_size_mb: Option<usize>, // TODO: use postfix sizing? like 1k vs 1m vs 1g
#[serde(default)]
pub gpu_acceleration: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
@@ -184,6 +188,7 @@ impl DockerProcedure {
io_format: injectable.io_format,
sigterm_timeout: injectable.sigterm_timeout,
shm_size_mb: container.shm_size_mb,
gpu_acceleration: container.gpu_acceleration,
}
}
@@ -709,6 +714,32 @@ impl DockerProcedure {
res.push(OsStr::new("--shm-size").into());
res.push(OsString::from(format!("{}m", shm_size_mb)).into());
}
if self.gpu_acceleration {
fn get_devices<'a>(
path: &'a Path,
res: &'a mut Vec<PathBuf>,
) -> BoxFuture<'a, Result<(), Error>> {
async move {
let mut read_dir = tokio::fs::read_dir(path).await?;
while let Some(entry) = read_dir.next_entry().await? {
let fty = entry.metadata().await?.file_type();
if fty.is_block_device() || fty.is_char_device() {
res.push(entry.path());
} else if fty.is_dir() {
get_devices(&*entry.path(), res).await?;
}
}
Ok(())
}
.boxed()
}
let mut devices = Vec::new();
get_devices(Path::new("/dev/dri"), &mut devices).await?;
for device in devices {
res.push(OsStr::new("--device").into());
res.push(OsString::from(device).into());
}
}
res.push(OsStr::new("--interactive").into());
res.push(OsStr::new("--log-driver=journald").into());
res.push(OsStr::new("--entrypoint").into());

View File

@@ -690,3 +690,49 @@ async fn js_rsync() {
.unwrap()
.unwrap();
}
#[tokio::test]
async fn js_disk_usage() {
let js_action = JsProcedure { args: vec![] };
let path: PathBuf = "test/js_action_execute/"
.parse::<PathBuf>()
.unwrap()
.canonicalize()
.unwrap();
let package_id = "test-package".parse().unwrap();
let package_version: Version = "0.3.0.3".parse().unwrap();
let name = ProcedureName::Action("test-disk-usage".parse().unwrap());
let volumes: Volumes = serde_json::from_value(serde_json::json!({
"main": {
"type": "data"
},
"compat": {
"type": "assets"
},
"filebrowser" :{
"package-id": "filebrowser",
"path": "data",
"readonly": true,
"type": "pointer",
"volume-id": "main",
}
}))
.unwrap();
let input: Option<serde_json::Value> = None;
let timeout = Some(Duration::from_secs(10));
dbg!(js_action
.execute::<serde_json::Value, serde_json::Value>(
&path,
&package_id,
&package_version,
name,
&volumes,
input,
timeout,
ProcessGroupId(0),
None,
)
.await
.unwrap()
.unwrap());
}

View File

@@ -135,7 +135,7 @@ pub async fn attach(
crate::disk::main::export(&*guid, &ctx.datadir).await?;
return Err(Error::new(
eyre!(
"Errors were corrected with your disk, but the Embassy must be restarted in order to proceed"
"Errors were corrected with your disk, but the server must be restarted in order to proceed"
),
ErrorKind::DiskManagement,
));
@@ -294,7 +294,7 @@ pub async fn execute(
}));
}
Err(e) => {
tracing::error!("Error Setting Up Embassy: {}", e);
tracing::error!("Error Setting Up Server: {}", e);
tracing::debug!("{:?}", e);
*ctx.setup_status.write().await = Some(Err(e.into()));
}

View File

@@ -8,7 +8,7 @@ use crate::disk::main::export;
use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH};
use crate::sound::SHUTDOWN;
use crate::util::{display_none, Invoke};
use crate::{Error, ErrorKind, IS_RASPBERRY_PI};
use crate::{Error, ErrorKind, OS_ARCH};
#[derive(Debug, Clone)]
pub struct Shutdown {
@@ -58,7 +58,7 @@ impl Shutdown {
tracing::debug!("{:?}", e);
}
}
if !*IS_RASPBERRY_PI || self.restart {
if OS_ARCH != "raspberrypi" || self.restart {
if let Err(e) = SHUTDOWN.play().await {
tracing::error!("Error Playing Shutdown Song: {}", e);
tracing::debug!("{:?}", e);
@@ -66,7 +66,7 @@ impl Shutdown {
}
});
drop(rt);
if *IS_RASPBERRY_PI {
if OS_ARCH == "raspberrypi" {
if !self.restart {
std::fs::write(STANDBY_MODE_PATH, "").unwrap();
Command::new("sync").spawn().unwrap().wait().unwrap();

View File

@@ -6,6 +6,7 @@ use futures::FutureExt;
use rpc_toolkit::command;
use rpc_toolkit::yajrc::RpcError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use tokio::process::Command;
use tokio::sync::broadcast::Receiver;
use tokio::sync::RwLock;
use tracing::instrument;
@@ -17,12 +18,72 @@ use crate::logs::{
LogResponse, LogSource,
};
use crate::shutdown::Shutdown;
use crate::util::display_none;
use crate::util::serde::{display_serializable, IoFormat};
use crate::util::{display_none, Invoke};
use crate::{Error, ErrorKind, ResultExt};
pub const SYSTEMD_UNIT: &'static str = "embassyd";
#[command(subcommands(zram))]
pub async fn experimental() -> Result<(), Error> {
Ok(())
}
pub async fn enable_zram() -> Result<(), Error> {
let mem_info = get_mem_info().await?;
Command::new("modprobe")
.arg("zram")
.invoke(ErrorKind::Zram)
.await?;
tokio::fs::write("/sys/block/zram0/comp_algorithm", "lz4")
.await
.with_kind(ErrorKind::Zram)?;
tokio::fs::write(
"/sys/block/zram0/disksize",
format!("{}M", mem_info.total.0 as u64 / 4),
)
.await
.with_kind(ErrorKind::Zram)?;
Command::new("mkswap")
.arg("/dev/zram0")
.invoke(ErrorKind::Zram)
.await?;
Command::new("swapon")
.arg("-p")
.arg("5")
.arg("/dev/zram0")
.invoke(ErrorKind::Zram)
.await?;
Ok(())
}
#[command(display(display_none))]
pub async fn zram(#[context] ctx: RpcContext, #[arg] enable: bool) -> Result<(), Error> {
let mut db = ctx.db.handle();
let mut zram = crate::db::DatabaseModel::new()
.server_info()
.zram()
.get_mut(&mut db)
.await?;
if enable == *zram {
return Ok(());
}
*zram = enable;
if enable {
enable_zram().await?;
} else {
Command::new("swapoff")
.arg("/dev/zram0")
.invoke(ErrorKind::Zram)
.await?;
tokio::fs::write("/sys/block/zram0/reset", "1")
.await
.with_kind(ErrorKind::Zram)?;
}
zram.save(&mut db).await?;
Ok(())
}
#[command]
pub async fn time() -> Result<String, Error> {
Ok(Utc::now().to_rfc3339())
@@ -512,13 +573,30 @@ async fn launch_disk_task(
#[instrument(skip_all)]
async fn get_temp() -> Result<Celsius, Error> {
let temp_file = "/sys/class/thermal/thermal_zone0/temp";
let milli = tokio::fs::read_to_string(temp_file)
.await
.with_ctx(|_| (crate::ErrorKind::Filesystem, temp_file))?
.trim()
.parse::<f64>()?;
Ok(Celsius(milli / 1000.0))
let temp = serde_json::from_slice::<serde_json::Value>(
&Command::new("sensors")
.arg("-j")
.invoke(ErrorKind::Filesystem)
.await?,
)
.with_kind(ErrorKind::Deserialization)?
.as_object()
.into_iter()
.flatten()
.flat_map(|(_, v)| v.as_object())
.flatten()
.flat_map(|(_, v)| v.as_object())
.flatten()
.filter_map(|(k, v)| {
if k.ends_with("_input") {
v.as_f64()
} else {
None
}
})
.reduce(f64::max)
.ok_or_else(|| Error::new(eyre!("No temperatures available"), ErrorKind::Filesystem))?;
Ok(Celsius(temp))
}
#[derive(Debug, Clone)]
@@ -681,7 +759,7 @@ async fn get_mem_info() -> Result<MetricsMemory, Error> {
let swap_total = MebiBytes(swap_total_k as f64 / 1024.0);
let swap_free = MebiBytes(swap_free_k as f64 / 1024.0);
let swap_used = MebiBytes((swap_total_k - swap_free_k) as f64 / 1024.0);
let percentage_used = Percentage(used.0 / total.0 * 100.0);
let percentage_used = Percentage((total.0 - available.0) / total.0 * 100.0);
Ok(MetricsMemory {
percentage_used,
total,

View File

@@ -26,7 +26,7 @@ use crate::sound::{
use crate::update::latest_information::LatestInformation;
use crate::util::Invoke;
use crate::version::{Current, VersionT};
use crate::{Error, ErrorKind, ResultExt, IS_RASPBERRY_PI};
use crate::{Error, ErrorKind, ResultExt, OS_ARCH};
mod latest_information;
@@ -81,16 +81,11 @@ async fn maybe_do_update(
marketplace_url: Url,
) -> Result<Option<Arc<Revision>>, Error> {
let mut db = ctx.db.handle();
let arch = if *IS_RASPBERRY_PI {
"raspberrypi"
} else {
*crate::ARCH
};
let latest_version: Version = reqwest::get(format!(
"{}/eos/v0/latest?eos-version={}&arch={}",
marketplace_url,
Current::new().semver(),
arch,
OS_ARCH,
))
.await
.with_kind(ErrorKind::Network)?
@@ -241,12 +236,7 @@ impl EosUrl {
.host_str()
.ok_or_else(|| Error::new(eyre!("Could not get host of base"), ErrorKind::ParseUrl))?;
let version: &Version = &self.version;
let arch = if *IS_RASPBERRY_PI {
"raspberrypi"
} else {
*crate::ARCH
};
Ok(format!("{host}::{version}/{arch}/")
Ok(format!("{host}::{version}/{OS_ARCH}/")
.parse()
.map_err(|_| Error::new(eyre!("Could not parse path"), ErrorKind::ParseUrl))?)
}
@@ -312,7 +302,7 @@ async fn sync_boot() -> Result<(), Error> {
.await?
.wait()
.await?;
if !*IS_RASPBERRY_PI {
if OS_ARCH != "raspberrypi" {
let dev_mnt =
MountGuard::mount(&Bind::new("/dev"), "/media/embassy/next/dev", ReadWrite).await?;
let sys_mnt =
@@ -323,7 +313,7 @@ async fn sync_boot() -> Result<(), Error> {
MountGuard::mount(&Bind::new("/boot"), "/media/embassy/next/boot", ReadWrite).await?;
Command::new("chroot")
.arg("/media/embassy/next")
.arg("update-grub")
.arg("update-grub2")
.invoke(ErrorKind::MigrationFailed)
.await?;
boot_mnt.unmount(false).await?;

View File

@@ -1,11 +1,13 @@
use std::future::Future;
use std::io::Cursor;
use std::os::unix::prelude::MetadataExt;
use std::path::Path;
use std::task::Poll;
use futures::future::{BoxFuture, Fuse};
use futures::{AsyncSeek, FutureExt, TryStreamExt};
use helpers::NonDetachingJoinHandle;
use nix::unistd::{Gid, Uid};
use tokio::io::{
duplex, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, DuplexStream, ReadBuf, WriteHalf,
};
@@ -416,3 +418,120 @@ impl<T: AsyncWrite> AsyncWrite for BackTrackingReader<T> {
self.project().reader.poll_write_vectored(cx, bufs)
}
}
pub fn dir_copy<'a, P0: AsRef<Path> + 'a + Send + Sync, P1: AsRef<Path> + 'a + Send + Sync>(
src: P0,
dst: P1,
) -> BoxFuture<'a, Result<(), crate::Error>> {
async move {
let m = tokio::fs::metadata(&src).await?;
let dst_path = dst.as_ref();
tokio::fs::create_dir_all(&dst_path).await.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
format!("mkdir {}", dst_path.display()),
)
})?;
tokio::fs::set_permissions(&dst_path, m.permissions())
.await
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
format!("chmod {}", dst_path.display()),
)
})?;
let tmp_dst_path = dst_path.to_owned();
tokio::task::spawn_blocking(move || {
nix::unistd::chown(
&tmp_dst_path,
Some(Uid::from_raw(m.uid())),
Some(Gid::from_raw(m.gid())),
)
})
.await
.with_kind(crate::ErrorKind::Unknown)?
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
format!("chown {}", dst_path.display()),
)
})?;
tokio_stream::wrappers::ReadDirStream::new(tokio::fs::read_dir(src.as_ref()).await?)
.map_err(|e| crate::Error::new(e, crate::ErrorKind::Filesystem))
.try_for_each(|e| async move {
let m = e.metadata().await?;
let src_path = e.path();
let dst_path = dst_path.join(e.file_name());
if m.is_file() {
let len = m.len();
let mut dst_file =
&mut tokio::fs::File::create(&dst_path).await.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
format!("create {}", dst_path.display()),
)
})?;
tokio::io::copy(
&mut tokio::fs::File::open(&src_path).await.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
format!("open {}", src_path.display()),
)
})?,
&mut dst_file,
)
.await
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
format!("cp {} -> {}", src_path.display(), dst_path.display()),
)
})?;
dst_file.flush().await?;
dst_file.shutdown().await?;
dst_file.sync_all().await?;
drop(dst_file);
let tmp_dst_path = dst_path.clone();
tokio::task::spawn_blocking(move || {
nix::unistd::chown(
&tmp_dst_path,
Some(Uid::from_raw(m.uid())),
Some(Gid::from_raw(m.gid())),
)
})
.await
.with_kind(crate::ErrorKind::Unknown)?
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
format!("chown {}", dst_path.display()),
)
})?;
} else if m.is_dir() {
dir_copy(src_path, dst_path).await?;
} else if m.file_type().is_symlink() {
tokio::fs::symlink(
tokio::fs::read_link(&src_path).await.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
format!("readlink {}", src_path.display()),
)
})?,
&dst_path,
)
.await
.with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
format!("cp -P {} -> {}", src_path.display(), dst_path.display()),
)
})?;
// Do not set permissions (see https://unix.stackexchange.com/questions/87200/change-permissions-for-a-symbolic-link)
}
Ok(())
})
.await?;
Ok(())
}
.boxed()
}

View File

@@ -58,7 +58,12 @@ impl Invoke for tokio::process::Command {
res.status.success(),
error_kind,
"{}",
std::str::from_utf8(&res.stderr).unwrap_or("Unknown Error")
Some(&res.stderr)
.filter(|a| !a.is_empty())
.or(Some(&res.stdout))
.filter(|a| !a.is_empty())
.and_then(|a| std::str::from_utf8(a).ok())
.unwrap_or(&format!("Unknown Error ({})", res.status))
);
Ok(res.stdout)
}

View File

@@ -21,8 +21,10 @@ mod v0_3_2_1;
mod v0_3_3;
mod v0_3_4;
mod v0_3_4_1;
mod v0_3_4_2;
mod v0_3_4_3;
pub type Current = v0_3_4_1::Version;
pub type Current = v0_3_4_3::Version;
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(untagged)]
@@ -39,6 +41,8 @@ enum Version {
V0_3_3(Wrapper<v0_3_3::Version>),
V0_3_4(Wrapper<v0_3_4::Version>),
V0_3_4_1(Wrapper<v0_3_4_1::Version>),
V0_3_4_2(Wrapper<v0_3_4_2::Version>),
V0_3_4_3(Wrapper<v0_3_4_3::Version>),
Other(emver::Version),
}
@@ -66,6 +70,8 @@ impl Version {
Version::V0_3_3(Wrapper(x)) => x.semver(),
Version::V0_3_4(Wrapper(x)) => x.semver(),
Version::V0_3_4_1(Wrapper(x)) => x.semver(),
Version::V0_3_4_2(Wrapper(x)) => x.semver(),
Version::V0_3_4_3(Wrapper(x)) => x.semver(),
Version::Other(x) => x.clone(),
}
}
@@ -251,6 +257,14 @@ pub async fn init<Db: DbHandle>(
v.0.migrate_to(&Current::new(), db, secrets, receipts)
.await?
}
Version::V0_3_4_2(v) => {
v.0.migrate_to(&Current::new(), db, secrets, receipts)
.await?
}
Version::V0_3_4_3(v) => {
v.0.migrate_to(&Current::new(), db, secrets, receipts)
.await?
}
Version::Other(_) => {
return Err(Error::new(
eyre!("Cannot downgrade"),
@@ -295,6 +309,8 @@ mod tests {
Just(Version::V0_3_3(Wrapper(v0_3_3::Version::new()))),
Just(Version::V0_3_4(Wrapper(v0_3_4::Version::new()))),
Just(Version::V0_3_4_1(Wrapper(v0_3_4_1::Version::new()))),
Just(Version::V0_3_4_2(Wrapper(v0_3_4_2::Version::new()))),
Just(Version::V0_3_4_3(Wrapper(v0_3_4_3::Version::new()))),
em_version().prop_map(Version::Other),
]
}

View File

@@ -5,11 +5,10 @@ use openssl::hash::MessageDigest;
use serde_json::{json, Value};
use ssh_key::public::Ed25519PublicKey;
use crate::account::AccountInfo;
use crate::hostname::{generate_hostname, sync_hostname, Hostname};
use super::v0_3_0::V0_3_0_COMPAT;
use super::*;
use crate::account::AccountInfo;
use crate::hostname::{generate_hostname, sync_hostname, Hostname};
const V0_3_4: emver::Version = emver::Version::new(0, 3, 4, 0);
@@ -79,7 +78,7 @@ impl VersionT for Version {
.unwrap_or_else(generate_hostname);
account.server_id = server_info.id;
account.save(secrets).await?;
sync_hostname(&account).await?;
sync_hostname(&account.hostname).await?;
let parsed_url = Some(COMMUNITY_URL.parse().unwrap());
let mut ui = crate::db::DatabaseModel::new().ui().get_mut(db).await?;

View File

@@ -0,0 +1,30 @@
use async_trait::async_trait;
use emver::VersionRange;
use super::v0_3_0::V0_3_0_COMPAT;
use super::*;
const V0_3_4_2: emver::Version = emver::Version::new(0, 3, 4, 2);
#[derive(Clone, Debug)]
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_3_4_1::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> emver::Version {
V0_3_4_2
}
fn compat(&self) -> &'static VersionRange {
&*V0_3_0_COMPAT
}
async fn up<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
Ok(())
}
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -0,0 +1,36 @@
use async_trait::async_trait;
use emver::VersionRange;
use super::v0_3_0::V0_3_0_COMPAT;
use super::*;
const V0_3_4_3: emver::Version = emver::Version::new(0, 3, 4, 3);
#[derive(Clone, Debug)]
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_3_4_2::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> emver::Version {
V0_3_4_3
}
fn compat(&self) -> &'static VersionRange {
&*V0_3_0_COMPAT
}
async fn up<Db: DbHandle>(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
crate::db::DatabaseModel::new()
.server_info()
.get_mut(db)
.await?
.save(db)
.await?;
Ok(())
}
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1039,4 +1039,9 @@ export const action = {
}
},
async "test-disk-usage"(effects, _input) {
const usage = await effects.diskUsage()
return usage
}
};

View File

@@ -1,4 +1,4 @@
# Building Embassy OS
# Building StartOS
⚠️ The commands given assume a Debian or Ubuntu-based environment. _Building in
a VM is NOT yet supported_ ⚠️
@@ -42,15 +42,15 @@ a VM is NOT yet supported_ ⚠️
2. Clone the latest repo with required submodules
> :information_source: You chan check latest available version
> [here](https://github.com/Start9Labs/embassy-os/releases)
> [here](https://github.com/Start9Labs/start-os/releases)
```
git clone --recursive https://github.com/Start9Labs/embassy-os.git --branch latest
git clone --recursive https://github.com/Start9Labs/start-os.git --branch latest
```
## Build Raspberry Pi Image
```
cd embassy-os
cd start-os
make embassyos-raspi.img ARCH=aarch64
```
@@ -62,7 +62,7 @@ We recommend [Balena Etcher](https://www.balena.io/etcher/)
## Setup
Visit http://embassy.local from any web browser - We recommend
Visit http://start.local from any web browser - We recommend
[Firefox](https://www.mozilla.org/firefox/browsers)
Enter your product key. This is generated during the build process and can be
@@ -70,11 +70,11 @@ found in `product_key.txt`, located in the root directory.
## Troubleshooting
1. I just flashed my SD card, fired up my Embassy, bootup sounds and all, but my
browser is saying "Unable to connect" with embassy.local.
1. I just flashed my SD card, fired up StartOS, bootup sounds and all, but my
browser is saying "Unable to connect" with start.local.
- Try doing a hard refresh on your browser, or opening the url in a
private/incognito window. If you've ran an instance of Embassy before,
private/incognito window. If you've ran an instance of StartOS before,
sometimes you can have a stale cache that will block you from navigating to
the page.
@@ -91,14 +91,14 @@ found in `product_key.txt`, located in the root directory.
- Find the IP of your device
- Run `nc <ip> 8080` and it will print the logs
4. I need to ssh into my Embassy to fix something, but I cannot get to the
4. I need to ssh into my server to fix something, but I cannot get to the
console to add ssh keys normally.
- During the Build step, instead of running just
`make embassyos-raspi.img ARCH=aarch64` run
`ENVIRONMENT=dev make embassyos-raspi.img ARCH=aarch64`. Flash like normal,
and insert into your Embassy. Boot up your Embassy, and on another computer on
the same network, ssh into the Embassy with the username `start9` password
and insert into your server. Boot up StartOS, then on another computer on
the same network, ssh into the the server with the username `start9` password
`embassy`.
4. I need to reset my password, how can I do that?

View File

@@ -1,35 +1,52 @@
tor
avahi-daemon
avahi-utils
iotop
bash-completion
beep
bmon
lvm2
htop
cryptsetup
exfat-utils
sqlite3
wireless-tools
net-tools
ecryptfs-utils
btrfs-progs
ca-certificates
cifs-utils
samba-common-bin
network-manager
vim
jq
ncdu
postgresql
pgloader
openssh-server
containerd.io
curl
cryptsetup
docker-ce
docker-ce-cli
containerd.io
docker-compose-plugin
beep
dosfstools
e2fsprogs
ecryptfs-utils
exfatprogs
grub-common
htop
httpdirfs
iotop
iw
squashfs-tools
rsync
systemd-timesyncd
jq
libavahi-client3
lm-sensors
lvm2
magic-wormhole
man-db
ncdu
net-tools
network-manager
nvme-cli
nyx
bash-completion
openssh-server
postgresql
psmisc
qemu-guest-agent
rsync
samba-common-bin
smartmontools
sqlite3
squashfs-tools
sudo
systemd
systemd-resolved
systemd-sysv
systemd-timesyncd
tor
util-linux
vim
wireless-tools

View File

@@ -2,14 +2,20 @@
printf "\n"
printf "Welcome to\n"
cat << "ASCII"
| ,---.,---.
,---.,-.-.|---.,---.,---.,---., .| |`---.
|---'| | || |,---|`---.`---.| || | |
`---'` ' '`---'`---^`---'`---'`---|`---'`---'
`---'
╭ ━ ━ ━ ╮ ╭ ╮ ╭ ╮ ╭ ━ ━ ━ ┳ ━ ━ ━ ╮
┃ ╭ ━ ╮ ┣ ╯ ╰ ╮ ╭ ╯ ╰ ┫ ╭ ━ ╮ ┃ ╭ ━ ╮ ┃
┃ ╰ ━ ━ ╋ ╮ ╭ ╋ ━ ━ ┳ ┻ ╮ ╭ ┫ ┃ ┃ ┃ ╰ ━ ━ ╮
╰ ━ ━ ╮ ┃ ┃ ┃ ┃ ╭ ╮ ┃ ╭ ┫ ┃ ┃ ┃ ┃ ┣ ━ ━ ╮ ┃
┃ ╰ ━ ╯ ┃ ┃ ╰ ┫ ╭ ╮ ┃ ┃ ┃ ╰ ┫ ╰ ━ ╯ ┃ ╰ ━ ╯ ┃
╰ ━ ━ ━ ╯ ╰ ━ ┻ ╯ ╰ ┻ ╯ ╰ ━ ┻ ━ ━ ━ ┻ ━ ━ ━ ╯
ASCII
printf " %s (%s %s)\n" "$(uname -o)" "$(uname -r)" "$(uname -m)"
printf " $(embassy-cli --version | sed 's/Embassy CLI /embassyOS v/g') - $(embassy-cli git-info)\n"
printf " $(embassy-cli --version | sed 's/Embassy CLI /StartOS v/g') - $(embassy-cli git-info)"
if [ -n "$(cat /usr/lib/embassy/ENVIRONMENT.txt)" ]; then
printf " ~ $(cat /usr/lib/embassy/ENVIRONMENT.txt)\n"
else
printf "\n"
fi
printf "\n"
printf " * Documentation: https://start9.com\n"

View File

@@ -20,16 +20,8 @@ mount --bind /sys /media/embassy/next/sys
mount --bind /proc /media/embassy/next/proc
mount --bind /boot /media/embassy/next/boot
rm /media/embassy/next/usr/local/bin/apt
rm /media/embassy/next/usr/local/bin/apt-get
rm /media/embassy/next/usr/local/bin/aptitude
chroot /media/embassy/next $@
ln -sf /usr/lib/embassy/scripts/fake-apt /media/embassy/next/usr/local/bin/apt
ln -sf /usr/lib/embassy/scripts/fake-apt /media/embassy/next/usr/local/bin/apt-get
ln -sf /usr/lib/embassy/scripts/fake-apt /media/embassy/next/usr/local/bin/aptitude
umount /media/embassy/next/run
umount /media/embassy/next/dev
umount /media/embassy/next/sys

View File

@@ -31,6 +31,8 @@ local_mount_root()
modprobe ${FSTYPE}
checkfs ${ROOT} root "${FSTYPE}"
ROOTFLAGS="$(echo "${ROOTFLAGS}" | sed 's/subvol=\(next\|current\)//' | sed 's/^-o *$//')"
if [ "${FSTYPE}" != "unknown" ]; then
mount -t ${FSTYPE} ${ROOTFLAGS} ${ROOT} ${rootmnt}
else

View File

@@ -10,8 +10,8 @@ set -e
cat > /home/start9/kiosk.sh << 'EOF'
#!/bin/sh
PROFILE=$(mktemp -d)
if [ -f /usr/local/share/ca-certificates/embassy-root-ca.crt ]; then
certutil -A -n "Embassy Local Root CA" -t "TCu,Cuw,Tuw" -i /usr/local/share/ca-certificates/embassy-root-ca.crt -d $PROFILE
if [ -f /usr/local/share/ca-certificates/startos-root-ca.crt ]; then
certutil -A -n "StartOS Local Root CA" -t "TCu,Cuw,Tuw" -i /usr/local/share/ca-certificates/startos-root-ca.crt -d $PROFILE
fi
cat >> $PROFILE/prefs.js << EOT
user_pref("network.proxy.autoconfig_url", "file:///usr/lib/embassy/proxy.pac");

View File

@@ -1,7 +1,7 @@
#!/bin/bash
>&2 echo 'THIS IS NOT A STANDARD DEBIAN SYSTEM'
>&2 echo 'USING apt COULD CAUSE IRREPARABLE DAMAGE TO YOUR EMBASSY'
>&2 echo 'USING apt COULD CAUSE IRREPARABLE DAMAGE TO YOUR START9 SERVER'
>&2 echo 'PLEASE TURN BACK NOW!!!'
if [ "$1" == "upgrade" ] && [ "$(whoami)" == "root" ]; then
>&2 echo 'IF YOU THINK RUNNING "sudo apt upgrade" IS A REASONABLE THING TO DO ON THIS SYSTEM, YOU PROBABLY SHOULDN'"'"'T BE ON THE COMMAND LINE.'

View File

@@ -3,6 +3,6 @@
for mozilladir in $(find /home -name ".mozilla"); do
for certDB in $(find ${mozilladir} -name "cert9.db"); do
certDir=$(dirname ${certDB});
certutil -A -n "Embassy Local Root CA" -t "TCu,Cuw,Tuw" -i /usr/local/share/ca-certificates/embassy-root-ca.crt -d ${certDir}
certutil -A -n "StartOS Local Root CA" -t "TCu,Cuw,Tuw" -i /usr/local/share/ca-certificates/startos-root-ca.crt -d ${certDir}
done
done

View File

@@ -3,30 +3,10 @@
ARGS=
for ARG in $@; do
if [ "${ARG%%[!/]*}" = "/" ]; then
OPTIONS=
path="$ARG"
while true; do
if FSTYPE=$( findmnt -n -o FSTYPE "$path" ); then
if [ "$FSTYPE" = "overlay" ]; then
OPTIONS=$(findmnt -n -o OPTIONS "$path")
break
else
break
fi
fi
if [ "$path" = "/" ]; then break; fi
path=$(dirname "$path")
done
if LOWERDIR=$(echo "$OPTIONS" | grep -m 1 -oP 'lowerdir=\K[^,]+'); then
#echo "[DEBUG] Overlay filesystem detected ${ARG} --> ${LOWERDIR}${ARG%*/}" 1>&2
ARG=/media/embassy/embassyfs"${ARG%*/}"
if [ -d "/media/embassy/embassyfs" ] && [ "$ARG" = "/" ]; then
ARG=/media/embassy/embassyfs
fi
fi
ARGS="$ARGS $ARG"
ARGS="$ARGS $ARG"
done
grub-probe-default $ARGS

View File

@@ -1,120 +0,0 @@
#!/bin/bash
set -e
function partition_for () {
if [[ "$1" =~ [0-9]+$ ]]; then
echo "$1p$2"
else
echo "$1$2"
fi
}
OSDISK=$1
if [ -z "$OSDISK" ]; then
>&2 echo "usage: $0 <TARGET DISK>"
exit 1
fi
WIFI_IFACE=
for IFACE in $(ls /sys/class/net); do
if [ -d /sys/class/net/$IFACE/wireless ]; then
WIFI_IFACE=$IFACE
break
fi
done
ETH_IFACE=
for IFACE in $(ls /sys/class/net); do
if ! [ -d /sys/class/net/$IFACE/wireless ] && [ -d /sys/class/net/$IFACE/device ]; then
ETH_IFACE=$IFACE
break
fi
done
if [ -z "$ETH_IFACE" ]; then
>&2 echo 'Could not detect ethernet interface'
exit 1
fi
(
echo o # MBR
echo n # New Partition
echo p # Primary
echo 1 # Index #1
echo # Default Starting Position
echo '+1G' # 1GB
echo t # Change Type
echo 0b # W95 FAT32
echo a # Set Bootable
echo n # New Partition
echo p # Primary
echo 2 # Index #2
echo # Default Starting Position
echo '+15G' # 15GB
echo n # New Partition
echo p # Primary
echo 3 # Index #3
echo # Default Starting Position
echo # Use Full Remaining
echo t # Change Type
echo 3 # (Still Index #3)
echo 8e # Linux LVM
echo w # Write Changes
) | fdisk $OSDISK
BOOTPART=`partition_for $OSDISK 1`
ROOTPART=`partition_for $OSDISK 2`
mkfs.vfat $BOOTPART
fatlabel $BOOTPART boot
mkfs.ext4 $ROOTPART
e2label $ROOTPART rootfs
mount $ROOTPART /mnt
mkdir /mnt/config
mkdir /mnt/current
mkdir /mnt/next
mkdir /mnt/current/boot
mount $BOOTPART /mnt/current/boot
unsquashfs -f -d /mnt/current /cdrom/casper/filesystem.squashfs
cat > /mnt/config/config.yaml << EOF
os-partitions:
boot: $BOOTPART
root: $ROOTPART
ethernet-interface: $ETH_IFACE
EOF
if [ -n "$WIFI_IFACE" ]; then
echo "wifi-interface: $WIFI_IFACE" >> /mnt/config/config.yaml
fi
# gen fstab
cat > /mnt/current/etc/fstab << EOF
$BOOTPART /boot vfat defaults 0 2
$ROOTPART / ext4 defaults 0 1
EOF
# gen machine-id
chroot /mnt/current systemd-machine-id-setup
# gen ssh host keys
chroot /mnt/current ssh-keygen -A
mount --bind /dev /mnt/current/dev
mount --bind /sys /mnt/current/sys
mount --bind /proc /mnt/current/proc
chroot /mnt/current update-grub
chroot /mnt/current grub-install $OSDISK
umount /mnt/current/dev
umount /mnt/current/sys
umount /mnt/current/proc
umount /mnt/current/boot
umount /mnt

View File

@@ -29,11 +29,15 @@ ln -s /usr/share/zoneinfo/Etc/UTC /etc/localtime
# switch to systemd-resolved & network-manager
echo "#" > /etc/network/interfaces
if ! [ -f /etc/resolv.conf ]; then
rm -f /etc/resolv.conf
echo "nameserver 8.8.8.8" > /etc/resolv.conf # Google DNS Fallback
fi
if ! [ -f /run/systemd/resolve/stub-resolv.conf ]; then
mkdir -p /run/systemd/resolve
cp /etc/resolv.conf /run/systemd/resolve/stub-resolv.conf
fi
ln -rsf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
cat << EOF > /etc/NetworkManager/NetworkManager.conf
[main]
plugins=ifupdown,keyfile
@@ -44,6 +48,7 @@ managed=true
EOF
$SYSTEMCTL enable systemd-resolved.service
$SYSTEMCTL enable systemd-networkd-wait-online.service
$SYSTEMCTL enable ssh.service
$SYSTEMCTL disable wpa_supplicant.service
$SYSTEMCTL disable docker.service
@@ -63,6 +68,7 @@ if which gsettings > /dev/null; then
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-timeout '0'
fi
sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config
sed -i 's/Restart=on-failure/Restart=always/g' /lib/systemd/system/tor@default.service
sed -i 's/ExecStart=\/usr\/bin\/dockerd/ExecStart=\/usr\/bin\/dockerd --exec-opt native.cgroupdriver=systemd/g' /lib/systemd/system/docker.service
sed -i '/\(^\|#\)entries-per-entry-group-max=/c\entries-per-entry-group-max=128' /etc/avahi/avahi-daemon.conf
@@ -112,7 +118,3 @@ rm -f /etc/motd
ln -sf /usr/lib/embassy/motd /etc/update-motd.d/00-embassy
chmod -x /etc/update-motd.d/*
chmod +x /etc/update-motd.d/00-embassy
ln -sf /usr/lib/embassy/scripts/fake-apt /usr/local/bin/apt
ln -sf /usr/lib/embassy/scripts/fake-apt /usr/local/bin/apt-get
ln -sf /usr/lib/embassy/scripts/fake-apt /usr/local/bin/aptitude

View File

@@ -1,12 +0,0 @@
[Unit]
Description=Boot process for system initialization.
After=network-online.target systemd-time-wait-sync.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/033-upgrade.sh
RemainAfterExit=true
StandardOutput=append:/var/log/initialization.log
[Install]
WantedBy=multi-user.target

View File

@@ -1,88 +0,0 @@
#!/bin/bash
set -e
(
while true; do
beep -r 2 -l 80 -d 20
sleep 60
done
) &
if grep 'cb15ae4d-03' /boot/cmdline.txt; then
echo Transfer files across
e2fsck -f -y /dev/mmcblk0p4
while ! resize2fs /dev/mmcblk0p4; do
e2fsck -f -y /dev/mmcblk0p4
done
mkdir -p /media/origin
mkdir -p /media/dest
mount -r /dev/mmcblk0p3 /media/origin
mount -w /dev/mmcblk0p4 /media/dest
rsync -acvAXUH --info=progress2 --delete --force /media/origin/ /media/dest/
umount /media/origin
umount /media/dest
rm -rf /media/origin
rm -rf /media/dest
echo Setting up boot to use other partition
sed -i 's/PARTUUID=cb15ae4d-03/PARTUUID=cb15ae4d-04/g' /boot/cmdline.txt
sync
reboot
fi
mkdir -p /media/root-rw
mkfs.ext4 /dev/mmcblk0p3
mount /dev/mmcblk0p3 /media/root-rw
mkdir -p /embassy-os
mount /dev/mmcblk0p2 /embassy-os
mkdir -p /media/root-rw/config
mkdir -p /media/root-rw/current
mkdir -p /media/root-rw/next
rsync -acvAXUH --info=progress2 /embassy-os/ /media/root-rw/config/
rsync -acvAXUH --info=progress2 /update/ /media/root-rw/current/
rsync -acvAXUH --info=progress2 /media/root-rw/current/boot/ /boot/
cp /etc/machine-id /media/root-rw/current/etc/machine-id
cp /etc/ssh/ssh_host_rsa_key /media/root-rw/current/etc/ssh/ssh_host_rsa_key
cp /etc/ssh/ssh_host_rsa_key.pub /media/root-rw/current/etc/ssh/ssh_host_rsa_key.pub
cp /etc/ssh/ssh_host_ecdsa_key /media/root-rw/current/etc/ssh/ssh_host_ecdsa_key
cp /etc/ssh/ssh_host_ecdsa_key.pub /media/root-rw/current/etc/ssh/ssh_host_ecdsa_key.pub
cp /etc/ssh/ssh_host_ed25519_key /media/root-rw/current/etc/ssh/ssh_host_ed25519_key
cp /etc/ssh/ssh_host_ed25519_key.pub /media/root-rw/current/etc/ssh/ssh_host_ed25519_key.pub
sync
umount /embassy-os
umount /media/root-rw
fatlabel /dev/mmcblk0p1 boot
e2label /dev/mmcblk0p3 rootfs
(
echo d
echo 1
echo d
echo 2
echo n
echo p
echo 1
echo
echo
echo d
echo 3
echo d
echo 4
echo n
echo p
echo 2
echo
echo
echo t
echo 1
echo c
echo w
) | fdisk /dev/mmcblk0
reboot

View File

@@ -1,18 +0,0 @@
#!/bin/bash
function flatline {
echo -n "0" > /sys/class/pwm/pwmchip0/export
sleep 0.5
echo -n "2272727" > /sys/class/pwm/pwmchip0/pwm0/period
echo -n "1136364" > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo -n "1" > /sys/class/pwm/pwmchip0/pwm0/enable
sleep 30
echo -n "0" > /sys/class/pwm/pwmchip0/pwm0/enable
}
initialization.sh
STATUS=$?
if [ $STATUS -ne 0 ]; then
flatline
exit $STATUS
fi

View File

@@ -1,12 +0,0 @@
[Unit]
Description=Boot process for system initialization.
After=network-online.target systemd-time-wait-sync.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/init-with-sound.sh
RemainAfterExit=true
StandardOutput=append:/var/log/initialization.log
[Install]
WantedBy=multi-user.target

View File

@@ -1,82 +0,0 @@
#!/bin/bash
set -e
# introduce start9 username and embassy as default password
if ! awk -F: '{ print $1 }' /etc/passwd | grep start9
then
usermod -l start9 -d /home/start9 -m pi
groupmod --new-name start9 pi
echo start9:embassy | chpasswd
fi
START=$(date +%s)
while ! ping -q -w 1 -c 1 `ip r | grep default | cut -d ' ' -f 3` > /dev/null; do
>&2 echo "Waiting for internet connection..."
sleep 1
if [ "$[$START + 60]" -lt $(date +%s) ]; then
>&2 echo "Timed out waiting for internet connection..."
exit 1
fi
done
echo "Connected to network"
# Convert all repos to use https:// before apt update
sed -i "s/http:/https:/g" /etc/apt/sources.list /etc/apt/sources.list.d/*.list
. /usr/lib/embassy/scripts/add-apt-sources
KERN=$(dpkg -s raspberrypi-kernel | grep Version | awk '{print $2}')
apt-get update
# TODO remove in 0.4.0
if [ "$KERN" != "1:1.20221104-1" ]; then
wget https://archive.raspberrypi.org/debian/pool/main/r/raspberrypi-firmware/raspberrypi-kernel_1.20221104-1_arm64.deb
sha256sum raspberrypi-kernel_1.20221104-1_arm64.deb | grep 9de9fe61e17eab351b6d4c8ee42d836c16b066f3593a4a9626283df6df718e42
apt-get install -y --allow-change-held-packages --allow-downgrades ./raspberrypi-kernel_1.20221104-1_arm64.deb
rm ./raspberrypi-kernel_1.20221104-1_arm64.deb
fi
apt-mark hold raspberrypi-bootloader
apt-mark hold raspberrypi-kernel
apt-get upgrade -y
if [ "$KERN" != "$(dpkg -s raspberrypi-kernel | grep Version | awk '{print $2}')" ]; then
echo "Kernel updated, restarting..."
sync
reboot
fi
apt-get install -y $(cat /usr/lib/embassy/depends)
apt-get remove --purge -y $(cat /usr/lib/embassy/conflicts) beep
apt-get autoremove -y
systemctl stop tor
. /usr/lib/embassy/scripts/postinst
usermod -aG embassy start9
systemctl enable embassyd.service embassy-init.service
# . /usr/lib/embassy/scripts/enable-kiosk
sed -i 's/^/usb-storage.quirks=152d:0562:u,14cd:121c:u,0781:cfcb:u /g' /boot/cmdline.txt
# making that *sudo docker stats* command fulfil its purpose by displaying all metrics
sed -i 's/rootwait quiet.*/rootwait cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory quiet/g' /boot/cmdline.txt
systemctl disable nc-broadcast.service
systemctl disable initialization.service
update-initramfs -c -k "$(uname -r)"
sed -i /boot/config.txt -e "/initramfs.*/d"
echo initramfs "initrd.img-$(uname -r)" >> /boot/config.txt
sed -i /boot/cmdline.txt -e "s/^/boot=embassy /"
passwd -l start9
sync
reboot

View File

@@ -1,34 +0,0 @@
#!/bin/bash
set -e
function partition_for () {
if [[ "$1" =~ [0-9]+$ ]]; then
echo "$1p$2"
else
echo "$1$2"
fi
}
TARGET_NAME=eos_raspberrypi-uninit.img
TARGET_SIZE=2400000000
cp raspios.img $TARGET_NAME
truncate -s $TARGET_SIZE $TARGET_NAME
(
echo d
echo 2
echo n
echo p
echo 2
echo 532480
echo
echo w
) | sudo fdisk $TARGET_NAME
export OUTPUT_DEVICE=$(sudo losetup --show -fP $TARGET_NAME)
sudo e2fsck -f -y `partition_for ${OUTPUT_DEVICE} 2`
sudo resize2fs `partition_for ${OUTPUT_DEVICE} 2`
./build/raspberry-pi/write-image.sh
sudo e2fsck -f -y `partition_for ${OUTPUT_DEVICE} 2`
sudo resize2fs -M `partition_for ${OUTPUT_DEVICE} 2`
sudo losetup -d $OUTPUT_DEVICE

View File

@@ -1,66 +0,0 @@
#!/bin/bash
set -e
function partition_for () {
if [[ "$1" =~ [0-9]+$ ]]; then
echo "$1p$2"
else
echo "$1$2"
fi
}
VERSION=$(cat VERSION.txt)
ENVIRONMENT=$(cat ENVIRONMENT.txt)
GIT_HASH=$(cat GIT_HASH.txt | head -c 7)
DATE=$(date +%Y%m%d)
VERSION_FULL="$VERSION-$GIT_HASH"
if [ -n "$ENVIRONMENT" ]; then
VERSION_FULL="$VERSION_FULL~$ENVIRONMENT"
fi
TARGET_NAME=eos-${VERSION_FULL}-${DATE}_raspberrypi.img
TARGET_SIZE=$[(31116287+1)*512]
rm -f $TARGET_NAME
truncate -s $TARGET_SIZE $TARGET_NAME
(
echo o
echo x
echo i
echo "0xcb15ae4d"
echo r
echo n
echo p
echo 1
echo 2048
echo 526335
echo 1050623
echo t
echo c
echo n
echo p
echo 2
echo 1050624
echo 31116287
echo a
echo 1
echo w
) | fdisk $TARGET_NAME
OUTPUT_DEVICE=$(sudo losetup --show -fP $TARGET_NAME)
sudo mkfs.ext4 `partition_for ${OUTPUT_DEVICE} 2`
sudo mkfs.vfat `partition_for ${OUTPUT_DEVICE} 1`
TMPDIR=$(mktemp -d)
sudo mount `partition_for ${OUTPUT_DEVICE} 2` $TMPDIR
sudo mkdir -p $TMPDIR/config
sudo mkdir -p $TMPDIR/next
sudo mkdir -p $TMPDIR/current/boot
sudo mount `partition_for ${OUTPUT_DEVICE} 1` $TMPDIR/current/boot
sudo unsquashfs -f -d $TMPDIR/current eos.raspberrypi.squashfs
sudo umount $TMPDIR/current/boot
sudo umount $TMPDIR
sudo losetup -d $OUTPUT_DEVICE

View File

@@ -1,45 +0,0 @@
#!/bin/bash
set -e
function partition_for () {
if [[ "$1" =~ [0-9]+$ ]]; then
echo "$1p$2"
else
echo "$1$2"
fi
}
TARGET_NAME=lite-upgrade.img
TARGET_SIZE=7000000000
LOOPDEV=$(sudo losetup --show -fP raspios.img)
sudo cat `partition_for ${LOOPDEV} 2` > $TARGET_NAME
sudo losetup -d $LOOPDEV
truncate -s $TARGET_SIZE $TARGET_NAME
sudo e2fsck -f -y $TARGET_NAME
sudo resize2fs $TARGET_NAME
TMPDIR=$(mktemp -d)
sudo mount $TARGET_NAME $TMPDIR/
sudo mkdir -p $TMPDIR/update
sudo unsquashfs -f -d $TMPDIR/update eos.raspberrypi.squashfs
sudo cp ./cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep $TMPDIR/usr/local/bin/beep
sudo cp ./build/raspberry-pi/033-upgrade.sh $TMPDIR/usr/local/bin/033-upgrade.sh
sudo cp ./build/raspberry-pi/033-upgrade.service $TMPDIR/etc/systemd/system/033-upgrade.service
sudo ln -s /etc/systemd/system/033-upgrade.service $TMPDIR/etc/systemd/system/multi-user.target.wants/033-upgrade.service
sudo cp ./cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast $TMPDIR/usr/local/bin
sudo cp ./build/raspberry-pi/nc-broadcast.service $TMPDIR/etc/systemd/system/nc-broadcast.service
sudo ln -s /etc/systemd/system/nc-broadcast.service $TMPDIR/etc/systemd/system/multi-user.target.wants/nc-broadcast.service
sudo umount $TMPDIR/
sudo e2fsck -f -y $TARGET_NAME
sudo resize2fs -M $TARGET_NAME
BLOCK_INFO=$(sudo dumpe2fs $TARGET_NAME)
BLOCK_COUNT=$(echo "$BLOCK_INFO" | grep "Block count:" | sed 's/Block count:\s\+//g')
BLOCK_SIZE=$(echo "$BLOCK_INFO" | grep "Block size:" | sed 's/Block size:\s\+//g')
FS_SIZE=$[$BLOCK_COUNT*$BLOCK_SIZE]
truncate -s $FS_SIZE $TARGET_NAME

View File

@@ -1,13 +0,0 @@
[Unit]
Description=Writes initialization logs to network
Requires=initialization.service
[Service]
Type=oneshot
Restart=on-failure
RestartSec=5s
ExecStart=/usr/local/bin/nc-broadcast --input=/var/log/initialization.log --tee 0.0.0.0:8080
RemainAfterExit=true
[Install]
WantedBy=multi-user.target

View File

@@ -1,30 +0,0 @@
#!/bin/bash
set -e
TMPDIR=$(mktemp -d)
ROOT_PARTITION=$(readlink -f /dev/disk/by-label/rootfs)
BOOT_PARTITION=$(readlink -f /dev/disk/by-label/boot)
if [[ "$ROOT_PARTITION" =~ ^/dev/loop ]] || [[ "$BOOT_PARTITION" =~ ^/dev/loop ]]; then
>&2 echo 'You are currently ripping from a loop device.'
>&2 echo 'This is probably a mistake, and usually means you failed to detach a .img file.'
read -p "Continue anyway? [y/N]" -n 1 -r
echo
if ! [[ "$REPLY" =~ ^[Yy]$ ]]; then
exit 1
fi
fi
sudo mount $ROOT_PARTITION $TMPDIR/
sudo mount $BOOT_PARTITION $TMPDIR/current/boot/
sudo sed -i 's/PARTUUID=[a-f0-9]\+/PARTUUID=cb15ae4d/g' $TMPDIR/current/etc/fstab
sudo sed -i 's/PARTUUID=[a-f0-9]\+/PARTUUID=cb15ae4d/g' $TMPDIR/current/boot/cmdline.txt
rm -f eos.raspberrypi.squashfs
sudo mksquashfs $TMPDIR/current/ eos.raspberrypi.squashfs
sudo umount $TMPDIR/current/boot/
sudo umount $TMPDIR/
rm -rf $TMPDIR

View File

@@ -1,56 +0,0 @@
#!/bin/bash
set -e
function partition_for () {
if [[ "$1" =~ [0-9]+$ ]]; then
echo "$1p$2"
else
echo "$1$2"
fi
}
# Mount the boot partition and config
TMPDIR=$(mktemp -d)
sudo mount `partition_for ${OUTPUT_DEVICE} 1` $TMPDIR
cat $TMPDIR/config.txt | grep -v "dtoverlay=" | sudo tee $TMPDIR/config.txt.tmp > /dev/null
echo "dtoverlay=pwm-2chan,disable-bt" | sudo tee -a $TMPDIR/config.txt.tmp > /dev/null
echo "gpu_mem=16" | sudo tee -a $TMPDIR/config.txt.tmp > /dev/null
sudo mv $TMPDIR/config.txt.tmp $TMPDIR/config.txt
sudo touch $TMPDIR/ssh
sudo umount $TMPDIR
sudo mount `partition_for ${OUTPUT_DEVICE} 2` $TMPDIR
sudo mkdir $TMPDIR/media/embassy/
sudo ENVIRONMENT=$ENVIRONMENT make V=1 install ARCH=aarch64 OS_ARCH=raspberrypi DESTDIR=$TMPDIR --debug
sudo sed -i 's/raspberrypi/embassy/g' $TMPDIR/etc/hostname
sudo sed -i 's/raspberrypi/embassy/g' $TMPDIR/etc/hosts
sudo cp ./cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep $TMPDIR/usr/local/bin/beep
sudo cp cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast $TMPDIR/usr/local/bin
sudo cp backend/*.service $TMPDIR/etc/systemd/system/
sudo mkdir -p $TMPDIR/etc/embassy
sudo cp build/raspberry-pi/config.yaml $TMPDIR/etc/embassy/config.yaml
# Make the .ssh directory for UID 1000 user
sudo mkdir -p $TMPDIR/home/$(awk -v val=1000 -F ":" '$3==val{print $1}' $TMPDIR/etc/passwd)/.ssh
sudo mv $TMPDIR/etc/sudoers.d/010_pi-nopasswd $TMPDIR/etc/sudoers.d/010_start9-nopasswd
sudo sed -i 's/pi/start9/g' $TMPDIR/etc/sudoers.d/010_start9-nopasswd
sudo sed -i 's/ pi / start9 /g' $TMPDIR/etc/systemd/system/autologin@.service
if [[ "$ENVIRONMENT" =~ (^|-)dev($|-) ]]; then
cat ./build/raspberry-pi/initialization.sh | grep -v "passwd -l start9" | sudo tee $TMPDIR/usr/local/bin/initialization.sh > /dev/null
sudo chmod +x $TMPDIR/usr/local/bin/initialization.sh
else
sudo cp ./build/raspberry-pi/initialization.sh $TMPDIR/usr/local/bin
fi
sudo cp ./build/raspberry-pi/init-with-sound.sh $TMPDIR/usr/local/bin
sudo cp ./build/raspberry-pi/initialization.service $TMPDIR/etc/systemd/system/initialization.service
sudo ln -s /etc/systemd/system/initialization.service $TMPDIR/etc/systemd/system/multi-user.target.wants/initialization.service
sudo cp ./build/raspberry-pi/nc-broadcast.service $TMPDIR/etc/systemd/system/nc-broadcast.service
sudo ln -s /etc/systemd/system/nc-broadcast.service $TMPDIR/etc/systemd/system/multi-user.target.wants/nc-broadcast.service
sudo umount $TMPDIR

View File

@@ -1,5 +1,5 @@
os-partitions:
boot: /dev/mmcblk0p1
root: /dev/mmcblk0p2
ethernet-interface: eth0
ethernet-interface: end0
wifi-interface: wlan0

2
build/raspberrypi/fstab Normal file
View File

@@ -0,0 +1,2 @@
/dev/mmcblk0p1 /boot vfat umask=0077 0 2
/dev/mmcblk0p2 / ext4 defaults 0 1

129
build/raspberrypi/init_resize.sh Executable file
View File

@@ -0,0 +1,129 @@
#!/bin/bash
get_variables () {
ROOT_PART_DEV=$(findmnt / -o source -n)
ROOT_PART_NAME=$(echo "$ROOT_PART_DEV" | cut -d "/" -f 3)
ROOT_DEV_NAME=$(echo /sys/block/*/"${ROOT_PART_NAME}" | cut -d "/" -f 4)
ROOT_DEV="/dev/${ROOT_DEV_NAME}"
ROOT_PART_NUM=$(cat "/sys/block/${ROOT_DEV_NAME}/${ROOT_PART_NAME}/partition")
BOOT_PART_DEV=$(findmnt /boot -o source -n)
BOOT_PART_NAME=$(echo "$BOOT_PART_DEV" | cut -d "/" -f 3)
BOOT_DEV_NAME=$(echo /sys/block/*/"${BOOT_PART_NAME}" | cut -d "/" -f 4)
BOOT_PART_NUM=$(cat "/sys/block/${BOOT_DEV_NAME}/${BOOT_PART_NAME}/partition")
OLD_DISKID=$(fdisk -l "$ROOT_DEV" | sed -n 's/Disk identifier: 0x\([^ ]*\)/\1/p')
ROOT_DEV_SIZE=$(cat "/sys/block/${ROOT_DEV_NAME}/size")
if [ "$ROOT_DEV_SIZE" -le 67108864 ]; then
TARGET_END=$((ROOT_DEV_SIZE - 1))
else
TARGET_END=$((33554432 - 1))
DATA_PART_START=33554432
DATA_PART_END=$((ROOT_DEV_SIZE - 1))
fi
PARTITION_TABLE=$(parted -m "$ROOT_DEV" unit s print | tr -d 's')
LAST_PART_NUM=$(echo "$PARTITION_TABLE" | tail -n 1 | cut -d ":" -f 1)
ROOT_PART_LINE=$(echo "$PARTITION_TABLE" | grep -e "^${ROOT_PART_NUM}:")
ROOT_PART_START=$(echo "$ROOT_PART_LINE" | cut -d ":" -f 2)
ROOT_PART_END=$(echo "$ROOT_PART_LINE" | cut -d ":" -f 3)
}
check_variables () {
if [ "$BOOT_DEV_NAME" != "$ROOT_DEV_NAME" ]; then
FAIL_REASON="Boot and root partitions are on different devices"
return 1
fi
if [ "$ROOT_PART_NUM" -ne "$LAST_PART_NUM" ]; then
FAIL_REASON="Root partition should be last partition"
return 1
fi
if [ "$ROOT_PART_END" -gt "$TARGET_END" ]; then
FAIL_REASON="Root partition runs past the end of device"
return 1
fi
if [ ! -b "$ROOT_DEV" ] || [ ! -b "$ROOT_PART_DEV" ] || [ ! -b "$BOOT_PART_DEV" ] ; then
FAIL_REASON="Could not determine partitions"
return 1
fi
}
main () {
get_variables
if ! check_variables; then
return 1
fi
# if [ "$ROOT_PART_END" -eq "$TARGET_END" ]; then
# reboot_pi
# fi
if ! echo Yes | parted -m --align=optimal "$ROOT_DEV" ---pretend-input-tty u s resizepart "$ROOT_PART_NUM" "$TARGET_END" ; then
FAIL_REASON="Root partition resize failed"
return 1
fi
if [ -n "$DATA_PART_START" ]; then
if ! parted -ms --align=optimal "$ROOT_DEV" u s mkpart primary "$DATA_PART_START" "$DATA_PART_END"; then
FAIL_REASON="Data partition creation failed"
return 1
fi
fi
(
echo x
echo i
echo "0xcb15ae4d"
echo r
echo w
) | fdisk $ROOT_DEV
mount / -o remount,rw
resize2fs $ROOT_PART_DEV
if ! systemd-machine-id-setup; then
FAIL_REASON="systemd-machine-id-setup failed"
return 1
fi
if ! ssh-keygen -A; then
FAIL_REASON="ssh host key generation failed"
return 1
fi
echo start > /etc/hostname
return 0
}
mount -t proc proc /proc
mount -t sysfs sys /sys
mount -t tmpfs tmp /run
mkdir -p /run/systemd
mount /boot
mount / -o remount,ro
beep
if main; then
sed -i 's| init=/usr/lib/embassy/scripts/init_resize\.sh| boot=embassy|' /boot/cmdline.txt
echo "Resized root filesystem. Rebooting in 5 seconds..."
sleep 5
else
echo -e "Could not expand filesystem.\n${FAIL_REASON}"
sleep 5
fi
sync
umount /boot
reboot -f

84
build/raspberrypi/make-image.sh Executable file
View File

@@ -0,0 +1,84 @@
#!/bin/bash
set -e
function partition_for () {
if [[ "$1" =~ [0-9]+$ ]]; then
echo "$1p$2"
else
echo "$1$2"
fi
}
VERSION=$(cat VERSION.txt)
ENVIRONMENT=$(cat ENVIRONMENT.txt)
GIT_HASH=$(cat GIT_HASH.txt | head -c 7)
DATE=$(date +%Y%m%d)
VERSION_FULL="$VERSION-$GIT_HASH"
if [ -n "$ENVIRONMENT" ]; then
VERSION_FULL="$VERSION_FULL~$ENVIRONMENT"
fi
TARGET_NAME=startos-${VERSION_FULL}-${DATE}_raspberrypi.img
TARGET_SIZE=$[(6817791+1)*512]
rm -f $TARGET_NAME
truncate -s $TARGET_SIZE $TARGET_NAME
(
echo o
echo x
echo i
echo "0xcb15ae4d"
echo r
echo n
echo p
echo 1
echo 2048
echo 526335
echo t
echo c
echo n
echo p
echo 2
echo 526336
echo 6817791
echo a
echo 1
echo w
) | fdisk $TARGET_NAME
OUTPUT_DEVICE=$(sudo losetup --show -fP $TARGET_NAME)
sudo mkfs.ext4 `partition_for ${OUTPUT_DEVICE} 2`
sudo mkfs.vfat `partition_for ${OUTPUT_DEVICE} 1`
TMPDIR=$(mktemp -d)
sudo mount `partition_for ${OUTPUT_DEVICE} 2` $TMPDIR
sudo mkdir $TMPDIR/boot
sudo mount `partition_for ${OUTPUT_DEVICE} 1` $TMPDIR/boot
sudo unsquashfs -f -d $TMPDIR startos.raspberrypi.squashfs
REAL_GIT_HASH=$(cat $TMPDIR/usr/lib/embassy/GIT_HASH.txt)
REAL_VERSION=$(cat $TMPDIR/usr/lib/embassy/VERSION.txt)
REAL_ENVIRONMENT=$(cat $TMPDIR/usr/lib/embassy/ENVIRONMENT.txt)
sudo sed -i 's| boot=embassy| init=/usr/lib/embassy/scripts/init_resize\.sh|' $TMPDIR/boot/cmdline.txt
sudo cp ./build/raspberrypi/fstab $TMPDIR/etc/
sudo cp ./build/raspberrypi/init_resize.sh $TMPDIR/usr/lib/embassy/scripts/init_resize.sh
sudo umount $TMPDIR/boot
sudo umount $TMPDIR
sudo losetup -d $OUTPUT_DEVICE
if [ "$ALLOW_VERSION_MISMATCH" != 1 ]; then
if [ "$(cat GIT_HASH.txt)" != "$REAL_GIT_HASH" ]; then
>&2 echo "startos.raspberrypi.squashfs GIT_HASH.txt mismatch"
exit 1
fi
if [ "$(cat VERSION.txt)" != "$REAL_VERSION" ]; then
>&2 echo "startos.raspberrypi.squashfs VERSION.txt mismatch"
exit 1
fi
if [ "$(cat ENVIRONMENT.txt)" != "$REAL_ENVIRONMENT" ]; then
>&2 echo "startos.raspberrypi.squashfs ENVIRONMENT.txt mismatch"
exit 1
fi
fi

View File

@@ -1,7 +1,7 @@
#!/bin/bash
if ! [ -f ./ENVIRONMENT.txt ] || [ "$(cat ./ENVIRONMENT.txt)" != "$ENVIRONMENT" ]; then
>&2 echo "Updating ENVIRONMENT.txt to $ENVIRONMENT"
>&2 echo "Updating ENVIRONMENT.txt to \"$ENVIRONMENT\""
echo -n "$ENVIRONMENT" > ./ENVIRONMENT.txt
fi

View File

@@ -1,11 +1,11 @@
# embassyOS Frontend
# StartOS Frontend
embassyOS has three user interfaces and a shared library, all written in Ionic/Angular/Typescript using an Angular workspace environment:
StartOS has three user interfaces and a shared library, all written in Ionic/Angular/Typescript using an Angular workspace environment:
1. **ui**: the main user interface
1. **install-wizard**: used to install embassyOS
1. **install-wizard**: used to install StartOS
1. **setup-wizard**: used to facilitate initial setup
1. **diagnostic-ui**: used to display certain diagnostic information in the event embassyOS fails to initialize
1. **diagnostic-ui**: used to display certain diagnostic information in the event StartOS fails to initialize
1. **marketplace**: abstracted ui elements to search for, list and display details for packages and their dependencies
1. **shared**: contains components, types, and functions shared amongst all of the UIs.
@@ -30,8 +30,8 @@ v8.0.0
1. Clone the repository
```sh
git clone https://github.com/Start9Labs/embassy-os.git
cd embassy-os
git clone https://github.com/Start9Labs/start-os.git
cd start-os
git submodule update --init --recursive
cd frontend
npm ci
@@ -58,7 +58,7 @@ npm run start:diagnostic-ui
## Running locally with proxied backend
This section enables you to run a local frontend with a remote backend (eg. hosted on a live Embassy). It assumes you have completed Step 1 and Step 2 in the [section above](#running-locally-with-mocks)
This section enables you to run a local frontend with a remote backend (eg. hosted on a live Start9 server). It assumes you have completed Step 1 and Step 2 in the [section above](#running-locally-with-mocks)
1. Set `useMocks: false` in `config.json`

View File

@@ -35,6 +35,12 @@
"glob": "**/*",
"input": "node_modules/monaco-editor",
"output": "assets/monaco-editor/"
},
"projects/ui/src/manifest.webmanifest",
{
"glob": "ngsw.json",
"input": "dist/ui",
"output": "projects/ui/src"
}
],
"styles": [
@@ -43,10 +49,12 @@
"projects/shared/styles/shared.scss",
"projects/ui/src/styles.scss"
],
"scripts": []
"scripts": [],
"ngswConfigPath": "projects/ui/ngsw-config.json"
},
"configurations": {
"production": {
"serviceWorker": true,
"budgets": [
{
"type": "initial",

Some files were not shown because too many files have changed in this diff Show More