diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index ed011188..00000000 --- a/.dockerignore +++ /dev/null @@ -1,12 +0,0 @@ -target/ -.git/ -.yoi/ -.direnv/ -result -result-* -.env -.env.* -**/node_modules/ -web/workspace/build/ -web/workspace/.svelte-kit/ -web/workspace/.vite/ diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 0a2b4a0c..00000000 --- a/Dockerfile +++ /dev/null @@ -1,88 +0,0 @@ -# syntax=docker/dockerfile:1.7 - -ARG RUST_VERSION=1.95.0 -ARG DEBIAN_VERSION=bookworm -ARG DENO_VERSION=2.7.14 -ARG NGINX_VERSION=1.27-alpine - -FROM rust:${RUST_VERSION}-${DEBIAN_VERSION} AS rust-source -WORKDIR /src - -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - ca-certificates \ - git \ - libssl-dev \ - pkg-config \ - && rm -rf /var/lib/apt/lists/* - -COPY Cargo.toml Cargo.lock ./ -COPY crates ./crates -COPY resources ./resources -COPY tests ./tests - -FROM rust-source AS runtime-builder -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/usr/local/cargo/git \ - --mount=type=cache,target=/src/target \ - cargo build --release -p worker-runtime --bin worker-runtime-rest-server --features ws-server,fs-store \ - && mkdir -p /out \ - && cp /src/target/release/worker-runtime-rest-server /out/worker-runtime-rest-server - -FROM rust-source AS server-builder -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/usr/local/cargo/git \ - --mount=type=cache,target=/src/target \ - cargo build --release -p yoi-workspace-server --bin yoi-workspace-server \ - && mkdir -p /out \ - && cp /src/target/release/yoi-workspace-server /out/yoi-workspace-server - -FROM denoland/deno:${DENO_VERSION} AS web-builder -WORKDIR /src/web/workspace -COPY web/workspace ./ -RUN deno task build - -FROM debian:${DEBIAN_VERSION}-slim AS runtime -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - bash \ - ca-certificates \ - git \ - libssl3 \ - openssh-client \ - && rm -rf /var/lib/apt/lists/* \ - && useradd --system --uid 10001 --gid root --home-dir /runtime-data --shell /usr/sbin/nologin yoi \ - && mkdir -p /runtime-data /workdirs \ - && chown -R yoi:root /runtime-data /workdirs - -COPY --from=runtime-builder /out/worker-runtime-rest-server /usr/local/bin/worker-runtime-rest-server - -USER yoi -EXPOSE 38800 -VOLUME ["/runtime-data", "/workdirs"] -ENTRYPOINT ["worker-runtime-rest-server"] -CMD ["--bind", "0.0.0.0:38800", "--display-name", "Docker Runtime", "--fs-root", "/runtime-data", "--workdir-target", "/workdirs"] - -FROM debian:${DEBIAN_VERSION}-slim AS server -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - ca-certificates \ - git \ - libssl3 \ - && rm -rf /var/lib/apt/lists/* \ - && useradd --system --uid 10002 --gid root --home-dir /server-data --shell /usr/sbin/nologin yoi \ - && mkdir -p /server-data /workspace \ - && chown -R yoi:root /server-data /workspace - -COPY --from=server-builder /out/yoi-workspace-server /usr/local/bin/yoi-workspace-server - -USER yoi -EXPOSE 8787 -VOLUME ["/server-data", "/workspace"] -ENTRYPOINT ["yoi-workspace-server"] -CMD ["serve", "--workspace", "/workspace", "--db", "/server-data/workspace.db", "--listen", "0.0.0.0:8787"] - -FROM nginx:${NGINX_VERSION} AS webui -COPY docker/nginx.conf /etc/nginx/conf.d/default.conf -COPY --from=web-builder /src/web/workspace/build /usr/share/nginx/html -EXPOSE 80 diff --git a/docker.nix b/docker.nix new file mode 100644 index 00000000..b886d193 --- /dev/null +++ b/docker.nix @@ -0,0 +1,304 @@ +{ + pkgs, + yoi ? pkgs.callPackage ./package.nix { }, +}: + +let + lib = pkgs.lib; + + imageTag = "latest"; + + mkRoot = + name: paths: _extraPathsToLink: + pkgs.symlinkJoin { + inherit name paths; + }; + + runtimeDirs = pkgs.runCommand "yoi-runtime-dirs" { } '' + mkdir -p "$out/runtime-data" "$out/workdirs" + ''; + + serverDirs = pkgs.runCommand "yoi-server-dirs" { } '' + mkdir -p "$out/server-data" "$out/workspace" + ''; + + runtimeRoot = + mkRoot "yoi-runtime-root" + [ + yoi + pkgs.bashInteractive + pkgs.coreutils + pkgs.cacert + pkgs.git + pkgs.openssh + runtimeDirs + ] + [ + "/runtime-data" + "/workdirs" + ]; + + serverRoot = + mkRoot "yoi-server-root" + [ + yoi + pkgs.coreutils + pkgs.cacert + pkgs.git + serverDirs + ] + [ + "/server-data" + "/workspace" + ]; + + webuiSrc = lib.cleanSourceWith { + src = ./web/workspace; + filter = + path: type: + let + baseName = baseNameOf path; + in + !(baseName == "node_modules" || baseName == ".svelte-kit" || baseName == "build"); + }; + + webuiDeps = pkgs.stdenvNoCC.mkDerivation { + pname = "yoi-webui-deno-deps"; + version = "0.1.0"; + + src = webuiSrc; + nativeBuildInputs = [ pkgs.deno ]; + + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + outputHash = "sha256-q+otr+ANR9gB8bRZKFZDfNpM6rnQ4B4wNd/s1QNeSA4="; + + buildPhase = '' + runHook preBuild + + export HOME="$TMPDIR/home" + export DENO_DIR="$TMPDIR/deno-cache" + mkdir -p "$HOME" "$DENO_DIR" + deno task build + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p "$out" + cp -R "$DENO_DIR" "$out/deno-cache" + if [ -d node_modules ]; then + cp -R node_modules "$out/node_modules" + fi + + runHook postInstall + ''; + }; + + webuiStatic = pkgs.stdenvNoCC.mkDerivation { + pname = "yoi-webui-static"; + version = "0.1.0"; + + src = webuiSrc; + + nativeBuildInputs = [ pkgs.deno ]; + + buildPhase = '' + runHook preBuild + + export HOME="$TMPDIR/home" + export DENO_DIR="$TMPDIR/deno-cache" + mkdir -p "$HOME" + cp -R ${webuiDeps}/deno-cache "$DENO_DIR" + chmod -R u+w "$DENO_DIR" + if [ -d ${webuiDeps}/node_modules ]; then + cp -R ${webuiDeps}/node_modules node_modules + chmod -R u+w node_modules + fi + deno task build + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p "$out" + cp -R build/. "$out/" + + runHook postInstall + ''; + }; + + webuiRoot = pkgs.runCommand "yoi-webui-root" { } '' + mkdir -p "$out/usr/share/yoi-webui" + cp -R ${webuiStatic}/. "$out/usr/share/yoi-webui/" + ''; + + webuiDirs = pkgs.runCommand "yoi-webui-dirs" { } '' + mkdir -p "$out/etc" "$out/tmp" "$out/var/cache/nginx" "$out/var/log/nginx" + touch "$out/tmp/.keep" "$out/var/cache/nginx/.keep" "$out/var/log/nginx/.keep" + cat > "$out/etc/passwd" <<'EOF' + root:x:0:0:root:/root:/bin/sh + nobody:x:65534:65534:nobody:/var/empty:/sbin/nologin + EOF + cat > "$out/etc/group" <<'EOF' + root:x:0: + nobody:x:65534: + nogroup:x:65534: + EOF + ''; + + nginxConf = pkgs.writeText "yoi-webui-nginx.conf" '' + pid /tmp/nginx.pid; + error_log /dev/stderr info; + + events {} + + http { + include ${pkgs.nginx}/conf/mime.types; + access_log /dev/stdout; + + map $http_upgrade $connection_upgrade { + default upgrade; + ''' close; + } + + server { + listen 80; + server_name _; + root /usr/share/yoi-webui; + index index.html; + + resolver 127.0.0.11 valid=30s ipv6=off; + set $yoi_backend server:8787; + + location = /api { + proxy_pass http://$yoi_backend; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + + location /api/ { + proxy_pass http://$yoi_backend; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + + location / { + try_files $uri $uri/ /index.html; + } + } + } + ''; + + webuiImageRoot = + mkRoot "yoi-webui-root-env" + [ + pkgs.nginx + pkgs.cacert + webuiRoot + webuiDirs + ] + [ + "/usr/share/yoi-webui" + "/var" + "/tmp" + ]; + + commonEnv = [ + "SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt" + "GIT_SSL_CAINFO=/etc/ssl/certs/ca-bundle.crt" + ]; +in +{ + runtime = pkgs.dockerTools.buildImage { + name = "yoi-runtime"; + tag = imageTag; + copyToRoot = runtimeRoot; + config = { + Entrypoint = [ "/bin/worker-runtime-rest-server" ]; + Cmd = [ + "--bind" + "0.0.0.0:38800" + "--display-name" + "Docker Runtime" + "--fs-root" + "/runtime-data" + "--workdir-target" + "/workdirs" + ]; + Env = [ "PATH=/bin" ] ++ commonEnv; + ExposedPorts = { + "38800/tcp" = { }; + }; + Volumes = { + "/runtime-data" = { }; + "/workdirs" = { }; + }; + User = "10001:0"; + WorkingDir = "/runtime-data"; + }; + }; + + server = pkgs.dockerTools.buildImage { + name = "yoi-server"; + tag = imageTag; + copyToRoot = serverRoot; + config = { + Entrypoint = [ "/bin/yoi-workspace-server" ]; + Cmd = [ + "serve" + "--workspace" + "/workspace" + "--db" + "/server-data/workspace.db" + "--listen" + "0.0.0.0:8787" + ]; + Env = [ "PATH=/bin" ] ++ commonEnv; + ExposedPorts = { + "8787/tcp" = { }; + }; + Volumes = { + "/server-data" = { }; + "/workspace" = { }; + }; + User = "10002:0"; + WorkingDir = "/server-data"; + }; + }; + + webui = pkgs.dockerTools.buildImage { + name = "yoi-webui"; + tag = imageTag; + copyToRoot = webuiImageRoot; + config = { + Entrypoint = [ + "/bin/nginx" + "-c" + "${nginxConf}" + "-g" + "daemon off;" + ]; + Env = [ "PATH=/bin" ] ++ commonEnv; + ExposedPorts = { + "80/tcp" = { }; + }; + }; + }; + + webui-static = webuiStatic; +} diff --git a/docker/nginx.conf b/docker/nginx.conf deleted file mode 100644 index 88db38ef..00000000 --- a/docker/nginx.conf +++ /dev/null @@ -1,41 +0,0 @@ -map $http_upgrade $connection_upgrade { - default upgrade; - '' close; -} - -server { - listen 80; - server_name _; - - root /usr/share/nginx/html; - index index.html; - - resolver 127.0.0.11 valid=30s ipv6=off; - set $yoi_backend server:8787; - - location = /api { - proxy_pass http://$yoi_backend; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - } - - location /api/ { - proxy_pass http://$yoi_backend; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - } - - location / { - try_files $uri $uri/ /index.html; - } -} diff --git a/flake.nix b/flake.nix index 1cf4f634..7e5f4e1e 100644 --- a/flake.nix +++ b/flake.nix @@ -17,6 +17,13 @@ let pkgs = nixpkgs.legacyPackages.${system}; yoi = pkgs.callPackage ./package.nix { }; + dockerImages = + if pkgs.stdenv.isLinux then + import ./docker.nix { + inherit pkgs yoi; + } + else + { }; mkApp = name: description: { type = "app"; program = "${yoi}/bin/${name}"; @@ -24,8 +31,16 @@ }; in { - packages.default = yoi; - packages.yoi = yoi; + packages = { + default = yoi; + yoi = yoi; + } + // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux { + docker-runtime = dockerImages.runtime; + docker-server = dockerImages.server; + docker-webui = dockerImages.webui; + webui-static = dockerImages.webui-static; + }; apps.default = mkApp "yoi" "Run the Yoi terminal UI"; apps.yoi = mkApp "yoi" "Run the Yoi terminal UI"; diff --git a/package.nix b/package.nix index a06e73e5..f079abe1 100644 --- a/package.nix +++ b/package.nix @@ -43,7 +43,7 @@ rustPlatform.buildRustPackage rec { filter = sourceFilter; }; - cargoHash = "sha256-MUEySmRu5Te8+GCwlt3qk7jxcumYIMvnuvj64o2XuDw="; + cargoHash = "sha256-iZaTREhL/aLixn67A1+Gi9opqh2j/yyFuGMyBBvuATM="; depsExtraArgs = { # Older fetchCargoVendor utilities used crates.io's API download endpoint, @@ -93,6 +93,7 @@ rustPlatform.buildRustPackage rec { postBuild = '' cargo build --offline --profile release -p yoi-workspace-server --bin yoi-workspace-server + cargo build --offline --profile release -p worker-runtime --bin worker-runtime-rest-server --features ws-server,fs-store ''; # The package check is a credential-free install smoke check below. Running the @@ -105,13 +106,15 @@ rustPlatform.buildRustPackage rec { yoi_bin=$(find . -type f -name yoi | head -n 1) workspace_server_bin=$(find . -type f -name yoi-workspace-server | head -n 1) - if [ -z "$yoi_bin" ] || [ -z "$workspace_server_bin" ]; then + worker_runtime_bin=$(find . -type f -name worker-runtime-rest-server | head -n 1) + if [ -z "$yoi_bin" ] || [ -z "$workspace_server_bin" ] || [ -z "$worker_runtime_bin" ]; then echo "built binaries not found" >&2 - find . -maxdepth 6 -type f \( -name yoi -o -name yoi-workspace-server \) -print >&2 + find . -maxdepth 6 -type f \( -name yoi -o -name yoi-workspace-server -o -name worker-runtime-rest-server \) -print >&2 exit 1 fi install -Dm755 "$yoi_bin" "$out/bin/yoi" install -Dm755 "$workspace_server_bin" "$out/bin/yoi-workspace-server" + install -Dm755 "$worker_runtime_bin" "$out/bin/worker-runtime-rest-server" runHook postInstall ''; @@ -123,7 +126,9 @@ rustPlatform.buildRustPackage rec { "$out/bin/yoi" worker --help >/dev/null test -x "$out/bin/yoi" test -x "$out/bin/yoi-workspace-server" + test -x "$out/bin/worker-runtime-rest-server" "$out/bin/yoi-workspace-server" --help >/dev/null + "$out/bin/worker-runtime-rest-server" --help >/dev/null test ! -e "$out/bin/yoi-pod" test ! -e "$out/share/yoi/resources" if "$out/bin/yoi" --session not-a-uuid 2>yoi.err; then