diff --git a/internal/provision/addons/codeserver/install.go b/internal/provision/addons/codeserver/install.go index 0d203db..996ee6b 100644 --- a/internal/provision/addons/codeserver/install.go +++ b/internal/provision/addons/codeserver/install.go @@ -4,39 +4,28 @@ import ( "fmt" "path/filepath" - "zlh-agent/internal/provision" - "zlh-agent/internal/provision/addons" + "zlh-agent/internal/provision/executil" + "zlh-agent/internal/provision/markers" "zlh-agent/internal/state" ) -/* - Install installs the code-server addon. - - IMPORTANT: - - Addon-only (not devcontainer-specific) - - No assumptions about users or workspaces - - Idempotent via addon marker -*/ func Install(cfg state.Config) error { + const marker = "addon-codeserver" - if addons.IsAddonProvisioned("codeserver") { + if markers.IsPresent(marker) { return nil } scriptPath := filepath.Join( - provision.ScriptsRoot, + executil.ScriptsRoot, "addons", "codeserver", "install.sh", ) - if err := provision.RunScript(scriptPath); err != nil { + if err := executil.RunScript(scriptPath); err != nil { return fmt.Errorf("codeserver install failed: %w", err) } - if err := addons.WriteAddonMarker("codeserver"); err != nil { - return fmt.Errorf("failed to write codeserver marker: %w", err) - } - - return nil + return markers.Write(marker) } diff --git a/internal/provision/addons/codeserver/verify.go b/internal/provision/addons/codeserver/verify.go index da3af85..88e8a1c 100644 --- a/internal/provision/addons/codeserver/verify.go +++ b/internal/provision/addons/codeserver/verify.go @@ -2,17 +2,17 @@ package codeserver import ( "fmt" + "os" "os/exec" - - "zlh-agent/internal/state" ) -/* - Verify validates that code-server is installed and usable. +const marker = "/opt/zlh/.zlh/addons/code-server.installed" - This does NOT start the service. -*/ -func Verify(cfg state.Config) error { +func Verify() error { + + if _, err := os.Stat(marker); err != nil { + return fmt.Errorf("code-server addon marker missing") + } if _, err := exec.LookPath("code-server"); err != nil { return fmt.Errorf("code-server binary not found in PATH") diff --git a/internal/provision/devcontainer/devcontainer.go b/internal/provision/devcontainer/devcontainer.go index a8ae588..22ef350 100644 --- a/internal/provision/devcontainer/devcontainer.go +++ b/internal/provision/devcontainer/devcontainer.go @@ -12,46 +12,19 @@ import ( "zlh-agent/internal/provision/devcontainer/python" ) -/* - Provision — entrypoint for dev container provisioning. - - IMPORTANT: - - This function ONLY performs installation. - - Verification happens in ensureProvisioned(). - - state.Config is treated as immutable desired state. - - Routing is based strictly on cfg.Runtime. -*/ func Provision(cfg state.Config) error { - runtime := strings.ToLower(cfg.Runtime) switch runtime { - case "node": - if err := node.Install(cfg); err != nil { - return fmt.Errorf("node devcontainer install failed: %w", err) - } - + return node.Install(cfg) case "python": - if err := python.Install(cfg); err != nil { - return fmt.Errorf("python devcontainer install failed: %w", err) - } - + return python.Install(cfg) case "go": - if err := devgo.Install(cfg); err != nil { - return fmt.Errorf("go devcontainer install failed: %w", err) - } - + return devgo.Install(cfg) case "java": - if err := java.Install(cfg); err != nil { - return fmt.Errorf("java devcontainer install failed: %w", err) - } - + return java.Install(cfg) default: return fmt.Errorf("unsupported dev container runtime: %s", runtime) } - - // DO NOT VERIFY HERE. - // Verification happens in ensureProvisioned(). - return nil } diff --git a/internal/provision/devcontainer/go/install.go b/internal/provision/devcontainer/go/install.go index ce00ed9..8d38f14 100644 --- a/internal/provision/devcontainer/go/install.go +++ b/internal/provision/devcontainer/go/install.go @@ -4,43 +4,28 @@ import ( "fmt" "path/filepath" - "zlh-agent/internal/provision" - "zlh-agent/internal/provision/devcontainer" + "zlh-agent/internal/provision/executil" + "zlh-agent/internal/provision/markers" "zlh-agent/internal/state" ) -/* - Install installs the Go dev container runtime. - - Execution model: - - Uses local, versioned install scripts from the agent repo - - Scripts handle downloading and installing Go - - IMPORTANT: - - This function ONLY installs - - No verification here -*/ func Install(cfg state.Config) error { + const marker = "devcontainer-go" - // Idempotency guard - if devcontainer.IsProvisioned() { + if markers.IsPresent(marker) { return nil } scriptPath := filepath.Join( - provision.ScriptsRoot, + executil.ScriptsRoot, "devcontainer", "go", "install.sh", ) - if err := provision.RunScript(scriptPath); err != nil { + if err := executil.RunScript(scriptPath); err != nil { return fmt.Errorf("go devcontainer install failed: %w", err) } - if err := devcontainer.WriteReadyMarker("go"); err != nil { - return fmt.Errorf("failed to write devcontainer ready marker: %w", err) - } - - return nil + return markers.Write(marker) } diff --git a/internal/provision/devcontainer/go/verify.go b/internal/provision/devcontainer/go/verify.go index 2454241..e61c937 100644 --- a/internal/provision/devcontainer/go/verify.go +++ b/internal/provision/devcontainer/go/verify.go @@ -7,30 +7,9 @@ import ( "zlh-agent/internal/state" ) -/* - Verify validates that the Go dev container runtime is usable. - - Responsibilities: - - Ensure `go` binary is present and executable - - Ensure GOPATH / GOMOD usage won’t immediately fail - - IMPORTANT: - - No installation here - - No state mutation - - Safe to call multiple times -*/ func Verify(cfg state.Config) error { - - // Check go binary if _, err := exec.LookPath("go"); err != nil { return fmt.Errorf("go binary not found in PATH") } - - // Optional sanity check: go env should run - cmd := exec.Command("go", "env") - if err := cmd.Run(); err != nil { - return fmt.Errorf("go env failed to execute") - } - return nil } diff --git a/internal/provision/devcontainer/java/install.go b/internal/provision/devcontainer/java/install.go index 824434d..b28954e 100644 --- a/internal/provision/devcontainer/java/install.go +++ b/internal/provision/devcontainer/java/install.go @@ -4,44 +4,27 @@ import ( "fmt" "path/filepath" - "zlh-agent/internal/provision" - "zlh-agent/internal/provision/devcontainer" + "zlh-agent/internal/provision/executil" + "zlh-agent/internal/provision/markers" "zlh-agent/internal/state" ) -/* - Install installs the Java dev container runtime. - - Execution model: - - Uses local, versioned install scripts from the agent repo - - Does NOT assume Minecraft semantics - - Provides a general-purpose Java environment for development - - IMPORTANT: - - This function ONLY installs - - No verification here -*/ func Install(cfg state.Config) error { - // Idempotency guard - if devcontainer.IsProvisioned() { + if markers.IsPresent("devcontainer-java") { return nil } scriptPath := filepath.Join( - provision.ScriptsRoot, + executil.ScriptsRoot, "devcontainer", "java", "install.sh", ) - if err := provision.RunScript(scriptPath); err != nil { + if err := executil.RunScript(scriptPath); err != nil { return fmt.Errorf("java devcontainer install failed: %w", err) } - if err := devcontainer.WriteReadyMarker("java"); err != nil { - return fmt.Errorf("failed to write devcontainer ready marker: %w", err) - } - - return nil + return markers.Write("devcontainer-java") } diff --git a/internal/provision/devcontainer/java/verify.go b/internal/provision/devcontainer/java/verify.go index 26df915..cc70975 100644 --- a/internal/provision/devcontainer/java/verify.go +++ b/internal/provision/devcontainer/java/verify.go @@ -7,29 +7,9 @@ import ( "zlh-agent/internal/state" ) -/* - Verify validates that the Java dev container runtime is usable. - - Responsibilities: - - Ensure `java` binary is present and executable - - Ensure `javac` is present (development use case) - - IMPORTANT: - - No installation here - - No state mutation - - Safe to call multiple times -*/ func Verify(cfg state.Config) error { - - // Check java runtime if _, err := exec.LookPath("java"); err != nil { return fmt.Errorf("java binary not found in PATH") } - - // Check Java compiler (dev requirement) - if _, err := exec.LookPath("javac"); err != nil { - return fmt.Errorf("javac binary not found in PATH") - } - return nil } diff --git a/internal/provision/devcontainer/node/install.go b/internal/provision/devcontainer/node/install.go index 3faebab..20fe945 100644 --- a/internal/provision/devcontainer/node/install.go +++ b/internal/provision/devcontainer/node/install.go @@ -4,46 +4,28 @@ import ( "fmt" "path/filepath" - "zlh-agent/internal/provision" - "zlh-agent/internal/provision/devcontainer" + "zlh-agent/internal/provision/executil" + "zlh-agent/internal/provision/markers" "zlh-agent/internal/state" ) -/* - Install installs the Node.js dev container runtime. - - Execution model: - - Uses local, versioned install scripts from the agent repo - - Scripts are responsible for fetching artifacts - - Artifact server is NOT an execution source - - IMPORTANT: - - This function ONLY installs - - No verification here -*/ func Install(cfg state.Config) error { + const marker = "devcontainer-node" - // Idempotency guard - if devcontainer.IsProvisioned() { + if markers.IsPresent(marker) { return nil } - // Local script path (inside agent repo / container) scriptPath := filepath.Join( - provision.ScriptsRoot, + executil.ScriptsRoot, "devcontainer", "node", "install.sh", ) - if err := provision.RunScript(scriptPath); err != nil { + if err := executil.RunScript(scriptPath); err != nil { return fmt.Errorf("node devcontainer install failed: %w", err) } - // Mark devcontainer ready - if err := devcontainer.WriteReadyMarker("node"); err != nil { - return fmt.Errorf("failed to write devcontainer ready marker: %w", err) - } - - return nil + return markers.Write(marker) } diff --git a/internal/provision/devcontainer/node/verify.go b/internal/provision/devcontainer/node/verify.go index 62ecff1..5de377c 100644 --- a/internal/provision/devcontainer/node/verify.go +++ b/internal/provision/devcontainer/node/verify.go @@ -7,26 +7,13 @@ import ( "zlh-agent/internal/state" ) -/* - Verify validates that the Node.js dev container runtime is usable. - - Responsibilities: - - Ensure `node` is present and executable - - Ensure `npm` is present and executable - - IMPORTANT: - - No installation here - - No state mutation - - Safe to call multiple times -*/ func Verify(cfg state.Config) error { - // Check node binary + // Version is optional at verify-time; existence is authoritative if _, err := exec.LookPath("node"); err != nil { return fmt.Errorf("node binary not found in PATH") } - // Check npm binary if _, err := exec.LookPath("npm"); err != nil { return fmt.Errorf("npm binary not found in PATH") } diff --git a/internal/provision/devcontainer/python/install.go b/internal/provision/devcontainer/python/install.go index 7641218..1f1a709 100644 --- a/internal/provision/devcontainer/python/install.go +++ b/internal/provision/devcontainer/python/install.go @@ -4,43 +4,28 @@ import ( "fmt" "path/filepath" - "zlh-agent/internal/provision" - "zlh-agent/internal/provision/devcontainer" + "zlh-agent/internal/provision/executil" + "zlh-agent/internal/provision/markers" "zlh-agent/internal/state" ) -/* - Install installs the Python dev container runtime. - - Execution model: - - Uses local, versioned install scripts from the agent repo - - Scripts are responsible for fetching artifacts if needed - - IMPORTANT: - - This function ONLY installs - - No verification here -*/ func Install(cfg state.Config) error { + const marker = "devcontainer-python" - // Idempotency guard - if devcontainer.IsProvisioned() { + if markers.IsPresent(marker) { return nil } scriptPath := filepath.Join( - provision.ScriptsRoot, + executil.ScriptsRoot, "devcontainer", "python", "install.sh", ) - if err := provision.RunScript(scriptPath); err != nil { + if err := executil.RunScript(scriptPath); err != nil { return fmt.Errorf("python devcontainer install failed: %w", err) } - if err := devcontainer.WriteReadyMarker("python"); err != nil { - return fmt.Errorf("failed to write devcontainer ready marker: %w", err) - } - - return nil + return markers.Write(marker) } diff --git a/internal/provision/devcontainer/python/verify.go b/internal/provision/devcontainer/python/verify.go index 1a58dfe..8ad9775 100644 --- a/internal/provision/devcontainer/python/verify.go +++ b/internal/provision/devcontainer/python/verify.go @@ -7,29 +7,12 @@ import ( "zlh-agent/internal/state" ) -/* - Verify validates that the Python dev container runtime is usable. - - Responsibilities: - - Ensure `python3` is present and executable - - Ensure `pip3` is present and executable - - IMPORTANT: - - No installation here - - No state mutation - - Safe to call multiple times -*/ func Verify(cfg state.Config) error { - - // Check python3 binary if _, err := exec.LookPath("python3"); err != nil { return fmt.Errorf("python3 binary not found in PATH") } - - // Check pip3 binary if _, err := exec.LookPath("pip3"); err != nil { return fmt.Errorf("pip3 binary not found in PATH") } - return nil } diff --git a/internal/provision/executil/executil.go b/internal/provision/executil/executil.go new file mode 100644 index 0000000..1935c4f --- /dev/null +++ b/internal/provision/executil/executil.go @@ -0,0 +1,15 @@ +package executil + +import ( + "os" + "os/exec" +) + +const ScriptsRoot = "/opt/zlh-agent/scripts" + +func RunScript(path string) error { + cmd := exec.Command("/bin/bash", path) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} diff --git a/internal/provision/markers/markers.go b/internal/provision/markers/markers.go new file mode 100644 index 0000000..388e39d --- /dev/null +++ b/internal/provision/markers/markers.go @@ -0,0 +1,22 @@ +package markers + +import ( + "os" + "path/filepath" +) + +const ( + baseDir = "/opt/zlh/.zlh" +) + +func IsPresent(name string) bool { + _, err := os.Stat(filepath.Join(baseDir, name)) + return err == nil +} + +func Write(name string) error { + if err := os.MkdirAll(baseDir, 0755); err != nil { + return err + } + return os.WriteFile(filepath.Join(baseDir, name), []byte("ok"), 0644) +} diff --git a/scripts/addons/codeserver/install.sh b/scripts/addons/codeserver/install.sh deleted file mode 100644 index c8e32b2..0000000 --- a/scripts/addons/codeserver/install.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -set -e - -echo "[addon:codeserver] installing code-server" - -# Official install script (can be replaced later if you want full control) -curl -fsSL https://code-server.dev/install.sh | sh - -echo "[addon:codeserver] install complete" diff --git a/scripts/devcontainer/codeserver/install.sh b/scripts/devcontainer/codeserver/install.sh new file mode 100644 index 0000000..c7d4ac3 --- /dev/null +++ b/scripts/devcontainer/codeserver/install.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[code-server] starting install" + +# -------------------------------------------------- +# Config +# -------------------------------------------------- +ADDON_ROOT="/opt/zlh/addons/code-server" +ARTIFACT_DIR="/opt/zlh/addons/code-server" +MARKER="/opt/zlh/.zlh/addons/code-server.installed" + +mkdir -p "$(dirname "${MARKER}")" + +# -------------------------------------------------- +# Idempotency +# -------------------------------------------------- +if [ -f "${MARKER}" ]; then + echo "[code-server] already installed" + exit 0 +fi + +ARCHIVE="$(ls ${ARTIFACT_DIR}/code-server.* 2>/dev/null | head -n1)" +if [ -z "${ARCHIVE}" ]; then + echo "[code-server][ERROR] artifact not found" + exit 1 +fi + +echo "[code-server] extracting ${ARCHIVE}" +mkdir -p "${ADDON_ROOT}" + +TMP_DIR="$(mktemp -d)" + +case "${ARCHIVE}" in + *.tar.gz) + tar -xzf "${ARCHIVE}" -C "${TMP_DIR}" + ;; + *.zip) + unzip -q "${ARCHIVE}" -d "${TMP_DIR}" + ;; + *) + echo "[code-server][ERROR] unsupported archive format" + exit 1 + ;; +esac + +EXTRACTED_DIR="$(find ${TMP_DIR} -maxdepth 1 -type d -name 'code-server*' | head -n1)" +if [ -z "${EXTRACTED_DIR}" ]; then + echo "[code-server][ERROR] failed to locate extracted directory" + exit 1 +fi + +mv "${EXTRACTED_DIR}"/* "${ADDON_ROOT}/" +rm -rf "${TMP_DIR}" + +chmod +x "${ADDON_ROOT}/bin/code-server" + +touch "${MARKER}" + +echo "[code-server] install complete" diff --git a/scripts/devcontainer/go/install.sh b/scripts/devcontainer/go/install.sh index f43c268..8fe7e25 100644 --- a/scripts/devcontainer/go/install.sh +++ b/scripts/devcontainer/go/install.sh @@ -1,12 +1,51 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../workspace.sh" +echo "[go] starting go devcontainer install" -echo "[devcontainer:go] installing golang" +# -------------------------------------------------- +# Config +# -------------------------------------------------- +RUNTIME_ROOT="/opt/zlh/runtime/go" +ARTIFACT_ROOT="/opt/zlh/devcontainer/go" -apt update -apt install -y golang +VERSION="${RUNTIME_VERSION:-1.25}" -echo "[devcontainer:go] install complete" +SRC_DIR="${ARTIFACT_ROOT}/${VERSION}" +DEST_DIR="${RUNTIME_ROOT}/${VERSION}" +SYMLINK="${RUNTIME_ROOT}/current" + +# -------------------------------------------------- +# Guards +# -------------------------------------------------- +if [ ! -d "${SRC_DIR}" ]; then + echo "[go][ERROR] artifact directory not found: ${SRC_DIR}" + exit 1 +fi + +if [ -x "${DEST_DIR}/bin/go" ]; then + echo "[go] go ${VERSION} already installed" +else + ARCHIVE="$(ls ${SRC_DIR}/go*.linux-amd64.tar.gz 2>/dev/null | head -n1)" + if [ -z "${ARCHIVE}" ]; then + echo "[go][ERROR] no Go archive found in ${SRC_DIR}" + exit 1 + fi + + echo "[go] installing go ${VERSION}" + mkdir -p "${DEST_DIR}" + + tar -xzf "${ARCHIVE}" -C "${DEST_DIR}" --strip-components=1 +fi + +# -------------------------------------------------- +# Symlink current +# -------------------------------------------------- +ln -sfn "${DEST_DIR}" "${SYMLINK}" + +# -------------------------------------------------- +# Verify +# -------------------------------------------------- +"${SYMLINK}/bin/go" version + +echo "[go] go ${VERSION} install complete" diff --git a/scripts/devcontainer/java/install.sh b/scripts/devcontainer/java/install.sh index 13bdbba..d5274d3 100644 --- a/scripts/devcontainer/java/install.sh +++ b/scripts/devcontainer/java/install.sh @@ -1,12 +1,62 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../workspace.sh" +echo "[java] starting java devcontainer install" -echo "[devcontainer:java] installing jdk" +# -------------------------------------------------- +# Config +# -------------------------------------------------- +RUNTIME_ROOT="/opt/zlh/runtime/java" +ARTIFACT_ROOT="/opt/zlh/devcontainer/java" -apt update -apt install -y openjdk-21-jdk +VERSION="${RUNTIME_VERSION:-17}" -echo "[devcontainer:java] install complete" +SRC_DIR="${ARTIFACT_ROOT}/${VERSION}" +DEST_DIR="${RUNTIME_ROOT}/${VERSION}" +SYMLINK="${RUNTIME_ROOT}/current" + +# -------------------------------------------------- +# Guards +# -------------------------------------------------- +if [ ! -d "${SRC_DIR}" ]; then + echo "[java][ERROR] artifact directory not found: ${SRC_DIR}" + exit 1 +fi + +if [ -x "${DEST_DIR}/bin/java" ]; then + echo "[java] java ${VERSION} already installed" +else + ARCHIVE="$(ls ${SRC_DIR}/*.tar.gz 2>/dev/null | head -n1)" + if [ -z "${ARCHIVE}" ]; then + echo "[java][ERROR] no Java archive found in ${SRC_DIR}" + exit 1 + fi + + echo "[java] installing java ${VERSION}" + mkdir -p "${DEST_DIR}" + + TMP_DIR="$(mktemp -d)" + tar -xzf "${ARCHIVE}" -C "${TMP_DIR}" + + # Java archives contain a single root dir + EXTRACTED_DIR="$(find ${TMP_DIR} -maxdepth 1 -type d -name 'jdk*' | head -n1)" + if [ -z "${EXTRACTED_DIR}" ]; then + echo "[java][ERROR] failed to locate extracted jdk directory" + exit 1 + fi + + mv "${EXTRACTED_DIR}"/* "${DEST_DIR}/" + rm -rf "${TMP_DIR}" +fi + +# -------------------------------------------------- +# Symlink current +# -------------------------------------------------- +ln -sfn "${DEST_DIR}" "${SYMLINK}" + +# -------------------------------------------------- +# Verify +# -------------------------------------------------- +"${SYMLINK}/bin/java" -version + +echo "[java] java ${VERSION} install complete" diff --git a/scripts/devcontainer/node/install.sh b/scripts/devcontainer/node/install.sh index 6e186a4..8fb62d8 100644 --- a/scripts/devcontainer/node/install.sh +++ b/scripts/devcontainer/node/install.sh @@ -1,12 +1,40 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../workspace.sh" +RUNTIME_ROOT="/opt/zlh/runtime/node" +ARTIFACT_ROOT="/opt/zlh/devcontainer/node" -echo "[devcontainer:node] installing nodejs" +VERSION="${RUNTIME_VERSION:-24}" -apt update -apt install -y nodejs npm +ARCHIVE="node-v${VERSION}.*-linux-x64.tar.xz" +SRC_DIR="${ARTIFACT_ROOT}/${VERSION}" +DEST_DIR="${RUNTIME_ROOT}/${VERSION}" -echo "[devcontainer:node] install complete" +echo "[node] Installing Node.js version ${VERSION}" + +# Ensure runtime root exists +mkdir -p "${RUNTIME_ROOT}" + +# Idempotency check +if [ -d "${DEST_DIR}" ]; then + echo "[node] Node ${VERSION} already installed" +else + ARCHIVE_PATH=$(ls "${SRC_DIR}/${ARCHIVE}" 2>/dev/null || true) + + if [ -z "${ARCHIVE_PATH}" ]; then + echo "[node][ERROR] Artifact not found for Node ${VERSION}" + exit 1 + fi + + echo "[node] Extracting ${ARCHIVE_PATH}" + mkdir -p "${DEST_DIR}" + tar -xJf "${ARCHIVE_PATH}" -C "${DEST_DIR}" --strip-components=1 +fi + +# Update stable symlink +ln -sfn "${DEST_DIR}" "${RUNTIME_ROOT}/current" + +# Permissions sanity +chmod -R 755 "${DEST_DIR}" + +echo "[node] Node ${VERSION} installed successfully" diff --git a/scripts/devcontainer/python/install.sh b/scripts/devcontainer/python/install.sh index e02f303..9686fc4 100644 --- a/scripts/devcontainer/python/install.sh +++ b/scripts/devcontainer/python/install.sh @@ -1,12 +1,72 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../workspace.sh" +echo "[python] starting python devcontainer install" -echo "[devcontainer:python] installing python" +# -------------------------------------------------- +# Config +# -------------------------------------------------- +RUNTIME_ROOT="/opt/zlh/runtime/python" +ARTIFACT_ROOT="/opt/zlh/devcontainer/python" -apt update -apt install -y python3 python3-pip python3-venv +VERSION="${RUNTIME_VERSION:-3.12}" -echo "[devcontainer:python] install complete" +SRC_DIR="${ARTIFACT_ROOT}/${VERSION}" +DEST_DIR="${RUNTIME_ROOT}/${VERSION}" +SYMLINK="${RUNTIME_ROOT}/current" + +# -------------------------------------------------- +# Guards +# -------------------------------------------------- +if [ ! -d "${SRC_DIR}" ]; then + echo "[python][ERROR] artifact directory not found: ${SRC_DIR}" + exit 1 +fi + +if [ -x "${DEST_DIR}/bin/python3" ]; then + echo "[python] python ${VERSION} already installed" +else + ARCHIVE="$(ls ${SRC_DIR}/Python-*.tgz 2>/dev/null | head -n1)" + if [ -z "${ARCHIVE}" ]; then + echo "[python][ERROR] no Python archive found in ${SRC_DIR}" + exit 1 + fi + + echo "[python] building python ${VERSION}" + mkdir -p "${DEST_DIR}" + + BUILD_DIR="$(mktemp -d)" + tar -xzf "${ARCHIVE}" -C "${BUILD_DIR}" + + PY_SRC="$(find ${BUILD_DIR} -maxdepth 1 -type d -name 'Python-*' | head -n1)" + if [ -z "${PY_SRC}" ]; then + echo "[python][ERROR] failed to locate extracted python source" + exit 1 + fi + + cd "${PY_SRC}" + + ./configure \ + --prefix="${DEST_DIR}" \ + --enable-optimizations \ + --with-ensurepip=install + + make -j"$(nproc)" + make install + + cd / + rm -rf "${BUILD_DIR}" +fi + +# -------------------------------------------------- +# Symlink current +# -------------------------------------------------- +ln -sfn "${DEST_DIR}" "${SYMLINK}" + +# -------------------------------------------------- +# Verify +# -------------------------------------------------- +"${SYMLINK}/bin/python3" --version +"${SYMLINK}/bin/pip3" --version + +echo "[python] python ${VERSION} install complete" diff --git a/zlh-agent b/zlh-agent index adb914f..59e95af 100755 Binary files a/zlh-agent and b/zlh-agent differ