From 474ab96f7a29a524b12fc2e1874ceae18373eacd Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Tue, 4 Aug 2026 17:50:34 +0800 Subject: [PATCH 1/5] fix(prelaunch): bump Phala Cloud script to v0.0.16 --- phala-cloud-prelaunch-script/prelaunch.sh | 48 ++++++++++++++--------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/phala-cloud-prelaunch-script/prelaunch.sh b/phala-cloud-prelaunch-script/prelaunch.sh index b4c650a..3c7c69a 100644 --- a/phala-cloud-prelaunch-script/prelaunch.sh +++ b/phala-cloud-prelaunch-script/prelaunch.sh @@ -1,6 +1,6 @@ #!/bin/bash echo "----------------------------------------------" -echo "Running Phala Cloud Pre-Launch Script v0.0.15" +echo "Running Phala Cloud Pre-Launch Script v0.0.16" echo "----------------------------------------------" set -e @@ -69,18 +69,17 @@ if [[ -n "$DSTACK_DOCKER_USERNAME" && -n "$DSTACK_DOCKER_PASSWORD" ]]; then echo "Logging in to Docker registry: $DOCKER_REGISTRY_TARGET" # Login without exposing password in process list if [[ -n "$DSTACK_DOCKER_REGISTRY" ]]; then - echo "$DSTACK_DOCKER_PASSWORD" | docker login -u "$DSTACK_DOCKER_USERNAME" --password-stdin "$DSTACK_DOCKER_REGISTRY" - else - echo "$DSTACK_DOCKER_PASSWORD" | docker login -u "$DSTACK_DOCKER_USERNAME" --password-stdin - fi - - if [ $? -eq 0 ]; then - echo "Docker login successful: $DOCKER_REGISTRY_TARGET" - else + if ! echo "$DSTACK_DOCKER_PASSWORD" | docker login -u "$DSTACK_DOCKER_USERNAME" --password-stdin "$DSTACK_DOCKER_REGISTRY"; then + echo "Docker login failed: $DOCKER_REGISTRY_TARGET" + notify_host_hoot_error "docker login failed" + exit 1 + fi + elif ! echo "$DSTACK_DOCKER_PASSWORD" | docker login -u "$DSTACK_DOCKER_USERNAME" --password-stdin; then echo "Docker login failed: $DOCKER_REGISTRY_TARGET" notify_host_hoot_error "docker login failed" exit 1 fi + echo "Docker login successful: $DOCKER_REGISTRY_TARGET" fi # Check if AWS ECR credentials exist elif [[ -n "$DSTACK_AWS_ACCESS_KEY_ID" && -n "$DSTACK_AWS_SECRET_ACCESS_KEY" && -n "$DSTACK_AWS_REGION" && -n "$DSTACK_AWS_ECR_REGISTRY" ]]; then @@ -90,9 +89,12 @@ elif [[ -n "$DSTACK_AWS_ACCESS_KEY_ID" && -n "$DSTACK_AWS_SECRET_ACCESS_KEY" && if [ ! -f "./aws/dist/aws" ]; then notify_host_hoot_info "awscli not installed, installing..." echo "AWS CLI not installed, installing..." - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.24.14.zip" -o "awscliv2.zip" - echo "6ff031a26df7daebbfa3ccddc9af1450 awscliv2.zip" | md5sum -c - if [ $? -ne 0 ]; then + if ! curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.24.14.zip" -o "awscliv2.zip"; then + echo "AWS CLI download failed" + notify_host_hoot_error "awscli install failed" + exit 1 + fi + if ! echo "6ff031a26df7daebbfa3ccddc9af1450 awscliv2.zip" | md5sum -c; then echo "MD5 checksum failed" notify_host_hoot_error "awscli install failed" exit 1 @@ -129,8 +131,7 @@ elif [[ -n "$DSTACK_AWS_ACCESS_KEY_ID" && -n "$DSTACK_AWS_SECRET_ACCESS_KEY" && fi else echo "Logging in to AWS ECR..." - ./aws/dist/aws ecr get-login-password --region $DSTACK_AWS_REGION | docker login --username AWS --password-stdin "$DSTACK_AWS_ECR_REGISTRY" - if [ $? -eq 0 ]; then + if ./aws/dist/aws ecr get-login-password --region "$DSTACK_AWS_REGION" | docker login --username AWS --password-stdin "$DSTACK_AWS_ECR_REGISTRY"; then echo "AWS ECR login successful" notify_host_hoot_info "AWS ECR login successful" else @@ -156,8 +157,17 @@ if [[ "$DOCKER_REGISTRY_TARGET" == "ghcr.io" && -n "$DSTACK_DOCKER_USERNAME" && COMPOSE_IMAGES=$(grep 'image:' /dstack/docker-compose.yaml 2>/dev/null | awk '{print $2}' | tr -d '"'"'" || true) for img in $COMPOSE_IMAGES; do [[ "$img" != ghcr.io/* ]] && continue - repo="${img#ghcr.io/}"; repo="${repo%%:*}" - tag="${img##*:}"; [[ "$tag" == "$img" || "$tag" == "$repo" ]] && tag="latest" + ref="${img#ghcr.io/}" + # Digest-pinned refs (repo@sha256:...) use the digest as the manifest + # reference; tag refs (repo:tag) use the tag, defaulting to latest. + if [[ "$ref" == *@* ]]; then + repo="${ref%%@*}" + reference="${ref#*@}" + else + repo="${ref%%:*}" + reference="${ref##*:}" + [[ "$reference" == "$ref" ]] && reference="latest" + fi echo "Verifying GHCR pull access: $img" token=$(curl -sf -u "$DSTACK_DOCKER_USERNAME:$DSTACK_DOCKER_PASSWORD" "https://ghcr.io/token?service=ghcr.io&scope=repository:${repo}:pull" | jq -r '.token // empty' || true) if [[ -z "$token" ]]; then @@ -165,7 +175,7 @@ if [[ "$DOCKER_REGISTRY_TARGET" == "ghcr.io" && -n "$DSTACK_DOCKER_USERNAME" && notify_host_hoot_error "GHCR token exchange failed: $img" exit 1 fi - http_code=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -H "Accept: application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.docker.distribution.manifest.v2+json" "https://ghcr.io/v2/${repo}/manifests/${tag}") + http_code=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -H "Accept: application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.docker.distribution.manifest.v2+json" "https://ghcr.io/v2/${repo}/manifests/${reference}") if [[ "$http_code" != "200" ]]; then echo "ERROR: GHCR pull access denied for $img (HTTP $http_code)" notify_host_hoot_error "GHCR pull access denied: $img (HTTP $http_code)" @@ -266,12 +276,12 @@ fi if mkdir -p /home/root/.ssh 2>/dev/null; then if [[ -n "$DSTACK_ROOT_PUBLIC_KEY" ]]; then echo "$DSTACK_ROOT_PUBLIC_KEY" > /home/root/.ssh/authorized_keys - unset $DSTACK_ROOT_PUBLIC_KEY + unset DSTACK_ROOT_PUBLIC_KEY echo "Root public key set" fi if [[ -n "$DSTACK_AUTHORIZED_KEYS" ]]; then echo "$DSTACK_AUTHORIZED_KEYS" > /home/root/.ssh/authorized_keys - unset $DSTACK_AUTHORIZED_KEYS + unset DSTACK_AUTHORIZED_KEYS echo "Root authorized_keys set" fi From 0593b4b9224bc789d83923382e30f9229c366c4a Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Tue, 4 Aug 2026 18:05:16 +0800 Subject: [PATCH 2/5] fix(prelaunch): avoid curl continuation spacing --- phala-cloud-prelaunch-script/prelaunch.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phala-cloud-prelaunch-script/prelaunch.sh b/phala-cloud-prelaunch-script/prelaunch.sh index 3c7c69a..5dd64c8 100644 --- a/phala-cloud-prelaunch-script/prelaunch.sh +++ b/phala-cloud-prelaunch-script/prelaunch.sh @@ -169,13 +169,13 @@ if [[ "$DOCKER_REGISTRY_TARGET" == "ghcr.io" && -n "$DSTACK_DOCKER_USERNAME" && [[ "$reference" == "$ref" ]] && reference="latest" fi echo "Verifying GHCR pull access: $img" - token=$(curl -sf -u "$DSTACK_DOCKER_USERNAME:$DSTACK_DOCKER_PASSWORD" "https://ghcr.io/token?service=ghcr.io&scope=repository:${repo}:pull" | jq -r '.token // empty' || true) + token=$(curl -sf -u "$DSTACK_DOCKER_USERNAME:$DSTACK_DOCKER_PASSWORD" "https://ghcr.io/token?service=ghcr.io&scope=repository:${repo}:pull" | jq -r '.token // empty' || true) if [[ -z "$token" ]]; then echo "ERROR: GHCR token exchange failed for $img" notify_host_hoot_error "GHCR token exchange failed: $img" exit 1 fi - http_code=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -H "Accept: application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.docker.distribution.manifest.v2+json" "https://ghcr.io/v2/${repo}/manifests/${reference}") + http_code=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -H "Accept: application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.docker.distribution.manifest.v2+json" "https://ghcr.io/v2/${repo}/manifests/${reference}") if [[ "$http_code" != "200" ]]; then echo "ERROR: GHCR pull access denied for $img (HTTP $http_code)" notify_host_hoot_error "GHCR pull access denied: $img (HTTP $http_code)" From 0f71d18a63f5d27cf5adef6425daecece58d2a5b Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Tue, 4 Aug 2026 18:48:01 +0800 Subject: [PATCH 3/5] fix(prelaunch): extract GHCR accept header --- phala-cloud-prelaunch-script/prelaunch.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phala-cloud-prelaunch-script/prelaunch.sh b/phala-cloud-prelaunch-script/prelaunch.sh index 5dd64c8..7d75446 100644 --- a/phala-cloud-prelaunch-script/prelaunch.sh +++ b/phala-cloud-prelaunch-script/prelaunch.sh @@ -155,6 +155,7 @@ perform_cleanup # if [[ "$DOCKER_REGISTRY_TARGET" == "ghcr.io" && -n "$DSTACK_DOCKER_USERNAME" && -n "$DSTACK_DOCKER_PASSWORD" ]]; then COMPOSE_IMAGES=$(grep 'image:' /dstack/docker-compose.yaml 2>/dev/null | awk '{print $2}' | tr -d '"'"'" || true) + HEADER="Accept: application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.docker.distribution.manifest.v2+json" for img in $COMPOSE_IMAGES; do [[ "$img" != ghcr.io/* ]] && continue ref="${img#ghcr.io/}" @@ -175,7 +176,7 @@ if [[ "$DOCKER_REGISTRY_TARGET" == "ghcr.io" && -n "$DSTACK_DOCKER_USERNAME" && notify_host_hoot_error "GHCR token exchange failed: $img" exit 1 fi - http_code=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -H "Accept: application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.docker.distribution.manifest.v2+json" "https://ghcr.io/v2/${repo}/manifests/${reference}") + http_code=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" -H "$HEADER" "https://ghcr.io/v2/${repo}/manifests/${reference}") if [[ "$http_code" != "200" ]]; then echo "ERROR: GHCR pull access denied for $img (HTTP $http_code)" notify_host_hoot_error "GHCR pull access denied: $img (HTTP $http_code)" From c4579ce56228787134b3e770fe37846f8ee25273 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Tue, 4 Aug 2026 21:12:24 +0800 Subject: [PATCH 4/5] fix(prelaunch): normalize passwd fallback formatting --- phala-cloud-prelaunch-script/prelaunch.sh | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/phala-cloud-prelaunch-script/prelaunch.sh b/phala-cloud-prelaunch-script/prelaunch.sh index 7d75446..7ba701a 100644 --- a/phala-cloud-prelaunch-script/prelaunch.sh +++ b/phala-cloud-prelaunch-script/prelaunch.sh @@ -250,9 +250,7 @@ else if [ -n "$DSTACK_ROOT_PASSWORD" ]; then echo "Setting root password from user.." - echo "$DSTACK_ROOT_PASSWORD" | passwd --stdin root 2>/dev/null || printf '%s -%s -' "$DSTACK_ROOT_PASSWORD" "$DSTACK_ROOT_PASSWORD" | passwd root + echo "$DSTACK_ROOT_PASSWORD" | passwd --stdin root 2>/dev/null || printf '%s\n%s\n' "$DSTACK_ROOT_PASSWORD" "$DSTACK_ROOT_PASSWORD" | passwd root unset DSTACK_ROOT_PASSWORD echo "Root password set/updated from DSTACK_ROOT_PASSWORD" elif [ -z "$(grep '^root:' /etc/shadow 2>/dev/null | cut -d: -f2)" ]; then @@ -260,9 +258,7 @@ else DSTACK_ROOT_PASSWORD=$( LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | dd bs=1 count=32 2>/dev/null ) - echo "$DSTACK_ROOT_PASSWORD" | passwd --stdin root 2>/dev/null || printf '%s -%s -' "$DSTACK_ROOT_PASSWORD" "$DSTACK_ROOT_PASSWORD" | passwd root + echo "$DSTACK_ROOT_PASSWORD" | passwd --stdin root 2>/dev/null || printf '%s\n%s\n' "$DSTACK_ROOT_PASSWORD" "$DSTACK_ROOT_PASSWORD" | passwd root unset DSTACK_ROOT_PASSWORD echo "Root password set (random auto-init)" else From deb6808161c5702aafff4e42e958d63148ab6e4b Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Thu, 6 Aug 2026 01:41:01 +0800 Subject: [PATCH 5/5] fix(prelaunch): v0.0.17 - stop piping interactive passwd on boot Guests without chpasswd were falling back to passwd(1) via stdin. Many passwd implementations read /dev/tty during boot and fail with "password for root is unchanged", leaving an empty root password. - empty root password without chpasswd: passwd -l root - DSTACK_ROOT_PASSWORD without chpasswd: only passwd --stdin when supported; otherwise fail clearly - chpasswd path unchanged; random password gen uses head -c Must stay byte-identical to teehouse CURRENT_PRE_LAUNCH_SCRIPT (sha256 93d29be852c0ccf92b275f824d71f1b589e906503c4d67332f36c633412fabe5). --- phala-cloud-prelaunch-script/prelaunch.sh | 32 ++++++++++++++--------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/phala-cloud-prelaunch-script/prelaunch.sh b/phala-cloud-prelaunch-script/prelaunch.sh index 7ba701a..1425c08 100644 --- a/phala-cloud-prelaunch-script/prelaunch.sh +++ b/phala-cloud-prelaunch-script/prelaunch.sh @@ -1,6 +1,6 @@ #!/bin/bash echo "----------------------------------------------" -echo "Running Phala Cloud Pre-Launch Script v0.0.16" +echo "Running Phala Cloud Pre-Launch Script v0.0.17" echo "----------------------------------------------" set -e @@ -236,8 +236,9 @@ else echo "Root password set/updated from DSTACK_ROOT_PASSWORD" elif [ -z "$(grep '^root:' /etc/shadow 2>/dev/null | cut -d: -f2)" ]; then echo "Setting random root password.." + # head -c closes after 32 bytes; without pipefail, pipeline status is head's. DSTACK_ROOT_PASSWORD=$( - LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | dd bs=1 count=32 2>/dev/null + LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom 2>/dev/null | head -c 32 ) echo "root:$DSTACK_ROOT_PASSWORD" | chpasswd unset DSTACK_ROOT_PASSWORD @@ -246,21 +247,28 @@ else echo "Root password already set; no changes." fi else + # No chpasswd: do not pipe into interactive passwd(1). It often reads + # /dev/tty and fails with "password for root is unchanged" during boot. echo "Using passwd method" if [ -n "$DSTACK_ROOT_PASSWORD" ]; then echo "Setting root password from user.." - echo "$DSTACK_ROOT_PASSWORD" | passwd --stdin root 2>/dev/null || printf '%s\n%s\n' "$DSTACK_ROOT_PASSWORD" "$DSTACK_ROOT_PASSWORD" | passwd root - unset DSTACK_ROOT_PASSWORD - echo "Root password set/updated from DSTACK_ROOT_PASSWORD" + if command -v passwd >/dev/null 2>&1 && passwd --help 2>&1 | grep -q -- '--stdin'; then + echo "$DSTACK_ROOT_PASSWORD" | passwd --stdin root + unset DSTACK_ROOT_PASSWORD + echo "Root password set/updated from DSTACK_ROOT_PASSWORD" + else + echo "Error: cannot set DSTACK_ROOT_PASSWORD non-interactively" + echo "Need chpasswd or passwd --stdin" + exit 1 + fi elif [ -z "$(grep '^root:' /etc/shadow 2>/dev/null | cut -d: -f2)" ]; then - echo "Setting random root password.." - DSTACK_ROOT_PASSWORD=$( - LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | dd bs=1 count=32 2>/dev/null - ) - echo "$DSTACK_ROOT_PASSWORD" | passwd --stdin root 2>/dev/null || printf '%s\n%s\n' "$DSTACK_ROOT_PASSWORD" "$DSTACK_ROOT_PASSWORD" | passwd root - unset DSTACK_ROOT_PASSWORD - echo "Root password set (random auto-init)" + # Random password was only used to avoid an empty root password and + # was discarded immediately. Locking achieves the same goal without + # driving interactive passwd. + echo "Locking empty root password.." + passwd -l root + echo "Root password locked (empty password disabled)" else echo "Root password already set; no changes." fi