mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 14:29:45 +00:00
add support for remote attaching to container (#2732)
* add support for remote attaching to container * feature: Add in the subcontainer searching * feat: Add in the name/ imageId filtering * Feat: Fix the env and the workdir * chore: Make the sigkill first? * add some extra guard on term * fix: Health during error doesnt return what we need * chore: Cleanup for pr * fix build * fix build * Update startos-iso.yaml * Update startos-iso.yaml * Update startos-iso.yaml * Update startos-iso.yaml * Update startos-iso.yaml * Update startos-iso.yaml * Update startos-iso.yaml * check status during build --------- Co-authored-by: J H <dragondef@gmail.com>
This commit is contained in:
21
.github/workflows/startos-iso.yaml
vendored
21
.github/workflows/startos-iso.yaml
vendored
@@ -78,7 +78,7 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: "3.x"
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
@@ -156,7 +156,7 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: "3.x"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -187,11 +187,24 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mkdir -p web/node_modules
|
mkdir -p web/node_modules
|
||||||
mkdir -p web/dist/raw
|
mkdir -p web/dist/raw
|
||||||
touch core/startos/bindings
|
mkdir -p container-runtime/node_modules
|
||||||
touch sdk/lib/osBindings
|
|
||||||
mkdir -p container-runtime/dist
|
mkdir -p container-runtime/dist
|
||||||
|
mkdir -p container-runtime/dist/node_modules
|
||||||
|
mkdir -p core/startos/bindings
|
||||||
|
mkdir -p sdk/dist
|
||||||
|
mkdir -p patch-db/client/node_modules
|
||||||
|
mkdir -p patch-db/client/dist
|
||||||
|
mkdir -p web/.angular
|
||||||
|
mkdir -p web/dist/raw/ui
|
||||||
|
mkdir -p web/dist/raw/install-wizard
|
||||||
|
mkdir -p web/dist/raw/setup-wizard
|
||||||
|
mkdir -p web/dist/static/ui
|
||||||
|
mkdir -p web/dist/static/install-wizard
|
||||||
|
mkdir -p web/dist/static/setup-wizard
|
||||||
PLATFORM=${{ matrix.platform }} make -t compiled-${{ env.ARCH }}.tar
|
PLATFORM=${{ matrix.platform }} make -t compiled-${{ env.ARCH }}.tar
|
||||||
|
|
||||||
|
- run: git status
|
||||||
|
|
||||||
- name: Run iso build
|
- name: Run iso build
|
||||||
run: PLATFORM=${{ matrix.platform }} make iso
|
run: PLATFORM=${{ matrix.platform }} make iso
|
||||||
if: ${{ matrix.platform != 'raspberrypi' }}
|
if: ${{ matrix.platform != 'raspberrypi' }}
|
||||||
|
|||||||
67
Makefile
67
Makefile
@@ -6,7 +6,8 @@ BASENAME := $(shell ./basename.sh)
|
|||||||
PLATFORM := $(shell if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi)
|
PLATFORM := $(shell if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi)
|
||||||
ARCH := $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo aarch64; else echo $(PLATFORM) | sed 's/-nonfree$$//g'; fi)
|
ARCH := $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo aarch64; else echo $(PLATFORM) | sed 's/-nonfree$$//g'; fi)
|
||||||
IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi)
|
IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi)
|
||||||
WEB_UIS := web/dist/raw/ui web/dist/raw/setup-wizard web/dist/raw/install-wizard
|
WEB_UIS := web/dist/raw/ui/index.html web/dist/raw/setup-wizard/index.html web/dist/raw/install-wizard/index.html
|
||||||
|
COMPRESSED_WEB_UIS := web/dist/static/ui/index.html web/dist/static/setup-wizard/index.html web/dist/static/install-wizard/index.html
|
||||||
FIRMWARE_ROMS := ./firmware/$(PLATFORM) $(shell jq --raw-output '.[] | select(.platform[] | contains("$(PLATFORM)")) | "./firmware/$(PLATFORM)/" + .id + ".rom.gz"' build/lib/firmware.json)
|
FIRMWARE_ROMS := ./firmware/$(PLATFORM) $(shell jq --raw-output '.[] | select(.platform[] | contains("$(PLATFORM)")) | "./firmware/$(PLATFORM)/" + .id + ".rom.gz"' build/lib/firmware.json)
|
||||||
BUILD_SRC := $(shell git ls-files build) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS)
|
BUILD_SRC := $(shell git ls-files build) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS)
|
||||||
DEBIAN_SRC := $(shell git ls-files debian/)
|
DEBIAN_SRC := $(shell git ls-files debian/)
|
||||||
@@ -16,7 +17,7 @@ COMPAT_SRC := $(shell git ls-files system-images/compat/)
|
|||||||
UTILS_SRC := $(shell git ls-files system-images/utils/)
|
UTILS_SRC := $(shell git ls-files system-images/utils/)
|
||||||
BINFMT_SRC := $(shell git ls-files system-images/binfmt/)
|
BINFMT_SRC := $(shell git ls-files system-images/binfmt/)
|
||||||
CORE_SRC := $(shell git ls-files core) $(shell git ls-files --recurse-submodules patch-db) $(GIT_HASH_FILE)
|
CORE_SRC := $(shell git ls-files core) $(shell git ls-files --recurse-submodules patch-db) $(GIT_HASH_FILE)
|
||||||
WEB_SHARED_SRC := $(shell git ls-files web/projects/shared) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist web/patchdb-ui-seed.json sdk/dist
|
WEB_SHARED_SRC := $(shell git ls-files web/projects/shared) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist/index.js web/patchdb-ui-seed.json sdk/dist/package.json
|
||||||
WEB_UI_SRC := $(shell git ls-files web/projects/ui)
|
WEB_UI_SRC := $(shell git ls-files web/projects/ui)
|
||||||
WEB_SETUP_WIZARD_SRC := $(shell git ls-files web/projects/setup-wizard)
|
WEB_SETUP_WIZARD_SRC := $(shell git ls-files web/projects/setup-wizard)
|
||||||
WEB_INSTALL_WIZARD_SRC := $(shell git ls-files web/projects/install-wizard)
|
WEB_INSTALL_WIZARD_SRC := $(shell git ls-files web/projects/install-wizard)
|
||||||
@@ -57,7 +58,7 @@ touch:
|
|||||||
metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
||||||
|
|
||||||
sudo:
|
sudo:
|
||||||
sudo true
|
sudo -v
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f system-images/**/*.tar
|
rm -f system-images/**/*.tar
|
||||||
@@ -94,10 +95,10 @@ test: | test-core test-sdk test-container-runtime
|
|||||||
test-core: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
test-core: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||||
./core/run-tests.sh
|
./core/run-tests.sh
|
||||||
|
|
||||||
test-sdk: $(shell git ls-files sdk) sdk/lib/osBindings
|
test-sdk: $(shell git ls-files sdk) sdk/lib/osBindings/index.ts
|
||||||
cd sdk && make test
|
cd sdk && make test
|
||||||
|
|
||||||
test-container-runtime: container-runtime/node_modules $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
test-container-runtime: container-runtime/node_modules/.package-lock.json $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
||||||
cd container-runtime && npm test
|
cd container-runtime && npm test
|
||||||
|
|
||||||
cli:
|
cli:
|
||||||
@@ -218,34 +219,34 @@ upload-ota: results/$(BASENAME).squashfs
|
|||||||
container-runtime/debian.$(ARCH).squashfs:
|
container-runtime/debian.$(ARCH).squashfs:
|
||||||
ARCH=$(ARCH) ./container-runtime/download-base-image.sh
|
ARCH=$(ARCH) ./container-runtime/download-base-image.sh
|
||||||
|
|
||||||
container-runtime/node_modules: container-runtime/package.json container-runtime/package-lock.json sdk/dist
|
container-runtime/node_modules/.package-lock.json: container-runtime/package.json container-runtime/package-lock.json sdk/dist/package.json
|
||||||
npm --prefix container-runtime ci
|
npm --prefix container-runtime ci
|
||||||
touch container-runtime/node_modules
|
touch container-runtime/node_modules/.package-lock.json
|
||||||
|
|
||||||
sdk/lib/osBindings: core/startos/bindings
|
sdk/lib/osBindings/index.ts: core/startos/bindings/index.ts
|
||||||
mkdir -p sdk/lib/osBindings
|
|
||||||
ls core/startos/bindings/*.ts | sed 's/core\/startos\/bindings\/\([^.]*\)\.ts/export { \1 } from ".\/\1";/g' > core/startos/bindings/index.ts
|
|
||||||
npm --prefix sdk exec -- prettier --config ./sdk/package.json -w ./core/startos/bindings/*.ts
|
|
||||||
rsync -ac --delete core/startos/bindings/ sdk/lib/osBindings/
|
rsync -ac --delete core/startos/bindings/ sdk/lib/osBindings/
|
||||||
touch sdk/lib/osBindings
|
touch sdk/lib/osBindings/index.ts
|
||||||
|
|
||||||
core/startos/bindings: $(shell git ls-files core) $(ENVIRONMENT_FILE)
|
core/startos/bindings/index.ts: $(shell git ls-files core) $(ENVIRONMENT_FILE)
|
||||||
rm -rf core/startos/bindings
|
rm -rf core/startos/bindings
|
||||||
./core/build-ts.sh
|
./core/build-ts.sh
|
||||||
touch core/startos/bindings
|
ls core/startos/bindings/*.ts | sed 's/core\/startos\/bindings\/\([^.]*\)\.ts/export { \1 } from ".\/\1";/g' > core/startos/bindings/index.ts
|
||||||
|
npm --prefix sdk exec -- prettier --config ./sdk/package.json -w ./core/startos/bindings/*.ts
|
||||||
|
touch core/startos/bindings/index.ts
|
||||||
|
|
||||||
sdk/dist: $(shell git ls-files sdk) sdk/lib/osBindings
|
sdk/dist/package.json: $(shell git ls-files sdk) sdk/lib/osBindings/index.ts
|
||||||
(cd sdk && make bundle)
|
(cd sdk && make bundle)
|
||||||
|
touch sdk/dist/package.json
|
||||||
|
|
||||||
# TODO: make container-runtime its own makefile?
|
# TODO: make container-runtime its own makefile?
|
||||||
container-runtime/dist/index.js: container-runtime/node_modules $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
container-runtime/dist/index.js: container-runtime/node_modules/.package-lock.json $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
||||||
npm --prefix container-runtime run build
|
npm --prefix container-runtime run build
|
||||||
|
|
||||||
container-runtime/dist/node_modules container-runtime/dist/package.json container-runtime/dist/package-lock.json: container-runtime/package.json container-runtime/package-lock.json sdk/dist container-runtime/install-dist-deps.sh
|
container-runtime/dist/node_modules/.package-lock.json container-runtime/dist/package.json container-runtime/dist/package-lock.json: container-runtime/package.json container-runtime/package-lock.json sdk/dist/package.json container-runtime/install-dist-deps.sh
|
||||||
./container-runtime/install-dist-deps.sh
|
./container-runtime/install-dist-deps.sh
|
||||||
touch container-runtime/dist/node_modules
|
touch container-runtime/dist/node_modules/.package-lock.json
|
||||||
|
|
||||||
container-runtime/rootfs.$(ARCH).squashfs: container-runtime/debian.$(ARCH).squashfs container-runtime/container-runtime.service container-runtime/update-image.sh container-runtime/deb-install.sh container-runtime/dist/index.js container-runtime/dist/node_modules core/target/$(ARCH)-unknown-linux-musl/release/containerbox | sudo
|
container-runtime/rootfs.$(ARCH).squashfs: container-runtime/debian.$(ARCH).squashfs container-runtime/container-runtime.service container-runtime/update-image.sh container-runtime/deb-install.sh container-runtime/dist/index.js container-runtime/dist/node_modules/.package-lock.json core/target/$(ARCH)-unknown-linux-musl/release/containerbox | sudo
|
||||||
ARCH=$(ARCH) ./container-runtime/update-image.sh
|
ARCH=$(ARCH) ./container-runtime/update-image.sh
|
||||||
|
|
||||||
build/lib/depends build/lib/conflicts: build/dpkg-deps/*
|
build/lib/depends build/lib/conflicts: build/dpkg-deps/*
|
||||||
@@ -263,7 +264,7 @@ system-images/utils/docker-images/$(ARCH).tar: $(UTILS_SRC)
|
|||||||
system-images/binfmt/docker-images/$(ARCH).tar: $(BINFMT_SRC)
|
system-images/binfmt/docker-images/$(ARCH).tar: $(BINFMT_SRC)
|
||||||
cd system-images/binfmt && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
cd system-images/binfmt && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
||||||
|
|
||||||
core/target/$(ARCH)-unknown-linux-musl/release/startbox: $(CORE_SRC) web/dist/static web/patchdb-ui-seed.json $(ENVIRONMENT_FILE)
|
core/target/$(ARCH)-unknown-linux-musl/release/startbox: $(CORE_SRC) $(COMPRESSED_WEB_UIS) web/patchdb-ui-seed.json $(ENVIRONMENT_FILE)
|
||||||
ARCH=$(ARCH) ./core/build-startbox.sh
|
ARCH=$(ARCH) ./core/build-startbox.sh
|
||||||
touch core/target/$(ARCH)-unknown-linux-musl/release/startbox
|
touch core/target/$(ARCH)-unknown-linux-musl/release/startbox
|
||||||
|
|
||||||
@@ -271,27 +272,28 @@ core/target/$(ARCH)-unknown-linux-musl/release/containerbox: $(CORE_SRC) $(ENVIR
|
|||||||
ARCH=$(ARCH) ./core/build-containerbox.sh
|
ARCH=$(ARCH) ./core/build-containerbox.sh
|
||||||
touch core/target/$(ARCH)-unknown-linux-musl/release/containerbox
|
touch core/target/$(ARCH)-unknown-linux-musl/release/containerbox
|
||||||
|
|
||||||
web/node_modules/.package-lock.json: web/package.json sdk/dist
|
web/node_modules/.package-lock.json: web/package.json sdk/dist/package.json
|
||||||
npm --prefix web ci
|
npm --prefix web ci
|
||||||
touch web/node_modules/.package-lock.json
|
touch web/node_modules/.package-lock.json
|
||||||
|
|
||||||
web/.angular: patch-db/client/dist sdk/dist web/node_modules/.package-lock.json
|
web/.angular/.updated: patch-db/client/dist/index.js sdk/dist/package.json web/node_modules/.package-lock.json
|
||||||
rm -rf web/.angular
|
rm -rf web/.angular
|
||||||
mkdir -p web/.angular
|
mkdir -p web/.angular
|
||||||
|
touch web/.angular/.updated
|
||||||
|
|
||||||
web/dist/raw/ui: $(WEB_UI_SRC) $(WEB_SHARED_SRC) web/.angular
|
web/dist/raw/ui/index.html: $(WEB_UI_SRC) $(WEB_SHARED_SRC) web/.angular/.updated
|
||||||
npm --prefix web run build:ui
|
npm --prefix web run build:ui
|
||||||
touch web/dist/raw/ui
|
touch web/dist/raw/ui/index.html
|
||||||
|
|
||||||
web/dist/raw/setup-wizard: $(WEB_SETUP_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular
|
web/dist/raw/setup-wizard/index.html: $(WEB_SETUP_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular/.updated
|
||||||
npm --prefix web run build:setup
|
npm --prefix web run build:setup
|
||||||
touch web/dist/raw/setup-wizard
|
touch web/dist/raw/setup-wizard/index.html
|
||||||
|
|
||||||
web/dist/raw/install-wizard: $(WEB_INSTALL_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular
|
web/dist/raw/install-wizard/index.html: $(WEB_INSTALL_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular/.updated
|
||||||
npm --prefix web run build:install-wiz
|
npm --prefix web run build:install-wiz
|
||||||
touch web/dist/raw/install-wizard
|
touch web/dist/raw/install-wizard/index.html
|
||||||
|
|
||||||
web/dist/static: $(WEB_UIS) $(ENVIRONMENT_FILE)
|
$(COMPRESSED_WEB_UIS): $(WEB_UIS) $(ENVIRONMENT_FILE)
|
||||||
./compress-uis.sh
|
./compress-uis.sh
|
||||||
|
|
||||||
web/config.json: $(GIT_HASH_FILE) web/config-sample.json
|
web/config.json: $(GIT_HASH_FILE) web/config-sample.json
|
||||||
@@ -301,13 +303,14 @@ web/patchdb-ui-seed.json: web/package.json
|
|||||||
jq '."ack-welcome" = $(shell jq '.version' web/package.json)' web/patchdb-ui-seed.json > ui-seed.tmp
|
jq '."ack-welcome" = $(shell jq '.version' web/package.json)' web/patchdb-ui-seed.json > ui-seed.tmp
|
||||||
mv ui-seed.tmp web/patchdb-ui-seed.json
|
mv ui-seed.tmp web/patchdb-ui-seed.json
|
||||||
|
|
||||||
patch-db/client/node_modules: patch-db/client/package.json
|
patch-db/client/node_modules/.package-lock.json: patch-db/client/package.json
|
||||||
npm --prefix patch-db/client ci
|
npm --prefix patch-db/client ci
|
||||||
touch patch-db/client/node_modules
|
touch patch-db/client/node_modules/.package-lock.json
|
||||||
|
|
||||||
patch-db/client/dist: $(PATCH_DB_CLIENT_SRC) patch-db/client/node_modules
|
patch-db/client/dist/index.js: $(PATCH_DB_CLIENT_SRC) patch-db/client/node_modules/.package-lock.json
|
||||||
rm -rf patch-db/client/dist
|
rm -rf patch-db/client/dist
|
||||||
npm --prefix patch-db/client run build
|
npm --prefix patch-db/client run build
|
||||||
|
touch patch-db/client/dist/index.js
|
||||||
|
|
||||||
# used by github actions
|
# used by github actions
|
||||||
compiled-$(ARCH).tar: $(COMPILED_TARGETS) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE)
|
compiled-$(ARCH).tar: $(COMPILED_TARGETS) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ ca-certificates
|
|||||||
cifs-utils
|
cifs-utils
|
||||||
cryptsetup
|
cryptsetup
|
||||||
curl
|
curl
|
||||||
|
dnsutils
|
||||||
dmidecode
|
dmidecode
|
||||||
dosfstools
|
dosfstools
|
||||||
e2fsprogs
|
e2fsprogs
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ function makeEffects(context: EffectContext): Effects {
|
|||||||
>
|
>
|
||||||
},
|
},
|
||||||
subcontainer: {
|
subcontainer: {
|
||||||
createFs(options: { imageId: string }) {
|
createFs(options: { imageId: string; name: string }) {
|
||||||
return rpcRound("subcontainer.create-fs", options) as ReturnType<
|
return rpcRound("subcontainer.create-fs", options) as ReturnType<
|
||||||
T.Effects["subcontainer"]["createFs"]
|
T.Effects["subcontainer"]["createFs"]
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -198,7 +198,11 @@ export class RpcListener {
|
|||||||
.then((x) => this.dealWithInput(x))
|
.then((x) => this.dealWithInput(x))
|
||||||
.catch(mapError)
|
.catch(mapError)
|
||||||
.then(logData("response"))
|
.then(logData("response"))
|
||||||
.then(writeDataToSocket),
|
.then(writeDataToSocket)
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(`Major error in socket handling: ${e}`)
|
||||||
|
console.debug(`Data in: ${a.toString()}`)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export class DockerProcedureContainer {
|
|||||||
packageId: string,
|
packageId: string,
|
||||||
data: DockerProcedure,
|
data: DockerProcedure,
|
||||||
volumes: { [id: VolumeId]: Volume },
|
volumes: { [id: VolumeId]: Volume },
|
||||||
|
name: string,
|
||||||
options: { subcontainer?: ExecSpawnable } = {},
|
options: { subcontainer?: ExecSpawnable } = {},
|
||||||
) {
|
) {
|
||||||
const subcontainer =
|
const subcontainer =
|
||||||
@@ -29,6 +30,7 @@ export class DockerProcedureContainer {
|
|||||||
packageId,
|
packageId,
|
||||||
data,
|
data,
|
||||||
volumes,
|
volumes,
|
||||||
|
name,
|
||||||
))
|
))
|
||||||
return new DockerProcedureContainer(subcontainer)
|
return new DockerProcedureContainer(subcontainer)
|
||||||
}
|
}
|
||||||
@@ -37,8 +39,13 @@ export class DockerProcedureContainer {
|
|||||||
packageId: string,
|
packageId: string,
|
||||||
data: DockerProcedure,
|
data: DockerProcedure,
|
||||||
volumes: { [id: VolumeId]: Volume },
|
volumes: { [id: VolumeId]: Volume },
|
||||||
|
name: string,
|
||||||
) {
|
) {
|
||||||
const subcontainer = await SubContainer.of(effects, { id: data.image })
|
const subcontainer = await SubContainer.of(
|
||||||
|
effects,
|
||||||
|
{ id: data.image },
|
||||||
|
name,
|
||||||
|
)
|
||||||
|
|
||||||
if (data.mounts) {
|
if (data.mounts) {
|
||||||
const mounts = data.mounts
|
const mounts = data.mounts
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export class MainLoop {
|
|||||||
this.system.manifest.id,
|
this.system.manifest.id,
|
||||||
this.system.manifest.main,
|
this.system.manifest.main,
|
||||||
this.system.manifest.volumes,
|
this.system.manifest.volumes,
|
||||||
|
`Main - ${currentCommand.join(" ")}`,
|
||||||
)
|
)
|
||||||
return CommandController.of()(
|
return CommandController.of()(
|
||||||
this.effects,
|
this.effects,
|
||||||
@@ -162,26 +163,29 @@ export class MainLoop {
|
|||||||
const subcontainer = actionProcedure.inject
|
const subcontainer = actionProcedure.inject
|
||||||
? this.mainSubContainerHandle
|
? this.mainSubContainerHandle
|
||||||
: undefined
|
: undefined
|
||||||
// prettier-ignore
|
const commands = [
|
||||||
const container =
|
actionProcedure.entrypoint,
|
||||||
await DockerProcedureContainer.of(
|
...actionProcedure.args,
|
||||||
effects,
|
]
|
||||||
manifest.id,
|
const container = await DockerProcedureContainer.of(
|
||||||
actionProcedure,
|
effects,
|
||||||
manifest.volumes,
|
manifest.id,
|
||||||
{
|
actionProcedure,
|
||||||
subcontainer,
|
manifest.volumes,
|
||||||
}
|
`Health Check - ${commands.join(" ")}`,
|
||||||
)
|
{
|
||||||
|
subcontainer,
|
||||||
|
},
|
||||||
|
)
|
||||||
const env: Record<string, string> = actionProcedure.inject
|
const env: Record<string, string> = actionProcedure.inject
|
||||||
? {
|
? {
|
||||||
HOME: "/root",
|
HOME: "/root",
|
||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
const executed = await container.exec(
|
const executed = await container.exec(commands, {
|
||||||
[actionProcedure.entrypoint, ...actionProcedure.args],
|
input: JSON.stringify(timeChanged),
|
||||||
{ input: JSON.stringify(timeChanged), env },
|
env,
|
||||||
)
|
})
|
||||||
|
|
||||||
if (executed.exitCode === 0) {
|
if (executed.exitCode === 0) {
|
||||||
await effects.setHealth({
|
await effects.setHealth({
|
||||||
|
|||||||
@@ -443,6 +443,7 @@ export class SystemForEmbassy implements System {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const backup = this.manifest.backup.create
|
const backup = this.manifest.backup.create
|
||||||
if (backup.type === "docker") {
|
if (backup.type === "docker") {
|
||||||
|
const commands = [backup.entrypoint, ...backup.args]
|
||||||
const container = await DockerProcedureContainer.of(
|
const container = await DockerProcedureContainer.of(
|
||||||
effects,
|
effects,
|
||||||
this.manifest.id,
|
this.manifest.id,
|
||||||
@@ -451,8 +452,9 @@ export class SystemForEmbassy implements System {
|
|||||||
...this.manifest.volumes,
|
...this.manifest.volumes,
|
||||||
BACKUP: { type: "backup", readonly: false },
|
BACKUP: { type: "backup", readonly: false },
|
||||||
},
|
},
|
||||||
|
`Backup - ${commands.join(" ")}`,
|
||||||
)
|
)
|
||||||
await container.execFail([backup.entrypoint, ...backup.args], timeoutMs)
|
await container.execFail(commands, timeoutMs)
|
||||||
} else {
|
} else {
|
||||||
const moduleCode = await this.moduleCode
|
const moduleCode = await this.moduleCode
|
||||||
await moduleCode.createBackup?.(polyfillEffects(effects, this.manifest))
|
await moduleCode.createBackup?.(polyfillEffects(effects, this.manifest))
|
||||||
@@ -464,6 +466,7 @@ export class SystemForEmbassy implements System {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const restoreBackup = this.manifest.backup.restore
|
const restoreBackup = this.manifest.backup.restore
|
||||||
if (restoreBackup.type === "docker") {
|
if (restoreBackup.type === "docker") {
|
||||||
|
const commands = [restoreBackup.entrypoint, ...restoreBackup.args]
|
||||||
const container = await DockerProcedureContainer.of(
|
const container = await DockerProcedureContainer.of(
|
||||||
effects,
|
effects,
|
||||||
this.manifest.id,
|
this.manifest.id,
|
||||||
@@ -472,11 +475,9 @@ export class SystemForEmbassy implements System {
|
|||||||
...this.manifest.volumes,
|
...this.manifest.volumes,
|
||||||
BACKUP: { type: "backup", readonly: true },
|
BACKUP: { type: "backup", readonly: true },
|
||||||
},
|
},
|
||||||
|
`Restore Backup - ${commands.join(" ")}`,
|
||||||
)
|
)
|
||||||
await container.execFail(
|
await container.execFail(commands, timeoutMs)
|
||||||
[restoreBackup.entrypoint, ...restoreBackup.args],
|
|
||||||
timeoutMs,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
const moduleCode = await this.moduleCode
|
const moduleCode = await this.moduleCode
|
||||||
await moduleCode.restoreBackup?.(polyfillEffects(effects, this.manifest))
|
await moduleCode.restoreBackup?.(polyfillEffects(effects, this.manifest))
|
||||||
@@ -495,20 +496,17 @@ export class SystemForEmbassy implements System {
|
|||||||
const config = this.manifest.config?.get
|
const config = this.manifest.config?.get
|
||||||
if (!config) return { spec: {} }
|
if (!config) return { spec: {} }
|
||||||
if (config.type === "docker") {
|
if (config.type === "docker") {
|
||||||
|
const commands = [config.entrypoint, ...config.args]
|
||||||
const container = await DockerProcedureContainer.of(
|
const container = await DockerProcedureContainer.of(
|
||||||
effects,
|
effects,
|
||||||
this.manifest.id,
|
this.manifest.id,
|
||||||
config,
|
config,
|
||||||
this.manifest.volumes,
|
this.manifest.volumes,
|
||||||
|
`Get Config - ${commands.join(" ")}`,
|
||||||
)
|
)
|
||||||
// TODO: yaml
|
// TODO: yaml
|
||||||
return JSON.parse(
|
return JSON.parse(
|
||||||
(
|
(await container.execFail(commands, timeoutMs)).stdout.toString(),
|
||||||
await container.execFail(
|
|
||||||
[config.entrypoint, ...config.args],
|
|
||||||
timeoutMs,
|
|
||||||
)
|
|
||||||
).stdout.toString(),
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const moduleCode = await this.moduleCode
|
const moduleCode = await this.moduleCode
|
||||||
@@ -543,24 +541,21 @@ export class SystemForEmbassy implements System {
|
|||||||
const setConfigValue = this.manifest.config?.set
|
const setConfigValue = this.manifest.config?.set
|
||||||
if (!setConfigValue) return
|
if (!setConfigValue) return
|
||||||
if (setConfigValue.type === "docker") {
|
if (setConfigValue.type === "docker") {
|
||||||
|
const commands = [
|
||||||
|
setConfigValue.entrypoint,
|
||||||
|
...setConfigValue.args,
|
||||||
|
JSON.stringify(newConfig),
|
||||||
|
]
|
||||||
const container = await DockerProcedureContainer.of(
|
const container = await DockerProcedureContainer.of(
|
||||||
effects,
|
effects,
|
||||||
this.manifest.id,
|
this.manifest.id,
|
||||||
setConfigValue,
|
setConfigValue,
|
||||||
this.manifest.volumes,
|
this.manifest.volumes,
|
||||||
|
`Set Config - ${commands.join(" ")}`,
|
||||||
)
|
)
|
||||||
const answer = matchSetResult.unsafeCast(
|
const answer = matchSetResult.unsafeCast(
|
||||||
JSON.parse(
|
JSON.parse(
|
||||||
(
|
(await container.execFail(commands, timeoutMs)).stdout.toString(),
|
||||||
await container.execFail(
|
|
||||||
[
|
|
||||||
setConfigValue.entrypoint,
|
|
||||||
...setConfigValue.args,
|
|
||||||
JSON.stringify(newConfig),
|
|
||||||
],
|
|
||||||
timeoutMs,
|
|
||||||
)
|
|
||||||
).stdout.toString(),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
const dependsOn = answer["depends-on"] ?? answer.dependsOn ?? {}
|
const dependsOn = answer["depends-on"] ?? answer.dependsOn ?? {}
|
||||||
@@ -652,23 +647,20 @@ export class SystemForEmbassy implements System {
|
|||||||
if (migration) {
|
if (migration) {
|
||||||
const [version, procedure] = migration
|
const [version, procedure] = migration
|
||||||
if (procedure.type === "docker") {
|
if (procedure.type === "docker") {
|
||||||
|
const commands = [
|
||||||
|
procedure.entrypoint,
|
||||||
|
...procedure.args,
|
||||||
|
JSON.stringify(fromVersion),
|
||||||
|
]
|
||||||
const container = await DockerProcedureContainer.of(
|
const container = await DockerProcedureContainer.of(
|
||||||
effects,
|
effects,
|
||||||
this.manifest.id,
|
this.manifest.id,
|
||||||
procedure,
|
procedure,
|
||||||
this.manifest.volumes,
|
this.manifest.volumes,
|
||||||
|
`Migration - ${commands.join(" ")}`,
|
||||||
)
|
)
|
||||||
return JSON.parse(
|
return JSON.parse(
|
||||||
(
|
(await container.execFail(commands, timeoutMs)).stdout.toString(),
|
||||||
await container.execFail(
|
|
||||||
[
|
|
||||||
procedure.entrypoint,
|
|
||||||
...procedure.args,
|
|
||||||
JSON.stringify(fromVersion),
|
|
||||||
],
|
|
||||||
timeoutMs,
|
|
||||||
)
|
|
||||||
).stdout.toString(),
|
|
||||||
)
|
)
|
||||||
} else if (procedure.type === "script") {
|
} else if (procedure.type === "script") {
|
||||||
const moduleCode = await this.moduleCode
|
const moduleCode = await this.moduleCode
|
||||||
@@ -695,20 +687,17 @@ export class SystemForEmbassy implements System {
|
|||||||
const setConfigValue = this.manifest.properties
|
const setConfigValue = this.manifest.properties
|
||||||
if (!setConfigValue) throw new Error("There is no properties")
|
if (!setConfigValue) throw new Error("There is no properties")
|
||||||
if (setConfigValue.type === "docker") {
|
if (setConfigValue.type === "docker") {
|
||||||
|
const commands = [setConfigValue.entrypoint, ...setConfigValue.args]
|
||||||
const container = await DockerProcedureContainer.of(
|
const container = await DockerProcedureContainer.of(
|
||||||
effects,
|
effects,
|
||||||
this.manifest.id,
|
this.manifest.id,
|
||||||
setConfigValue,
|
setConfigValue,
|
||||||
this.manifest.volumes,
|
this.manifest.volumes,
|
||||||
|
`Properties - ${commands.join(" ")}`,
|
||||||
)
|
)
|
||||||
const properties = matchProperties.unsafeCast(
|
const properties = matchProperties.unsafeCast(
|
||||||
JSON.parse(
|
JSON.parse(
|
||||||
(
|
(await container.execFail(commands, timeoutMs)).stdout.toString(),
|
||||||
await container.execFail(
|
|
||||||
[setConfigValue.entrypoint, ...setConfigValue.args],
|
|
||||||
timeoutMs,
|
|
||||||
)
|
|
||||||
).stdout.toString(),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return asProperty(properties.data)
|
return asProperty(properties.data)
|
||||||
@@ -761,6 +750,7 @@ export class SystemForEmbassy implements System {
|
|||||||
this.manifest.id,
|
this.manifest.id,
|
||||||
actionProcedure,
|
actionProcedure,
|
||||||
this.manifest.volumes,
|
this.manifest.volumes,
|
||||||
|
`Action ${actionId}`,
|
||||||
{
|
{
|
||||||
subcontainer,
|
subcontainer,
|
||||||
},
|
},
|
||||||
@@ -801,23 +791,20 @@ export class SystemForEmbassy implements System {
|
|||||||
const actionProcedure = this.manifest.dependencies?.[id]?.config?.check
|
const actionProcedure = this.manifest.dependencies?.[id]?.config?.check
|
||||||
if (!actionProcedure) return { message: "Action not found", value: null }
|
if (!actionProcedure) return { message: "Action not found", value: null }
|
||||||
if (actionProcedure.type === "docker") {
|
if (actionProcedure.type === "docker") {
|
||||||
|
const commands = [
|
||||||
|
actionProcedure.entrypoint,
|
||||||
|
...actionProcedure.args,
|
||||||
|
JSON.stringify(oldConfig),
|
||||||
|
]
|
||||||
const container = await DockerProcedureContainer.of(
|
const container = await DockerProcedureContainer.of(
|
||||||
effects,
|
effects,
|
||||||
this.manifest.id,
|
this.manifest.id,
|
||||||
actionProcedure,
|
actionProcedure,
|
||||||
this.manifest.volumes,
|
this.manifest.volumes,
|
||||||
|
`Dependencies Check - ${commands.join(" ")}`,
|
||||||
)
|
)
|
||||||
return JSON.parse(
|
return JSON.parse(
|
||||||
(
|
(await container.execFail(commands, timeoutMs)).stdout.toString(),
|
||||||
await container.execFail(
|
|
||||||
[
|
|
||||||
actionProcedure.entrypoint,
|
|
||||||
...actionProcedure.args,
|
|
||||||
JSON.stringify(oldConfig),
|
|
||||||
],
|
|
||||||
timeoutMs,
|
|
||||||
)
|
|
||||||
).stdout.toString(),
|
|
||||||
)
|
)
|
||||||
} else if (actionProcedure.type === "script") {
|
} else if (actionProcedure.type === "script") {
|
||||||
const moduleCode = await this.moduleCode
|
const moduleCode = await this.moduleCode
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ export const polyfillEffects = (
|
|||||||
manifest.id,
|
manifest.id,
|
||||||
manifest.main,
|
manifest.main,
|
||||||
manifest.volumes,
|
manifest.volumes,
|
||||||
|
[input.command, ...(input.args || [])].join(" "),
|
||||||
)
|
)
|
||||||
const daemon = promiseSubcontainer.then((subcontainer) =>
|
const daemon = promiseSubcontainer.then((subcontainer) =>
|
||||||
daemons.runCommand()(
|
daemons.runCommand()(
|
||||||
|
|||||||
44
core/Cargo.lock
generated
44
core/Cargo.lock
generated
@@ -533,7 +533,7 @@ dependencies = [
|
|||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"shlex",
|
"shlex",
|
||||||
"syn 2.0.74",
|
"syn 2.0.74",
|
||||||
"which 4.4.2",
|
"which",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3055,18 +3055,6 @@ dependencies = [
|
|||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nix"
|
|
||||||
version = "0.20.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"cc",
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.24.3"
|
version = "0.24.3"
|
||||||
@@ -5147,11 +5135,9 @@ dependencies = [
|
|||||||
"tty-spawn",
|
"tty-spawn",
|
||||||
"typed-builder",
|
"typed-builder",
|
||||||
"unix-named-pipe",
|
"unix-named-pipe",
|
||||||
"unshare",
|
|
||||||
"url",
|
"url",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
"uuid",
|
"uuid",
|
||||||
"which 6.0.3",
|
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -6051,16 +6037,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unshare"
|
|
||||||
version = "0.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1ceda295552a1eda89f8a748237654ad76b9c87e383fc07af5c4e423eb8e7b9b"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"nix 0.20.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -6278,18 +6254,6 @@ dependencies = [
|
|||||||
"rustix",
|
"rustix",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "which"
|
|
||||||
version = "6.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
"home",
|
|
||||||
"rustix",
|
|
||||||
"winsafe",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "whoami"
|
name = "whoami"
|
||||||
version = "1.5.1"
|
version = "1.5.1"
|
||||||
@@ -6516,12 +6480,6 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winsafe"
|
|
||||||
version = "0.0.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wyz"
|
name = "wyz"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
cli = []
|
cli = []
|
||||||
container-runtime = ["procfs", "unshare", "tty-spawn"]
|
container-runtime = ["procfs", "tty-spawn"]
|
||||||
daemon = []
|
daemon = []
|
||||||
registry = []
|
registry = []
|
||||||
default = ["cli", "daemon", "registry", "container-runtime"]
|
default = ["cli", "daemon", "registry", "container-runtime"]
|
||||||
@@ -207,9 +207,7 @@ trust-dns-server = "0.23.1"
|
|||||||
ts-rs = { git = "https://github.com/dr-bonez/ts-rs.git", branch = "feature/top-level-as" } # "8.1.0"
|
ts-rs = { git = "https://github.com/dr-bonez/ts-rs.git", branch = "feature/top-level-as" } # "8.1.0"
|
||||||
tty-spawn = { version = "0.4.0", optional = true }
|
tty-spawn = { version = "0.4.0", optional = true }
|
||||||
typed-builder = "0.18.0"
|
typed-builder = "0.18.0"
|
||||||
which = "6.0.3"
|
|
||||||
unix-named-pipe = "0.2.0"
|
unix-named-pipe = "0.2.0"
|
||||||
unshare = { version = "0.7.0", optional = true }
|
|
||||||
url = { version = "2.4.1", features = ["serde"] }
|
url = { version = "2.4.1", features = ["serde"] }
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
uuid = { version = "1.4.1", features = ["v4"] }
|
uuid = { version = "1.4.1", features = ["v4"] }
|
||||||
|
|||||||
@@ -275,8 +275,8 @@ pub struct Session {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct SessionList {
|
pub struct SessionList {
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string | null")]
|
||||||
current: InternedString,
|
current: Option<InternedString>,
|
||||||
sessions: Sessions,
|
sessions: Sessions,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +323,7 @@ fn display_sessions(params: WithIoFormat<ListParams>, arg: SessionList) {
|
|||||||
session.user_agent.as_deref().unwrap_or("N/A"),
|
session.user_agent.as_deref().unwrap_or("N/A"),
|
||||||
&format!("{}", session.metadata),
|
&format!("{}", session.metadata),
|
||||||
];
|
];
|
||||||
if id == arg.current {
|
if Some(id) == arg.current {
|
||||||
row.iter_mut()
|
row.iter_mut()
|
||||||
.map(|c| c.style(Attr::ForegroundColor(color::GREEN)))
|
.map(|c| c.style(Attr::ForegroundColor(color::GREEN)))
|
||||||
.collect::<()>()
|
.collect::<()>()
|
||||||
@@ -340,7 +340,7 @@ pub struct ListParams {
|
|||||||
#[arg(skip)]
|
#[arg(skip)]
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_session")] // from Auth middleware
|
#[serde(rename = "__auth_session")] // from Auth middleware
|
||||||
session: InternedString,
|
session: Option<InternedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[command(display(display_sessions))]
|
// #[command(display(display_sessions))]
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ pub struct RpcContextSeed {
|
|||||||
pub shutdown: broadcast::Sender<Option<Shutdown>>,
|
pub shutdown: broadcast::Sender<Option<Shutdown>>,
|
||||||
pub tor_socks: SocketAddr,
|
pub tor_socks: SocketAddr,
|
||||||
pub lxc_manager: Arc<LxcManager>,
|
pub lxc_manager: Arc<LxcManager>,
|
||||||
pub open_authed_continuations: OpenAuthedContinuations<InternedString>,
|
pub open_authed_continuations: OpenAuthedContinuations<Option<InternedString>>,
|
||||||
pub rpc_continuations: RpcContinuations,
|
pub rpc_continuations: RpcContinuations,
|
||||||
pub callbacks: ServiceCallbacks,
|
pub callbacks: ServiceCallbacks,
|
||||||
pub wifi_manager: Option<Arc<RwLock<WpaCli>>>,
|
pub wifi_manager: Option<Arc<RwLock<WpaCli>>>,
|
||||||
@@ -431,8 +431,8 @@ impl AsRef<RpcContinuations> for RpcContext {
|
|||||||
&self.rpc_continuations
|
&self.rpc_continuations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl AsRef<OpenAuthedContinuations<InternedString>> for RpcContext {
|
impl AsRef<OpenAuthedContinuations<Option<InternedString>>> for RpcContext {
|
||||||
fn as_ref(&self) -> &OpenAuthedContinuations<InternedString> {
|
fn as_ref(&self) -> &OpenAuthedContinuations<Option<InternedString>> {
|
||||||
&self.open_authed_continuations
|
&self.open_authed_continuations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ pub struct SubscribeParams {
|
|||||||
pointer: Option<JsonPointer>,
|
pointer: Option<JsonPointer>,
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_session")]
|
#[serde(rename = "__auth_session")]
|
||||||
session: InternedString,
|
session: Option<InternedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, TS)]
|
#[derive(Deserialize, Serialize, TS)]
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ pub async fn install(
|
|||||||
pub struct SideloadParams {
|
pub struct SideloadParams {
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_session")]
|
#[serde(rename = "__auth_session")]
|
||||||
session: InternedString,
|
session: Option<InternedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, TS)]
|
#[derive(Deserialize, Serialize, TS)]
|
||||||
|
|||||||
@@ -323,6 +323,13 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
|||||||
"connect",
|
"connect",
|
||||||
from_fn_async(service::connect_rpc_cli).no_display(),
|
from_fn_async(service::connect_rpc_cli).no_display(),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
"attach",
|
||||||
|
from_fn_async(service::attach)
|
||||||
|
.with_metadata("get_session", Value::Bool(true))
|
||||||
|
.no_cli(),
|
||||||
|
)
|
||||||
|
.subcommand("attach", from_fn_async(service::cli_attach).no_display())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diagnostic_api() -> ParentHandler<DiagnosticContext> {
|
pub fn diagnostic_api() -> ParentHandler<DiagnosticContext> {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ impl HasLoggedOutSessions {
|
|||||||
.map(|s| s.as_logout_session_id())
|
.map(|s| s.as_logout_session_id())
|
||||||
.collect();
|
.collect();
|
||||||
for sid in &to_log_out {
|
for sid in &to_log_out {
|
||||||
ctx.open_authed_continuations.kill(sid)
|
ctx.open_authed_continuations.kill(&Some(sid.clone()))
|
||||||
}
|
}
|
||||||
ctx.ephemeral_sessions.mutate(|s| {
|
ctx.ephemeral_sessions.mutate(|s| {
|
||||||
for sid in &to_log_out {
|
for sid in &to_log_out {
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use imbl_value::InternedString;
|
||||||
use models::ImageId;
|
use models::ImageId;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
|
|
||||||
use crate::rpc_continuations::Guid;
|
use crate::rpc_continuations::Guid;
|
||||||
use crate::service::effects::prelude::*;
|
use crate::service::effects::prelude::*;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
|
use crate::{
|
||||||
|
disk::mount::filesystem::overlayfs::OverlayGuard, service::persistent_container::Subcontainer,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "container-runtime")]
|
#[cfg(feature = "container-runtime")]
|
||||||
mod sync;
|
mod sync;
|
||||||
@@ -38,7 +41,7 @@ pub async fn destroy_subcontainer_fs(
|
|||||||
.await
|
.await
|
||||||
.remove(&guid)
|
.remove(&guid)
|
||||||
{
|
{
|
||||||
overlay.unmount(true).await?;
|
overlay.overlay.unmount(true).await?;
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!("Could not find a subcontainer fs to destroy; assumming that it already is destroyed and will be skipping");
|
tracing::warn!("Could not find a subcontainer fs to destroy; assumming that it already is destroyed and will be skipping");
|
||||||
}
|
}
|
||||||
@@ -50,11 +53,13 @@ pub async fn destroy_subcontainer_fs(
|
|||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct CreateSubcontainerFsParams {
|
pub struct CreateSubcontainerFsParams {
|
||||||
image_id: ImageId,
|
image_id: ImageId,
|
||||||
|
#[ts(type = "string | null")]
|
||||||
|
name: Option<InternedString>,
|
||||||
}
|
}
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn create_subcontainer_fs(
|
pub async fn create_subcontainer_fs(
|
||||||
context: EffectContext,
|
context: EffectContext,
|
||||||
CreateSubcontainerFsParams { image_id }: CreateSubcontainerFsParams,
|
CreateSubcontainerFsParams { image_id, name }: CreateSubcontainerFsParams,
|
||||||
) -> Result<(PathBuf, Guid), Error> {
|
) -> Result<(PathBuf, Guid), Error> {
|
||||||
let context = context.deref()?;
|
let context = context.deref()?;
|
||||||
if let Some(image) = context
|
if let Some(image) = context
|
||||||
@@ -87,7 +92,13 @@ pub async fn create_subcontainer_fs(
|
|||||||
.with_kind(ErrorKind::Incoherent)?,
|
.with_kind(ErrorKind::Incoherent)?,
|
||||||
);
|
);
|
||||||
tracing::info!("Mounting overlay {guid} for {image_id}");
|
tracing::info!("Mounting overlay {guid} for {image_id}");
|
||||||
let guard = OverlayGuard::mount(image, &mountpoint).await?;
|
let subcontainer_wrapper = Subcontainer {
|
||||||
|
overlay: OverlayGuard::mount(image, &mountpoint).await?,
|
||||||
|
name: name
|
||||||
|
.unwrap_or_else(|| InternedString::intern(format!("subcontainer-{}", image_id))),
|
||||||
|
image_id: image_id.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
Command::new("chown")
|
Command::new("chown")
|
||||||
.arg("100000:100000")
|
.arg("100000:100000")
|
||||||
.arg(&mountpoint)
|
.arg(&mountpoint)
|
||||||
@@ -100,7 +111,7 @@ pub async fn create_subcontainer_fs(
|
|||||||
.subcontainers
|
.subcontainers
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.insert(guid.clone(), guard);
|
.insert(guid.clone(), subcontainer_wrapper);
|
||||||
Ok((container_mountpoint, guid))
|
Ok((container_mountpoint, guid))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::new(
|
Err(Error::new(
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::ffi::{c_int, OsStr, OsString};
|
use std::ffi::{c_int, OsStr, OsString};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@@ -12,7 +11,6 @@ use nix::unistd::Pid;
|
|||||||
use signal_hook::consts::signal::*;
|
use signal_hook::consts::signal::*;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
use tty_spawn::TtySpawn;
|
use tty_spawn::TtySpawn;
|
||||||
use unshare::Command as NSCommand;
|
|
||||||
|
|
||||||
use crate::service::effects::prelude::*;
|
use crate::service::effects::prelude::*;
|
||||||
use crate::service::effects::ContainerCliContext;
|
use crate::service::effects::ContainerCliContext;
|
||||||
@@ -50,11 +48,13 @@ fn open_file_read(path: impl AsRef<Path>) -> Result<File, Error> {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Parser)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Parser)]
|
||||||
pub struct ExecParams {
|
pub struct ExecParams {
|
||||||
#[arg(short = 'e', long = "env")]
|
#[arg(long)]
|
||||||
|
force_tty: bool,
|
||||||
|
#[arg(short, long)]
|
||||||
env: Option<PathBuf>,
|
env: Option<PathBuf>,
|
||||||
#[arg(short = 'w', long = "workdir")]
|
#[arg(short, long)]
|
||||||
workdir: Option<PathBuf>,
|
workdir: Option<PathBuf>,
|
||||||
#[arg(short = 'u', long = "user")]
|
#[arg(short, long)]
|
||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
chroot: PathBuf,
|
chroot: PathBuf,
|
||||||
#[arg(trailing_var_arg = true)]
|
#[arg(trailing_var_arg = true)]
|
||||||
@@ -68,6 +68,7 @@ impl ExecParams {
|
|||||||
user,
|
user,
|
||||||
chroot,
|
chroot,
|
||||||
command,
|
command,
|
||||||
|
..
|
||||||
} = self;
|
} = self;
|
||||||
let Some(([command], args)) = command.split_at_checked(1) else {
|
let Some(([command], args)) = command.split_at_checked(1) else {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
@@ -88,16 +89,6 @@ impl ExecParams {
|
|||||||
.collect::<BTreeMap<_, _>>();
|
.collect::<BTreeMap<_, _>>();
|
||||||
std::os::unix::fs::chroot(chroot)
|
std::os::unix::fs::chroot(chroot)
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("chroot {chroot:?}")))?;
|
.with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("chroot {chroot:?}")))?;
|
||||||
let command = which::which_in(
|
|
||||||
command,
|
|
||||||
env.get("PATH")
|
|
||||||
.copied()
|
|
||||||
.map(Cow::Borrowed)
|
|
||||||
.or_else(|| std::env::var("PATH").ok().map(Cow::Owned))
|
|
||||||
.as_deref(),
|
|
||||||
workdir.as_deref().unwrap_or(Path::new("/")),
|
|
||||||
)
|
|
||||||
.with_kind(ErrorKind::Filesystem)?;
|
|
||||||
let mut cmd = StdCommand::new(command);
|
let mut cmd = StdCommand::new(command);
|
||||||
cmd.args(args);
|
cmd.args(args);
|
||||||
for (k, v) in env {
|
for (k, v) in env {
|
||||||
@@ -135,6 +126,7 @@ impl ExecParams {
|
|||||||
pub fn launch(
|
pub fn launch(
|
||||||
_: ContainerCliContext,
|
_: ContainerCliContext,
|
||||||
ExecParams {
|
ExecParams {
|
||||||
|
force_tty,
|
||||||
env,
|
env,
|
||||||
workdir,
|
workdir,
|
||||||
user,
|
user,
|
||||||
@@ -142,47 +134,8 @@ pub fn launch(
|
|||||||
command,
|
command,
|
||||||
}: ExecParams,
|
}: ExecParams,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
use unshare::{Namespace, Stdio};
|
|
||||||
|
|
||||||
use crate::service::cli::ContainerCliContext;
|
|
||||||
let mut sig = signal_hook::iterator::Signals::new(FWD_SIGNALS)?;
|
|
||||||
let mut cmd = NSCommand::new("/usr/bin/start-cli");
|
|
||||||
cmd.arg("subcontainer").arg("launch-init");
|
|
||||||
if let Some(env) = env {
|
|
||||||
cmd.arg("--env").arg(env);
|
|
||||||
}
|
|
||||||
if let Some(workdir) = workdir {
|
|
||||||
cmd.arg("--workdir").arg(workdir);
|
|
||||||
}
|
|
||||||
if let Some(user) = user {
|
|
||||||
cmd.arg("--user").arg(user);
|
|
||||||
}
|
|
||||||
cmd.arg(&chroot);
|
|
||||||
cmd.args(&command);
|
|
||||||
cmd.unshare(&[Namespace::Pid, Namespace::Cgroup, Namespace::Ipc]);
|
|
||||||
cmd.stdin(Stdio::piped());
|
|
||||||
cmd.stdout(Stdio::piped());
|
|
||||||
cmd.stderr(Stdio::piped());
|
|
||||||
let (stdin_send, stdin_recv) = oneshot::channel();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
if let Ok(mut stdin) = stdin_recv.blocking_recv() {
|
|
||||||
std::io::copy(&mut std::io::stdin(), &mut stdin).unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let (stdout_send, stdout_recv) = oneshot::channel();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
if let Ok(mut stdout) = stdout_recv.blocking_recv() {
|
|
||||||
std::io::copy(&mut stdout, &mut std::io::stdout()).unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let (stderr_send, stderr_recv) = oneshot::channel();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
if let Ok(mut stderr) = stderr_recv.blocking_recv() {
|
|
||||||
std::io::copy(&mut stderr, &mut std::io::stderr()).unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if chroot.join("proc/1").exists() {
|
if chroot.join("proc/1").exists() {
|
||||||
let ns_id = procfs::process::Process::new_with_root(chroot.join("proc"))
|
let ns_id = procfs::process::Process::new_with_root(chroot.join("proc/1"))
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, "open subcontainer procfs"))?
|
.with_ctx(|_| (ErrorKind::Filesystem, "open subcontainer procfs"))?
|
||||||
.namespaces()
|
.namespaces()
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, "read subcontainer pid 1 ns"))?
|
.with_ctx(|_| (ErrorKind::Filesystem, "read subcontainer pid 1 ns"))?
|
||||||
@@ -225,20 +178,92 @@ pub fn launch(
|
|||||||
nix::mount::umount(&chroot.join("proc"))
|
nix::mount::umount(&chroot.join("proc"))
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, "unmounting subcontainer procfs"))?;
|
.with_ctx(|_| (ErrorKind::Filesystem, "unmounting subcontainer procfs"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (std::io::stdin().is_terminal()
|
||||||
|
&& std::io::stdout().is_terminal()
|
||||||
|
&& std::io::stderr().is_terminal())
|
||||||
|
|| force_tty
|
||||||
|
{
|
||||||
|
let mut cmd = TtySpawn::new("/usr/bin/start-cli");
|
||||||
|
cmd.arg("subcontainer").arg("launch-init");
|
||||||
|
if let Some(env) = env {
|
||||||
|
cmd.arg("--env").arg(env);
|
||||||
|
}
|
||||||
|
if let Some(workdir) = workdir {
|
||||||
|
cmd.arg("--workdir").arg(workdir);
|
||||||
|
}
|
||||||
|
if let Some(user) = user {
|
||||||
|
cmd.arg("--user").arg(user);
|
||||||
|
}
|
||||||
|
cmd.arg(&chroot);
|
||||||
|
cmd.args(command.iter());
|
||||||
|
nix::sched::unshare(CloneFlags::CLONE_NEWPID)
|
||||||
|
.with_ctx(|_| (ErrorKind::Filesystem, "unshare pid ns"))?;
|
||||||
|
nix::sched::unshare(CloneFlags::CLONE_NEWCGROUP)
|
||||||
|
.with_ctx(|_| (ErrorKind::Filesystem, "unshare cgroup ns"))?;
|
||||||
|
nix::sched::unshare(CloneFlags::CLONE_NEWIPC)
|
||||||
|
.with_ctx(|_| (ErrorKind::Filesystem, "unshare ipc ns"))?;
|
||||||
|
std::process::exit(cmd.spawn().with_kind(ErrorKind::Filesystem)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sig = signal_hook::iterator::Signals::new(FWD_SIGNALS)?;
|
||||||
|
let (send_pid, recv_pid) = oneshot::channel();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
if let Ok(pid) = recv_pid.blocking_recv() {
|
||||||
|
for sig in sig.forever() {
|
||||||
|
nix::sys::signal::kill(
|
||||||
|
Pid::from_raw(pid),
|
||||||
|
Some(nix::sys::signal::Signal::try_from(sig).unwrap()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let mut cmd = StdCommand::new("/usr/bin/start-cli");
|
||||||
|
cmd.arg("subcontainer").arg("launch-init");
|
||||||
|
if let Some(env) = env {
|
||||||
|
cmd.arg("--env").arg(env);
|
||||||
|
}
|
||||||
|
if let Some(workdir) = workdir {
|
||||||
|
cmd.arg("--workdir").arg(workdir);
|
||||||
|
}
|
||||||
|
if let Some(user) = user {
|
||||||
|
cmd.arg("--user").arg(user);
|
||||||
|
}
|
||||||
|
cmd.arg(&chroot);
|
||||||
|
cmd.args(&command);
|
||||||
|
cmd.stdin(Stdio::piped());
|
||||||
|
cmd.stdout(Stdio::piped());
|
||||||
|
cmd.stderr(Stdio::piped());
|
||||||
|
let (stdin_send, stdin_recv) = oneshot::channel();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
if let Ok(mut stdin) = stdin_recv.blocking_recv() {
|
||||||
|
std::io::copy(&mut std::io::stdin(), &mut stdin).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let (stdout_send, stdout_recv) = oneshot::channel();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
if let Ok(mut stdout) = stdout_recv.blocking_recv() {
|
||||||
|
std::io::copy(&mut stdout, &mut std::io::stdout()).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let (stderr_send, stderr_recv) = oneshot::channel();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
if let Ok(mut stderr) = stderr_recv.blocking_recv() {
|
||||||
|
std::io::copy(&mut stderr, &mut std::io::stderr()).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
nix::sched::unshare(CloneFlags::CLONE_NEWPID)
|
||||||
|
.with_ctx(|_| (ErrorKind::Filesystem, "unshare pid ns"))?;
|
||||||
|
nix::sched::unshare(CloneFlags::CLONE_NEWCGROUP)
|
||||||
|
.with_ctx(|_| (ErrorKind::Filesystem, "unshare cgroup ns"))?;
|
||||||
|
nix::sched::unshare(CloneFlags::CLONE_NEWIPC)
|
||||||
|
.with_ctx(|_| (ErrorKind::Filesystem, "unshare ipc ns"))?;
|
||||||
let mut child = cmd
|
let mut child = cmd
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(color_eyre::eyre::Report::msg)
|
.map_err(color_eyre::eyre::Report::msg)
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, "spawning child process"))?;
|
.with_ctx(|_| (ErrorKind::Filesystem, "spawning child process"))?;
|
||||||
let pid = child.pid();
|
send_pid.send(child.id() as i32).unwrap_or_default();
|
||||||
std::thread::spawn(move || {
|
|
||||||
for sig in sig.forever() {
|
|
||||||
nix::sys::signal::kill(
|
|
||||||
Pid::from_raw(pid),
|
|
||||||
Some(nix::sys::signal::Signal::try_from(sig).unwrap()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
stdin_send
|
stdin_send
|
||||||
.send(child.stdin.take().unwrap())
|
.send(child.stdin.take().unwrap())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
@@ -253,16 +278,16 @@ pub fn launch(
|
|||||||
.wait()
|
.wait()
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, "waiting on child process"))?;
|
.with_ctx(|_| (ErrorKind::Filesystem, "waiting on child process"))?;
|
||||||
if let Some(code) = exit.code() {
|
if let Some(code) = exit.code() {
|
||||||
|
nix::mount::umount(&chroot.join("proc"))
|
||||||
|
.with_ctx(|_| (ErrorKind::Filesystem, "umount procfs"))?;
|
||||||
std::process::exit(code);
|
std::process::exit(code);
|
||||||
|
} else if exit.success() {
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
if exit.success() {
|
Err(Error::new(
|
||||||
Ok(())
|
color_eyre::eyre::Report::msg(exit),
|
||||||
} else {
|
ErrorKind::Unknown,
|
||||||
Err(Error::new(
|
))
|
||||||
color_eyre::eyre::Report::msg(exit),
|
|
||||||
ErrorKind::Unknown,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,6 +313,7 @@ pub fn launch_init(_: ContainerCliContext, params: ExecParams) -> Result<(), Err
|
|||||||
pub fn exec(
|
pub fn exec(
|
||||||
_: ContainerCliContext,
|
_: ContainerCliContext,
|
||||||
ExecParams {
|
ExecParams {
|
||||||
|
force_tty,
|
||||||
env,
|
env,
|
||||||
workdir,
|
workdir,
|
||||||
user,
|
user,
|
||||||
@@ -295,7 +321,11 @@ pub fn exec(
|
|||||||
command,
|
command,
|
||||||
}: ExecParams,
|
}: ExecParams,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if std::io::stdin().is_terminal() {
|
if (std::io::stdin().is_terminal()
|
||||||
|
&& std::io::stdout().is_terminal()
|
||||||
|
&& std::io::stderr().is_terminal())
|
||||||
|
|| force_tty
|
||||||
|
{
|
||||||
let mut cmd = TtySpawn::new("/usr/bin/start-cli");
|
let mut cmd = TtySpawn::new("/usr/bin/start-cli");
|
||||||
cmd.arg("subcontainer").arg("exec-command");
|
cmd.arg("subcontainer").arg("exec-command");
|
||||||
if let Some(env) = env {
|
if let Some(env) = env {
|
||||||
@@ -407,15 +437,13 @@ pub fn exec(
|
|||||||
.with_ctx(|_| (ErrorKind::Filesystem, "waiting on child process"))?;
|
.with_ctx(|_| (ErrorKind::Filesystem, "waiting on child process"))?;
|
||||||
if let Some(code) = exit.code() {
|
if let Some(code) = exit.code() {
|
||||||
std::process::exit(code);
|
std::process::exit(code);
|
||||||
|
} else if exit.success() {
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
if exit.success() {
|
Err(Error::new(
|
||||||
Ok(())
|
color_eyre::eyre::Report::msg(exit),
|
||||||
} else {
|
ErrorKind::Unknown,
|
||||||
Err(Error::new(
|
))
|
||||||
color_eyre::eyre::Report::msg(exit),
|
|
||||||
ErrorKind::Unknown,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,31 @@
|
|||||||
|
use std::io::IsTerminal;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use std::os::unix::process::ExitStatusExt;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Stdio;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use std::{ffi::OsString, path::PathBuf};
|
||||||
|
|
||||||
|
use axum::extract::ws::WebSocket;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use imbl::OrdMap;
|
use futures::stream::FusedStream;
|
||||||
use models::{HealthCheckId, PackageId, ProcedureName};
|
use futures::{SinkExt, StreamExt, TryStreamExt};
|
||||||
use persistent_container::PersistentContainer;
|
use imbl_value::{json, InternedString};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use models::{ImageId, PackageId, ProcedureName};
|
||||||
|
use nix::sys::signal::Signal;
|
||||||
|
use persistent_container::{PersistentContainer, Subcontainer};
|
||||||
use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, HandlerArgs, HandlerFor};
|
use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, HandlerArgs, HandlerFor};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use service_actor::ServiceActor;
|
use service_actor::ServiceActor;
|
||||||
use start_stop::StartStop;
|
use start_stop::StartStop;
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tokio::process::Command;
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
|
use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
@@ -24,15 +37,16 @@ use crate::install::PKG_ARCHIVE_DIR;
|
|||||||
use crate::lxc::ContainerId;
|
use crate::lxc::ContainerId;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{NamedProgress, Progress};
|
use crate::progress::{NamedProgress, Progress};
|
||||||
use crate::rpc_continuations::Guid;
|
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||||
use crate::s9pk::S9pk;
|
use crate::s9pk::S9pk;
|
||||||
use crate::service::service_map::InstallProgressHandles;
|
use crate::service::service_map::InstallProgressHandles;
|
||||||
use crate::status::health_check::NamedHealthCheckResult;
|
|
||||||
use crate::util::actor::concurrent::ConcurrentActor;
|
use crate::util::actor::concurrent::ConcurrentActor;
|
||||||
use crate::util::io::create_file;
|
use crate::util::io::{create_file, AsyncReadStream};
|
||||||
|
use crate::util::net::WebSocketExt;
|
||||||
use crate::util::serde::{NoOutput, Pem};
|
use crate::util::serde::{NoOutput, Pem};
|
||||||
use crate::util::Never;
|
use crate::util::Never;
|
||||||
use crate::volume::data_dir;
|
use crate::volume::data_dir;
|
||||||
|
use crate::CAP_1_KiB;
|
||||||
|
|
||||||
mod action;
|
mod action;
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
@@ -68,6 +82,8 @@ pub enum LoadDisposition {
|
|||||||
Undo,
|
Undo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct RootCommand(pub String);
|
||||||
|
|
||||||
pub struct ServiceRef(Arc<Service>);
|
pub struct ServiceRef(Arc<Service>);
|
||||||
impl ServiceRef {
|
impl ServiceRef {
|
||||||
pub fn weak(&self) -> Weak<Service> {
|
pub fn weak(&self) -> Weak<Service> {
|
||||||
@@ -183,7 +199,7 @@ impl ServiceRef {
|
|||||||
impl Deref for ServiceRef {
|
impl Deref for ServiceRef {
|
||||||
type Target = Service;
|
type Target = Service;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&*self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<Service> for ServiceRef {
|
impl From<Service> for ServiceRef {
|
||||||
@@ -354,7 +370,7 @@ impl Service {
|
|||||||
tracing::debug!("{e:?}")
|
tracing::debug!("{e:?}")
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
match ServiceRef::from(service).uninstall(None).await {
|
match service.uninstall(None).await {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("Error uninstalling service: {e}");
|
tracing::error!("Error uninstalling service: {e}");
|
||||||
tracing::debug!("{e:?}")
|
tracing::debug!("{e:?}")
|
||||||
@@ -578,3 +594,488 @@ pub async fn connect_rpc_cli(
|
|||||||
|
|
||||||
crate::lxc::connect_cli(&ctx, guid).await
|
crate::lxc::connect_cli(&ctx, guid).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct AttachParams {
|
||||||
|
pub id: PackageId,
|
||||||
|
#[ts(type = "string[]")]
|
||||||
|
pub command: Vec<OsString>,
|
||||||
|
pub tty: bool,
|
||||||
|
#[ts(skip)]
|
||||||
|
#[serde(rename = "__auth_session")]
|
||||||
|
session: Option<InternedString>,
|
||||||
|
#[ts(type = "string | null")]
|
||||||
|
subcontainer: Option<InternedString>,
|
||||||
|
#[ts(type = "string | null")]
|
||||||
|
name: Option<InternedString>,
|
||||||
|
#[ts(type = "string | null")]
|
||||||
|
image_id: Option<ImageId>,
|
||||||
|
}
|
||||||
|
pub async fn attach(
|
||||||
|
ctx: RpcContext,
|
||||||
|
AttachParams {
|
||||||
|
id,
|
||||||
|
command,
|
||||||
|
tty,
|
||||||
|
session,
|
||||||
|
subcontainer,
|
||||||
|
image_id,
|
||||||
|
name,
|
||||||
|
}: AttachParams,
|
||||||
|
) -> Result<Guid, Error> {
|
||||||
|
let (container_id, subcontainer_id, image_id, workdir, root_command) = {
|
||||||
|
let id = &id;
|
||||||
|
|
||||||
|
let service = ctx.services.get(id).await;
|
||||||
|
|
||||||
|
let service_ref = service.as_ref().or_not_found(id)?;
|
||||||
|
|
||||||
|
let container = &service_ref.seed.persistent_container;
|
||||||
|
let root_dir = container
|
||||||
|
.lxc_container
|
||||||
|
.get()
|
||||||
|
.map(|x| x.rootfs_dir().to_owned())
|
||||||
|
.or_not_found(format!("container for {id}"))?;
|
||||||
|
|
||||||
|
let subcontainer = subcontainer.map(|x| AsRef::<str>::as_ref(&x).to_uppercase());
|
||||||
|
let name = name.map(|x| AsRef::<str>::as_ref(&x).to_uppercase());
|
||||||
|
let image_id = image_id.map(|x| AsRef::<Path>::as_ref(&x).to_string_lossy().to_uppercase());
|
||||||
|
|
||||||
|
let subcontainers = container.subcontainers.lock().await;
|
||||||
|
let subcontainer_ids: Vec<_> = subcontainers
|
||||||
|
.iter()
|
||||||
|
.filter(|(x, wrapper)| {
|
||||||
|
if let Some(subcontainer) = subcontainer.as_ref() {
|
||||||
|
AsRef::<str>::as_ref(x).contains(AsRef::<str>::as_ref(subcontainer))
|
||||||
|
} else if let Some(name) = name.as_ref() {
|
||||||
|
AsRef::<str>::as_ref(&wrapper.name)
|
||||||
|
.to_uppercase()
|
||||||
|
.contains(AsRef::<str>::as_ref(name))
|
||||||
|
} else if let Some(image_id) = image_id.as_ref() {
|
||||||
|
let Some(wrapper_image_id) = AsRef::<Path>::as_ref(&wrapper.image_id).to_str()
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
wrapper_image_id
|
||||||
|
.to_uppercase()
|
||||||
|
.contains(AsRef::<str>::as_ref(&image_id))
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let format_subcontainer_pair = |(guid, wrapper): (&Guid, &Subcontainer)| {
|
||||||
|
format!(
|
||||||
|
"{guid} imageId: {image_id} name: \"{name}\"",
|
||||||
|
name = &wrapper.name,
|
||||||
|
image_id = &wrapper.image_id
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let Some((subcontainer_id, image_id)) = subcontainer_ids
|
||||||
|
.first()
|
||||||
|
.map::<(Guid, ImageId), _>(|&x| (x.0.clone(), x.1.image_id.clone()))
|
||||||
|
else {
|
||||||
|
drop(subcontainers);
|
||||||
|
let subcontainers = container
|
||||||
|
.subcontainers
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.map(format_subcontainer_pair)
|
||||||
|
.join("\n");
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("no matching subcontainers are running for {id}; some possible choices are:\n{subcontainers}"),
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let passwd = root_dir
|
||||||
|
.join("media/startos/subcontainers")
|
||||||
|
.join(subcontainer_id.as_ref())
|
||||||
|
.join("etc")
|
||||||
|
.join("passwd");
|
||||||
|
|
||||||
|
let root_command = get_passwd_root_command(passwd).await;
|
||||||
|
|
||||||
|
let workdir = attach_workdir(&image_id, &root_dir).await?;
|
||||||
|
|
||||||
|
if subcontainer_ids.len() > 1 {
|
||||||
|
let subcontainer_ids = subcontainer_ids
|
||||||
|
.into_iter()
|
||||||
|
.map(format_subcontainer_pair)
|
||||||
|
.join("\n");
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("multiple subcontainers found for {id}: \n{subcontainer_ids}"),
|
||||||
|
ErrorKind::InvalidRequest,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
service_ref.container_id()?,
|
||||||
|
subcontainer_id,
|
||||||
|
image_id,
|
||||||
|
workdir,
|
||||||
|
root_command,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let guid = Guid::new();
|
||||||
|
async fn handler(
|
||||||
|
ws: &mut WebSocket,
|
||||||
|
container_id: ContainerId,
|
||||||
|
subcontainer_id: Guid,
|
||||||
|
command: Vec<OsString>,
|
||||||
|
tty: bool,
|
||||||
|
image_id: ImageId,
|
||||||
|
workdir: Option<String>,
|
||||||
|
root_command: &RootCommand,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
use axum::extract::ws::Message;
|
||||||
|
|
||||||
|
let mut ws = ws.fuse();
|
||||||
|
|
||||||
|
let mut cmd = Command::new("lxc-attach");
|
||||||
|
let root_path = Path::new("/media/startos/subcontainers").join(subcontainer_id.as_ref());
|
||||||
|
cmd.kill_on_drop(true);
|
||||||
|
|
||||||
|
cmd.arg(&*container_id)
|
||||||
|
.arg("--")
|
||||||
|
.arg("start-cli")
|
||||||
|
.arg("subcontainer")
|
||||||
|
.arg("exec")
|
||||||
|
.arg("--env")
|
||||||
|
.arg(
|
||||||
|
Path::new("/media/startos/images")
|
||||||
|
.join(image_id)
|
||||||
|
.with_extension("env"),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(workdir) = workdir {
|
||||||
|
cmd.arg("--workdir").arg(workdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if tty {
|
||||||
|
cmd.arg("--force-tty");
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.arg(&root_path).arg("--");
|
||||||
|
|
||||||
|
if command.is_empty() {
|
||||||
|
cmd.arg(&root_command.0);
|
||||||
|
} else {
|
||||||
|
cmd.args(&command);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut child = cmd
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
let pid = nix::unistd::Pid::from_raw(child.id().or_not_found("child pid")? as i32);
|
||||||
|
|
||||||
|
let mut stdin = child.stdin.take().or_not_found("child stdin")?;
|
||||||
|
|
||||||
|
let mut current_in = "stdin".to_owned();
|
||||||
|
let mut current_out = "stdout";
|
||||||
|
ws.send(Message::Text(current_out.into()))
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
let mut stdout = AsyncReadStream::new(
|
||||||
|
child.stdout.take().or_not_found("child stdout")?,
|
||||||
|
4 * CAP_1_KiB,
|
||||||
|
)
|
||||||
|
.fuse();
|
||||||
|
let mut stderr = AsyncReadStream::new(
|
||||||
|
child.stderr.take().or_not_found("child stderr")?,
|
||||||
|
4 * CAP_1_KiB,
|
||||||
|
)
|
||||||
|
.fuse();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
futures::select_biased! {
|
||||||
|
out = stdout.try_next() => {
|
||||||
|
if let Some(out) = out? {
|
||||||
|
if current_out != "stdout" {
|
||||||
|
ws.send(Message::Text("stdout".into()))
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
current_out = "stdout";
|
||||||
|
}
|
||||||
|
dbg!(¤t_out);
|
||||||
|
ws.send(Message::Binary(out))
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = stderr.try_next() => {
|
||||||
|
if let Some(err) = err? {
|
||||||
|
if current_out != "stderr" {
|
||||||
|
ws.send(Message::Text("stderr".into()))
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
current_out = "stderr";
|
||||||
|
}
|
||||||
|
dbg!(¤t_out);
|
||||||
|
ws.send(Message::Binary(err))
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg = ws.try_next() => {
|
||||||
|
if let Some(msg) = msg.with_kind(ErrorKind::Network)? {
|
||||||
|
match msg {
|
||||||
|
Message::Text(in_ty) => {
|
||||||
|
current_in = in_ty;
|
||||||
|
}
|
||||||
|
Message::Binary(data) => {
|
||||||
|
match &*current_in {
|
||||||
|
"stdin" => {
|
||||||
|
stdin.write_all(&data).await?;
|
||||||
|
}
|
||||||
|
"signal" => {
|
||||||
|
if data.len() != 4 {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("invalid byte length for signal: {}", data.len()),
|
||||||
|
ErrorKind::InvalidRequest
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut sig_buf = [0u8; 4];
|
||||||
|
sig_buf.clone_from_slice(&data);
|
||||||
|
nix::sys::signal::kill(
|
||||||
|
pid,
|
||||||
|
Signal::try_from(i32::from_be_bytes(sig_buf))
|
||||||
|
.with_kind(ErrorKind::InvalidRequest)?
|
||||||
|
).with_kind(ErrorKind::Filesystem)?;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stdout.is_terminated() && stderr.is_terminated() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let exit = child.wait().await?;
|
||||||
|
ws.send(Message::Text("exit".into()))
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
ws.send(Message::Binary(i32::to_be_bytes(exit.into_raw()).to_vec()))
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
ctx.rpc_continuations
|
||||||
|
.add(
|
||||||
|
guid.clone(),
|
||||||
|
RpcContinuation::ws_authed(
|
||||||
|
&ctx,
|
||||||
|
session,
|
||||||
|
move |mut ws| async move {
|
||||||
|
if let Err(e) = handler(
|
||||||
|
&mut ws,
|
||||||
|
container_id,
|
||||||
|
subcontainer_id,
|
||||||
|
command,
|
||||||
|
tty,
|
||||||
|
image_id,
|
||||||
|
workdir,
|
||||||
|
&root_command,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!("Error in attach websocket: {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
ws.close_result(Err::<&str, _>(e)).await.log_err();
|
||||||
|
} else {
|
||||||
|
ws.normal_close("exit").await.log_err();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Duration::from_secs(30),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(guid)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn attach_workdir(image_id: &ImageId, root_dir: &Path) -> Result<Option<String>, Error> {
|
||||||
|
let path_str = root_dir.join("media/startos/images/");
|
||||||
|
|
||||||
|
let mut subcontainer_json =
|
||||||
|
tokio::fs::File::open(path_str.join(image_id).with_extension("json")).await?;
|
||||||
|
let mut contents = vec![];
|
||||||
|
subcontainer_json.read_to_end(&mut contents).await?;
|
||||||
|
let subcontainer_json: serde_json::Value =
|
||||||
|
serde_json::from_slice(&contents).with_kind(ErrorKind::Filesystem)?;
|
||||||
|
Ok(subcontainer_json["workdir"].as_str().map(|x| x.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_passwd_root_command(etc_passwd_path: PathBuf) -> RootCommand {
|
||||||
|
async {
|
||||||
|
let mut file = tokio::fs::File::open(etc_passwd_path).await?;
|
||||||
|
|
||||||
|
let mut contents = vec![];
|
||||||
|
file.read_to_end(&mut contents).await?;
|
||||||
|
|
||||||
|
let contents = String::from_utf8_lossy(&contents);
|
||||||
|
|
||||||
|
for line in contents.split('\n') {
|
||||||
|
let line_information = line.split(':').collect::<Vec<_>>();
|
||||||
|
if let (Some(&"root"), Some(shell)) =
|
||||||
|
(line_information.first(), line_information.last())
|
||||||
|
{
|
||||||
|
return Ok(shell.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("Could not parse /etc/passwd for shell: {}", contents),
|
||||||
|
ErrorKind::Filesystem,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
.map(RootCommand)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
tracing::error!("Could not get the /etc/passwd: {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
RootCommand("/bin/sh".to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Parser)]
|
||||||
|
pub struct CliAttachParams {
|
||||||
|
pub id: PackageId,
|
||||||
|
#[arg(long)]
|
||||||
|
pub force_tty: bool,
|
||||||
|
#[arg(trailing_var_arg = true)]
|
||||||
|
pub command: Vec<OsString>,
|
||||||
|
#[arg(long, short)]
|
||||||
|
subcontainer: Option<InternedString>,
|
||||||
|
#[arg(long, short)]
|
||||||
|
name: Option<InternedString>,
|
||||||
|
#[arg(long, short)]
|
||||||
|
image_id: Option<ImageId>,
|
||||||
|
}
|
||||||
|
pub async fn cli_attach(
|
||||||
|
HandlerArgs {
|
||||||
|
context,
|
||||||
|
parent_method,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
..
|
||||||
|
}: HandlerArgs<CliContext, CliAttachParams>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
|
|
||||||
|
let guid: Guid = from_value(
|
||||||
|
context
|
||||||
|
.call_remote::<RpcContext>(
|
||||||
|
&parent_method.into_iter().chain(method).join("."),
|
||||||
|
json!({
|
||||||
|
"id": params.id,
|
||||||
|
"command": params.command,
|
||||||
|
"tty": (std::io::stdin().is_terminal()
|
||||||
|
&& std::io::stdout().is_terminal()
|
||||||
|
&& std::io::stderr().is_terminal())
|
||||||
|
|| params.force_tty,
|
||||||
|
"subcontainer": params.subcontainer,
|
||||||
|
"imageId": params.image_id,
|
||||||
|
"name": params.name,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
)?;
|
||||||
|
let mut ws = context.ws_continuation(guid).await?;
|
||||||
|
|
||||||
|
let mut current_in = "stdin";
|
||||||
|
let mut current_out = "stdout".to_owned();
|
||||||
|
ws.send(Message::Text(current_in.into()))
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
let mut stdin = AsyncReadStream::new(tokio::io::stdin(), 4 * CAP_1_KiB).fuse();
|
||||||
|
let mut stdout = tokio::io::stdout();
|
||||||
|
let mut stderr = tokio::io::stderr();
|
||||||
|
loop {
|
||||||
|
futures::select_biased! {
|
||||||
|
// signal = tokio:: => {
|
||||||
|
// let exit = exit?;
|
||||||
|
// if current_out != "exit" {
|
||||||
|
// ws.send(Message::Text("exit".into()))
|
||||||
|
// .await
|
||||||
|
// .with_kind(ErrorKind::Network)?;
|
||||||
|
// current_out = "exit";
|
||||||
|
// }
|
||||||
|
// ws.send(Message::Binary(
|
||||||
|
// i32::to_be_bytes(exit.into_raw()).to_vec()
|
||||||
|
// )).await.with_kind(ErrorKind::Network)?;
|
||||||
|
// }
|
||||||
|
input = stdin.try_next() => {
|
||||||
|
if let Some(input) = input? {
|
||||||
|
if current_in != "stdin" {
|
||||||
|
ws.send(Message::Text("stdin".into()))
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
current_in = "stdin";
|
||||||
|
}
|
||||||
|
ws.send(Message::Binary(input))
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg = ws.try_next() => {
|
||||||
|
if let Some(msg) = msg.with_kind(ErrorKind::Network)? {
|
||||||
|
match msg {
|
||||||
|
Message::Text(out_ty) => {
|
||||||
|
current_out = out_ty;
|
||||||
|
}
|
||||||
|
Message::Binary(data) => {
|
||||||
|
match &*current_out {
|
||||||
|
"stdout" => {
|
||||||
|
stdout.write_all(&data).await?;
|
||||||
|
stdout.flush().await?;
|
||||||
|
}
|
||||||
|
"stderr" => {
|
||||||
|
stderr.write_all(&data).await?;
|
||||||
|
stderr.flush().await?;
|
||||||
|
}
|
||||||
|
"exit" => {
|
||||||
|
if data.len() != 4 {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("invalid byte length for exit code: {}", data.len()),
|
||||||
|
ErrorKind::InvalidRequest
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut exit_buf = [0u8; 4];
|
||||||
|
exit_buf.clone_from_slice(&data);
|
||||||
|
let code = i32::from_be_bytes(exit_buf);
|
||||||
|
std::process::exit(code);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::Close(Some(close)) => {
|
||||||
|
if close.code != CloseCode::Normal {
|
||||||
|
return Err(Error::new(
|
||||||
|
color_eyre::eyre::Report::msg(close.reason),
|
||||||
|
ErrorKind::Network
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ use std::sync::{Arc, Weak};
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use futures::future::ready;
|
use futures::future::ready;
|
||||||
use futures::{Future, FutureExt};
|
use futures::Future;
|
||||||
use helpers::NonDetachingJoinHandle;
|
use helpers::NonDetachingJoinHandle;
|
||||||
use imbl::Vector;
|
use imbl::Vector;
|
||||||
|
use imbl_value::InternedString;
|
||||||
use models::{ImageId, ProcedureName, VolumeId};
|
use models::{ImageId, ProcedureName, VolumeId};
|
||||||
use rpc_toolkit::{Empty, Server, ShutdownHandle};
|
use rpc_toolkit::{Empty, Server, ShutdownHandle};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
@@ -36,7 +37,7 @@ use crate::service::{rpc, RunningStatus, Service};
|
|||||||
use crate::util::io::create_file;
|
use crate::util::io::create_file;
|
||||||
use crate::util::rpc_client::UnixRpcClient;
|
use crate::util::rpc_client::UnixRpcClient;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::volume::{asset_dir, data_dir};
|
use crate::volume::data_dir;
|
||||||
use crate::ARCH;
|
use crate::ARCH;
|
||||||
|
|
||||||
const RPC_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
const RPC_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
@@ -84,6 +85,14 @@ impl ServiceState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Want to have a wrapper for uses like the inject where we are going to be finding the subcontainer and doing some filtering on it.
|
||||||
|
/// As well, the imageName is also used for things like env.
|
||||||
|
pub struct Subcontainer {
|
||||||
|
pub(super) name: InternedString,
|
||||||
|
pub(super) image_id: ImageId,
|
||||||
|
pub(super) overlay: OverlayGuard<Arc<MountGuard>>,
|
||||||
|
}
|
||||||
|
|
||||||
// @DRB On top of this we need to also have the procedures to have the effects and get the results back for them, maybe lock them to the running instance?
|
// @DRB On top of this we need to also have the procedures to have the effects and get the results back for them, maybe lock them to the running instance?
|
||||||
/// This contains the LXC container running the javascript init system
|
/// This contains the LXC container running the javascript init system
|
||||||
/// that can be used via a JSON RPC Client connected to a unix domain
|
/// that can be used via a JSON RPC Client connected to a unix domain
|
||||||
@@ -98,7 +107,7 @@ pub struct PersistentContainer {
|
|||||||
volumes: BTreeMap<VolumeId, MountGuard>,
|
volumes: BTreeMap<VolumeId, MountGuard>,
|
||||||
assets: BTreeMap<VolumeId, MountGuard>,
|
assets: BTreeMap<VolumeId, MountGuard>,
|
||||||
pub(super) images: BTreeMap<ImageId, Arc<MountGuard>>,
|
pub(super) images: BTreeMap<ImageId, Arc<MountGuard>>,
|
||||||
pub(super) subcontainers: Arc<Mutex<BTreeMap<Guid, OverlayGuard<Arc<MountGuard>>>>>,
|
pub(super) subcontainers: Arc<Mutex<BTreeMap<Guid, Subcontainer>>>,
|
||||||
pub(super) state: Arc<watch::Sender<ServiceState>>,
|
pub(super) state: Arc<watch::Sender<ServiceState>>,
|
||||||
pub(super) net_service: Mutex<NetService>,
|
pub(super) net_service: Mutex<NetService>,
|
||||||
destroyed: bool,
|
destroyed: bool,
|
||||||
@@ -405,7 +414,7 @@ impl PersistentContainer {
|
|||||||
errs.handle(assets.unmount(true).await);
|
errs.handle(assets.unmount(true).await);
|
||||||
}
|
}
|
||||||
for (_, overlay) in std::mem::take(&mut *subcontainers.lock().await) {
|
for (_, overlay) in std::mem::take(&mut *subcontainers.lock().await) {
|
||||||
errs.handle(overlay.unmount(true).await);
|
errs.handle(overlay.overlay.unmount(true).await);
|
||||||
}
|
}
|
||||||
for (_, images) in images {
|
for (_, images) in images {
|
||||||
errs.handle(images.unmount().await);
|
errs.handle(images.unmount().await);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ use crate::util::io::{create_file, TmpDir};
|
|||||||
|
|
||||||
pub async fn upload(
|
pub async fn upload(
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
session: InternedString,
|
session: Option<InternedString>,
|
||||||
) -> Result<(Guid, UploadingFile), Error> {
|
) -> Result<(Guid, UploadingFile), Error> {
|
||||||
let guid = Guid::new();
|
let guid = Guid::new();
|
||||||
let (mut handle, file) = UploadingFile::new().await?;
|
let (mut handle, file) = UploadingFile::new().await?;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
use std::os::unix::prelude::MetadataExt;
|
use std::os::unix::prelude::MetadataExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
@@ -11,7 +12,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use bytes::{Buf, BytesMut};
|
use bytes::{Buf, BytesMut};
|
||||||
use futures::future::{BoxFuture, Fuse};
|
use futures::future::{BoxFuture, Fuse};
|
||||||
use futures::{AsyncSeek, FutureExt, TryStreamExt};
|
use futures::{AsyncSeek, FutureExt, Stream, TryStreamExt};
|
||||||
use helpers::NonDetachingJoinHandle;
|
use helpers::NonDetachingJoinHandle;
|
||||||
use nix::unistd::{Gid, Uid};
|
use nix::unistd::{Gid, Uid};
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
@@ -23,6 +24,7 @@ use tokio::sync::{Notify, OwnedMutexGuard};
|
|||||||
use tokio::time::{Instant, Sleep};
|
use tokio::time::{Instant, Sleep};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::{CAP_1_KiB, CAP_1_MiB};
|
||||||
|
|
||||||
pub trait AsyncReadSeek: AsyncRead + AsyncSeek {}
|
pub trait AsyncReadSeek: AsyncRead + AsyncSeek {}
|
||||||
impl<T: AsyncRead + AsyncSeek> AsyncReadSeek for T {}
|
impl<T: AsyncRead + AsyncSeek> AsyncReadSeek for T {}
|
||||||
@@ -1267,3 +1269,33 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for MutexIO<W> {
|
|||||||
Pin::new(&mut *self.get_mut().0).poll_shutdown(cx)
|
Pin::new(&mut *self.get_mut().0).poll_shutdown(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pin_project::pin_project]
|
||||||
|
pub struct AsyncReadStream<T> {
|
||||||
|
buffer: Vec<MaybeUninit<u8>>,
|
||||||
|
#[pin]
|
||||||
|
pub io: T,
|
||||||
|
}
|
||||||
|
impl<T> AsyncReadStream<T> {
|
||||||
|
pub fn new(io: T, buffer_size: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
buffer: vec![MaybeUninit::uninit(); buffer_size],
|
||||||
|
io,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: AsyncRead> Stream for AsyncReadStream<T> {
|
||||||
|
type Item = Result<Vec<u8>, Error>;
|
||||||
|
fn poll_next(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Option<Self::Item>> {
|
||||||
|
let this = self.project();
|
||||||
|
let mut buf = ReadBuf::uninit(this.buffer);
|
||||||
|
match futures::ready!(this.io.poll_read(cx, &mut buf)) {
|
||||||
|
Ok(()) if buf.filled().is_empty() => Poll::Ready(None),
|
||||||
|
Ok(()) => Poll::Ready(Some(Ok(buf.filled().to_vec()))),
|
||||||
|
Err(e) => Poll::Ready(Some(Err(e.into()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -782,6 +782,7 @@ export async function runCommand<Manifest extends T.Manifest>(
|
|||||||
effects,
|
effects,
|
||||||
image,
|
image,
|
||||||
options.mounts || [],
|
options.mounts || [],
|
||||||
|
commands.join(" "),
|
||||||
(subcontainer) => subcontainer.exec(commands),
|
(subcontainer) => subcontainer.exec(commands),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export class CommandController {
|
|||||||
| SubContainer,
|
| SubContainer,
|
||||||
command: T.CommandType,
|
command: T.CommandType,
|
||||||
options: {
|
options: {
|
||||||
|
subcontainerName?: string
|
||||||
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
|
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
|
||||||
sigtermTimeout?: number
|
sigtermTimeout?: number
|
||||||
mounts?: { path: string; options: MountOptions }[]
|
mounts?: { path: string; options: MountOptions }[]
|
||||||
@@ -51,7 +52,11 @@ export class CommandController {
|
|||||||
subcontainer instanceof SubContainer
|
subcontainer instanceof SubContainer
|
||||||
? subcontainer
|
? subcontainer
|
||||||
: await (async () => {
|
: await (async () => {
|
||||||
const subc = await SubContainer.of(effects, subcontainer)
|
const subc = await SubContainer.of(
|
||||||
|
effects,
|
||||||
|
subcontainer,
|
||||||
|
options?.subcontainerName || commands.join(" "),
|
||||||
|
)
|
||||||
for (let mount of options.mounts || []) {
|
for (let mount of options.mounts || []) {
|
||||||
await subc.mount(mount.options, mount.path)
|
await subc.mount(mount.options, mount.path)
|
||||||
}
|
}
|
||||||
@@ -119,6 +124,11 @@ export class CommandController {
|
|||||||
async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) {
|
async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) {
|
||||||
try {
|
try {
|
||||||
if (!this.state.exited) {
|
if (!this.state.exited) {
|
||||||
|
if (signal !== "SIGKILL") {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.state.exited) this.process.kill("SIGKILL")
|
||||||
|
}, timeout)
|
||||||
|
}
|
||||||
if (!this.process.kill(signal)) {
|
if (!this.process.kill(signal)) {
|
||||||
console.error(
|
console.error(
|
||||||
`failed to send signal ${signal} to pid ${this.process.pid}`,
|
`failed to send signal ${signal} to pid ${this.process.pid}`,
|
||||||
@@ -126,11 +136,6 @@ export class CommandController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signal !== "SIGKILL") {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.process.kill("SIGKILL")
|
|
||||||
}, timeout)
|
|
||||||
}
|
|
||||||
await this.runningAnswer
|
await this.runningAnswer
|
||||||
} finally {
|
} finally {
|
||||||
await this.subcontainer.destroy?.()
|
await this.subcontainer.destroy?.()
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export class Daemon {
|
|||||||
| SubContainer,
|
| SubContainer,
|
||||||
command: T.CommandType,
|
command: T.CommandType,
|
||||||
options: {
|
options: {
|
||||||
|
subcontainerName?: string
|
||||||
mounts?: { path: string; options: MountOptions }[]
|
mounts?: { path: string; options: MountOptions }[]
|
||||||
env?:
|
env?:
|
||||||
| {
|
| {
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ export class Daemons<Manifest extends T.Manifest, Ids extends string> {
|
|||||||
const daemon = Daemon.of()(this.effects, options.image, options.command, {
|
const daemon = Daemon.of()(this.effects, options.image, options.command, {
|
||||||
...options,
|
...options,
|
||||||
mounts: options.mounts.build(),
|
mounts: options.mounts.build(),
|
||||||
|
subcontainerName: id,
|
||||||
})
|
})
|
||||||
const healthDaemon = new HealthDaemon(
|
const healthDaemon = new HealthDaemon(
|
||||||
daemon,
|
daemon,
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { ImageId } from "./ImageId"
|
import type { ImageId } from "./ImageId"
|
||||||
|
|
||||||
export type CreateSubcontainerFsParams = { imageId: ImageId }
|
export type CreateSubcontainerFsParams = {
|
||||||
|
imageId: ImageId
|
||||||
|
name: string | null
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { Sessions } from "./Sessions"
|
import type { Sessions } from "./Sessions"
|
||||||
|
|
||||||
export type SessionList = { current: string; sessions: Sessions }
|
export type SessionList = { current: string | null; sessions: Sessions }
|
||||||
|
|||||||
@@ -366,7 +366,10 @@ export type Effects = {
|
|||||||
// subcontainer
|
// subcontainer
|
||||||
subcontainer: {
|
subcontainer: {
|
||||||
/** A low level api used by SubContainer */
|
/** A low level api used by SubContainer */
|
||||||
createFs(options: { imageId: string }): Promise<[string, string]>
|
createFs(options: {
|
||||||
|
imageId: string
|
||||||
|
name: string | null
|
||||||
|
}): Promise<[string, string]>
|
||||||
/** A low level api used by SubContainer */
|
/** A low level api used by SubContainer */
|
||||||
destroyFs(options: { guid: string }): Promise<void>
|
destroyFs(options: { guid: string }): Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,10 +86,12 @@ export class SubContainer implements ExecSpawnable {
|
|||||||
static async of(
|
static async of(
|
||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
image: { id: T.ImageId; sharedRun?: boolean },
|
image: { id: T.ImageId; sharedRun?: boolean },
|
||||||
|
name: string,
|
||||||
) {
|
) {
|
||||||
const { id, sharedRun } = image
|
const { id, sharedRun } = image
|
||||||
const [rootfs, guid] = await effects.subcontainer.createFs({
|
const [rootfs, guid] = await effects.subcontainer.createFs({
|
||||||
imageId: id as string,
|
imageId: id as string,
|
||||||
|
name,
|
||||||
})
|
})
|
||||||
|
|
||||||
const shared = ["dev", "sys"]
|
const shared = ["dev", "sys"]
|
||||||
@@ -115,9 +117,10 @@ export class SubContainer implements ExecSpawnable {
|
|||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
image: { id: T.ImageId; sharedRun?: boolean },
|
image: { id: T.ImageId; sharedRun?: boolean },
|
||||||
mounts: { options: MountOptions; path: string }[],
|
mounts: { options: MountOptions; path: string }[],
|
||||||
|
name: string,
|
||||||
fn: (subContainer: SubContainer) => Promise<T>,
|
fn: (subContainer: SubContainer) => Promise<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const subContainer = await SubContainer.of(effects, image)
|
const subContainer = await SubContainer.of(effects, image, name)
|
||||||
try {
|
try {
|
||||||
for (let mount of mounts) {
|
for (let mount of mounts) {
|
||||||
await subContainer.mount(mount.options, mount.path)
|
await subContainer.mount(mount.options, mount.path)
|
||||||
@@ -179,10 +182,12 @@ export class SubContainer implements ExecSpawnable {
|
|||||||
}
|
}
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
|
let timeout = setTimeout(() => this.leader.kill("SIGKILL"), 30000)
|
||||||
this.leader.on("exit", () => {
|
this.leader.on("exit", () => {
|
||||||
|
clearTimeout(timeout)
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
if (!this.leader.kill("SIGKILL")) {
|
if (!this.leader.kill("SIGTERM")) {
|
||||||
reject(new Error("kill(2) failed"))
|
reject(new Error("kill(2) failed"))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user