agent update 12-21-25
This commit is contained in:
parent
146737cf50
commit
8645548c4a
@ -48,7 +48,6 @@ func runProvisionPipeline(cfg *state.Config) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Extra Minecraft verification ONLY for Minecraft
|
||||
if strings.ToLower(cfg.Game) == "minecraft" {
|
||||
if err := minecraft.VerifyMinecraftInstallWithRepair(*cfg); err != nil {
|
||||
state.SetError(err)
|
||||
@ -67,39 +66,40 @@ func runProvisionPipeline(cfg *state.Config) error {
|
||||
----------------------------------------------------------------------------*/
|
||||
func ensureProvisioned(cfg *state.Config) error {
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
DEV CONTAINERS (FIRST-CLASS)
|
||||
--------------------------------------------------------- */
|
||||
if cfg.ContainerType == "dev" {
|
||||
if cfg.ContainerType == "dev" {
|
||||
|
||||
// If not provisioned yet, install
|
||||
if !devcontainer.IsProvisioned() {
|
||||
return runProvisionPipeline(cfg)
|
||||
}
|
||||
if !devcontainer.IsProvisioned() {
|
||||
if err := runProvisionPipeline(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Verify runtime
|
||||
switch strings.ToLower(cfg.Runtime) {
|
||||
var err error
|
||||
|
||||
case "node":
|
||||
return node.Verify(*cfg)
|
||||
switch strings.ToLower(cfg.Runtime) {
|
||||
case "node":
|
||||
err = node.Verify(*cfg)
|
||||
case "python":
|
||||
err = python.Verify(*cfg)
|
||||
case "go":
|
||||
err = goenv.Verify(*cfg)
|
||||
case "java":
|
||||
err = java.Verify(*cfg)
|
||||
default:
|
||||
return fmt.Errorf("unsupported devcontainer runtime: %s", cfg.Runtime)
|
||||
}
|
||||
|
||||
case "python":
|
||||
return python.Verify(*cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "go":
|
||||
return goenv.Verify(*cfg)
|
||||
// ✅ DEV READY = RUNNING
|
||||
state.SetState(state.StateRunning)
|
||||
state.SetError(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
case "java":
|
||||
return java.Verify(*cfg)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported devcontainer runtime: %s", cfg.Runtime)
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
GAME SERVERS (EXISTING LOGIC)
|
||||
--------------------------------------------------------- */
|
||||
dir := provision.ServerDir(*cfg)
|
||||
game := strings.ToLower(cfg.Game)
|
||||
variant := strings.ToLower(cfg.Variant)
|
||||
@ -109,12 +109,10 @@ func ensureProvisioned(cfg *state.Config) error {
|
||||
isSteam := provision.IsSteamGame(game)
|
||||
isForgeLike := variant == "forge" || variant == "neoforge"
|
||||
|
||||
// ---------- STEAM GAMES ALWAYS REQUIRE PROVISION ----------
|
||||
if isSteam {
|
||||
return runProvisionPipeline(cfg)
|
||||
}
|
||||
|
||||
// ---------- FORGE / NEOFORGE ----------
|
||||
if isForgeLike {
|
||||
runSh := filepath.Join(dir, "run.sh")
|
||||
libraries := filepath.Join(dir, "libraries")
|
||||
@ -122,11 +120,9 @@ func ensureProvisioned(cfg *state.Config) error {
|
||||
if fileExists(runSh) && dirExists(libraries) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return runProvisionPipeline(cfg)
|
||||
}
|
||||
|
||||
// ---------- VANILLA / PAPER / PURPUR / FABRIC ----------
|
||||
jar := filepath.Join(dir, "server.jar")
|
||||
if fileExists(jar) {
|
||||
return nil
|
||||
@ -165,14 +161,54 @@ func handleConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Dev containers may not start a server process
|
||||
if c.ContainerType != "dev" {
|
||||
|
||||
if err := system.StartServer(&c); err != nil {
|
||||
log.Println("[agent] start error:", err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
return
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// FORGE / NEOFORGE: wait → stop → patch → restart
|
||||
// -------------------------------------------------
|
||||
game := strings.ToLower(c.Game)
|
||||
variant := strings.ToLower(c.Variant)
|
||||
|
||||
if game == "minecraft" && (variant == "forge" || variant == "neoforge") {
|
||||
|
||||
deadline := time.Now().Add(5 * time.Minute)
|
||||
for {
|
||||
if state.GetState() == state.StateRunning {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
err := fmt.Errorf("forge did not reach running state")
|
||||
log.Println("[agent]", err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
return
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
_ = system.StopServer()
|
||||
|
||||
if err := minecraft.EnforceForgeServerProperties(c); err != nil {
|
||||
log.Println("[agent] forge post-start error:", err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := system.StartServer(&c); err != nil {
|
||||
log.Println("[agent] restart error:", err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("[agent] async provision+start complete")
|
||||
@ -184,7 +220,7 @@ func handleConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
/start — manual UI start
|
||||
/start
|
||||
----------------------------------------------------------------------------*/
|
||||
func handleStart(w http.ResponseWriter, r *http.Request) {
|
||||
cfg, err := state.LoadConfig()
|
||||
@ -245,7 +281,7 @@ func handleRestart(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
/status — API polls this
|
||||
/status
|
||||
----------------------------------------------------------------------------*/
|
||||
func handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
cfg, _ := state.LoadConfig()
|
||||
|
||||
@ -2,7 +2,6 @@ package goenv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"zlh-agent/internal/provision/executil"
|
||||
"zlh-agent/internal/provision/markers"
|
||||
@ -16,14 +15,9 @@ func Install(cfg state.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
scriptPath := filepath.Join(
|
||||
executil.ScriptsRoot,
|
||||
"devcontainer",
|
||||
"go",
|
||||
"install.sh",
|
||||
)
|
||||
|
||||
if err := executil.RunScript(scriptPath); err != nil {
|
||||
if err := executil.RunEmbeddedScript(
|
||||
"devcontainer/go/install.sh",
|
||||
); err != nil {
|
||||
return fmt.Errorf("go devcontainer install failed: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@ -2,14 +2,22 @@ package goenv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"zlh-agent/internal/state"
|
||||
)
|
||||
|
||||
const goBin = "/opt/zlh/runtime/go/bin/go"
|
||||
|
||||
func Verify(cfg state.Config) error {
|
||||
if _, err := exec.LookPath("go"); err != nil {
|
||||
return fmt.Errorf("go binary not found in PATH")
|
||||
if _, err := os.Stat(goBin); err != nil {
|
||||
return fmt.Errorf("go binary missing at %s", goBin)
|
||||
}
|
||||
|
||||
if err := exec.Command(goBin, "version").Run(); err != nil {
|
||||
return fmt.Errorf("go runtime not executable: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package java
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"zlh-agent/internal/provision/executil"
|
||||
"zlh-agent/internal/provision/markers"
|
||||
@ -10,21 +9,17 @@ import (
|
||||
)
|
||||
|
||||
func Install(cfg state.Config) error {
|
||||
const marker = "devcontainer-java"
|
||||
|
||||
if markers.IsPresent("devcontainer-java") {
|
||||
if markers.IsPresent(marker) {
|
||||
return nil
|
||||
}
|
||||
|
||||
scriptPath := filepath.Join(
|
||||
executil.ScriptsRoot,
|
||||
"devcontainer",
|
||||
"java",
|
||||
"install.sh",
|
||||
)
|
||||
|
||||
if err := executil.RunScript(scriptPath); err != nil {
|
||||
if err := executil.RunEmbeddedScript(
|
||||
"devcontainer/java/install.sh",
|
||||
); err != nil {
|
||||
return fmt.Errorf("java devcontainer install failed: %w", err)
|
||||
}
|
||||
|
||||
return markers.Write("devcontainer-java")
|
||||
return markers.Write(marker)
|
||||
}
|
||||
|
||||
@ -2,14 +2,22 @@ package java
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"zlh-agent/internal/state"
|
||||
)
|
||||
|
||||
const javaBin = "/opt/zlh/runtime/java/bin/java"
|
||||
|
||||
func Verify(cfg state.Config) error {
|
||||
if _, err := exec.LookPath("java"); err != nil {
|
||||
return fmt.Errorf("java binary not found in PATH")
|
||||
if _, err := os.Stat(javaBin); err != nil {
|
||||
return fmt.Errorf("java binary missing at %s", javaBin)
|
||||
}
|
||||
|
||||
if err := exec.Command(javaBin, "-version").Run(); err != nil {
|
||||
return fmt.Errorf("java runtime not executable: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ func Install(cfg state.Config) error {
|
||||
|
||||
// Execute embedded installer (mirrors game server model)
|
||||
if err := executil.RunEmbeddedScript(
|
||||
"scripts/devcontainer/node/install.sh",
|
||||
"devcontainer/node/install.sh",
|
||||
); err != nil {
|
||||
return fmt.Errorf("node devcontainer install failed: %w", err)
|
||||
}
|
||||
|
||||
@ -2,21 +2,34 @@ package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"zlh-agent/internal/state"
|
||||
)
|
||||
|
||||
const (
|
||||
nodeBin = "/opt/zlh/runtime/node/bin/node"
|
||||
npmBin = "/opt/zlh/runtime/node/bin/npm"
|
||||
)
|
||||
|
||||
func Verify(cfg state.Config) error {
|
||||
|
||||
// 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")
|
||||
// Node must exist
|
||||
if _, err := os.Stat(nodeBin); err != nil {
|
||||
return fmt.Errorf("node binary missing at %s", nodeBin)
|
||||
}
|
||||
|
||||
if _, err := exec.LookPath("npm"); err != nil {
|
||||
return fmt.Errorf("npm binary not found in PATH")
|
||||
// Node must execute
|
||||
if err := exec.Command(nodeBin, "-v").Run(); err != nil {
|
||||
return fmt.Errorf("node runtime not executable: %w", err)
|
||||
}
|
||||
|
||||
// npm must exist (installation completeness)
|
||||
if _, err := os.Stat(npmBin); err != nil {
|
||||
return fmt.Errorf("npm missing at %s", npmBin)
|
||||
}
|
||||
|
||||
// Do NOT execute npm here — it is PATH-dependent by design
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package python
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"zlh-agent/internal/provision/executil"
|
||||
"zlh-agent/internal/provision/markers"
|
||||
@ -16,14 +15,9 @@ func Install(cfg state.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
scriptPath := filepath.Join(
|
||||
executil.ScriptsRoot,
|
||||
"devcontainer",
|
||||
"python",
|
||||
"install.sh",
|
||||
)
|
||||
|
||||
if err := executil.RunScript(scriptPath); err != nil {
|
||||
if err := executil.RunEmbeddedScript(
|
||||
"devcontainer/python/install.sh",
|
||||
); err != nil {
|
||||
return fmt.Errorf("python devcontainer install failed: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@ -2,17 +2,34 @@ package python
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"zlh-agent/internal/state"
|
||||
)
|
||||
|
||||
const (
|
||||
pythonBin = "/opt/zlh/runtime/python/bin/python3"
|
||||
pipBin = "/opt/zlh/runtime/python/bin/pip3"
|
||||
)
|
||||
|
||||
func Verify(cfg state.Config) error {
|
||||
if _, err := exec.LookPath("python3"); err != nil {
|
||||
return fmt.Errorf("python3 binary not found in PATH")
|
||||
|
||||
// python3 must exist
|
||||
if _, err := os.Stat(pythonBin); err != nil {
|
||||
return fmt.Errorf("python3 binary missing at %s", pythonBin)
|
||||
}
|
||||
if _, err := exec.LookPath("pip3"); err != nil {
|
||||
return fmt.Errorf("pip3 binary not found in PATH")
|
||||
|
||||
// python3 must execute
|
||||
if err := exec.Command(pythonBin, "--version").Run(); err != nil {
|
||||
return fmt.Errorf("python3 runtime not executable: %w", err)
|
||||
}
|
||||
|
||||
// pip must exist (completeness check only)
|
||||
if _, err := os.Stat(pipBin); err != nil {
|
||||
return fmt.Errorf("pip3 missing at %s", pipBin)
|
||||
}
|
||||
|
||||
// Do NOT execute pip (PATH + env dependent)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"zlh-agent/scripts"
|
||||
)
|
||||
@ -12,7 +13,8 @@ import (
|
||||
// RunEmbeddedScript executes an embedded script via bash by piping its contents to stdin.
|
||||
// This mirrors RunScript's stdout/stderr behavior without requiring any files on disk.
|
||||
func RunEmbeddedScript(path string) error {
|
||||
data, err := scripts.FS.ReadFile(path)
|
||||
normalized := normalizeEmbeddedPath(path)
|
||||
data, err := scripts.FS.ReadFile(normalized)
|
||||
if err != nil {
|
||||
return fmt.Errorf("embedded script not found: %s", path)
|
||||
}
|
||||
@ -25,4 +27,8 @@ func RunEmbeddedScript(path string) error {
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeEmbeddedPath(path string) string {
|
||||
return strings.TrimPrefix(path, "scripts/")
|
||||
}
|
||||
|
||||
21
internal/provision/executil/embedded_exec_test.go
Normal file
21
internal/provision/executil/embedded_exec_test.go
Normal file
@ -0,0 +1,21 @@
|
||||
package executil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"zlh-agent/scripts"
|
||||
)
|
||||
|
||||
func TestNormalizeEmbeddedPath(t *testing.T) {
|
||||
paths := []string{
|
||||
"devcontainer/node/install.sh",
|
||||
"scripts/devcontainer/node/install.sh",
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
normalized := normalizeEmbeddedPath(path)
|
||||
if _, err := scripts.FS.ReadFile(normalized); err != nil {
|
||||
t.Fatalf("expected embedded script to resolve for %q (normalized to %q): %v", path, normalized, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
53
internal/provision/minecraft/forge_post.go
Normal file
53
internal/provision/minecraft/forge_post.go
Normal file
@ -0,0 +1,53 @@
|
||||
package minecraft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"zlh-agent/internal/state"
|
||||
)
|
||||
|
||||
/*
|
||||
EnforceForgeServerProperties
|
||||
- Runs AFTER first Forge/NeoForge server start
|
||||
- Ensures Velocity-compatible settings
|
||||
- Requires restart to take effect
|
||||
*/
|
||||
func EnforceForgeServerProperties(cfg state.Config) error {
|
||||
variant := strings.ToLower(cfg.Variant)
|
||||
if variant != "forge" && variant != "neoforge" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Forge working directory is authoritative
|
||||
serverDir := filepath.Join("/opt/zlh/minecraft", variant, "world")
|
||||
propsPath := filepath.Join(serverDir, "server.properties")
|
||||
|
||||
data, err := os.ReadFile(propsPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read server.properties: %w", err)
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
found := false
|
||||
|
||||
for i, l := range lines {
|
||||
if strings.HasPrefix(l, "online-mode=") {
|
||||
lines[i] = "online-mode=false"
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
lines = append(lines, "online-mode=false")
|
||||
}
|
||||
|
||||
out := strings.Join(lines, "\n")
|
||||
if err := os.WriteFile(propsPath, []byte(out), 0644); err != nil {
|
||||
return fmt.Errorf("write server.properties: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,51 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "[go] starting go devcontainer install"
|
||||
RUNTIME="go"
|
||||
ARCHIVE_FILE="go-${DEST_VERSION}.tar.gz"
|
||||
|
||||
# --------------------------------------------------
|
||||
# Config
|
||||
# --------------------------------------------------
|
||||
RUNTIME_ROOT="/opt/zlh/runtime/go"
|
||||
ARTIFACT_ROOT="/opt/zlh/devcontainer/go"
|
||||
|
||||
VERSION="${RUNTIME_VERSION:-1.25}"
|
||||
ARTIFACT_BASE_URL="${ZLH_ARTIFACT_BASE_URL:-http://10.60.0.251:8080}"
|
||||
DEST_DIR="${RUNTIME_ROOT}/${DEST_VERSION}"
|
||||
ARCHIVE_FILE="go-${DEST_VERSION}.tar.gz"
|
||||
URL="${ARTIFACT_BASE_URL%/}/devcontainer/go/${DEST_VERSION}/${ARCHIVE_FILE}"
|
||||
|
||||
SRC_DIR="${ARTIFACT_ROOT}/${VERSION}"
|
||||
DEST_DIR="${RUNTIME_ROOT}/${VERSION}"
|
||||
SYMLINK="${RUNTIME_ROOT}/current"
|
||||
mkdir -p "${RUNTIME_ROOT}"
|
||||
|
||||
# --------------------------------------------------
|
||||
# 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}"
|
||||
if [ ! -d "${DEST_DIR}" ]; then
|
||||
curl -fL "${URL}" -o /tmp/${ARCHIVE_FILE}
|
||||
mkdir -p "${DEST_DIR}"
|
||||
|
||||
tar -xzf "${ARCHIVE}" -C "${DEST_DIR}" --strip-components=1
|
||||
tar -xf /tmp/${ARCHIVE_FILE} -C "${DEST_DIR}" --strip-components=1
|
||||
fi
|
||||
|
||||
# --------------------------------------------------
|
||||
# Symlink current
|
||||
# --------------------------------------------------
|
||||
ln -sfn "${DEST_DIR}" "${SYMLINK}"
|
||||
ln -sfn "${DEST_DIR}" "${RUNTIME_ROOT}/current"
|
||||
ln -sfn "${DEST_DIR}/bin" "${RUNTIME_ROOT}/bin"
|
||||
|
||||
# --------------------------------------------------
|
||||
# Verify
|
||||
# --------------------------------------------------
|
||||
"${SYMLINK}/bin/go" version
|
||||
|
||||
echo "[go] go ${VERSION} install complete"
|
||||
cat >/etc/profile.d/zlh-go.sh <<'EOF'
|
||||
export PATH="/opt/zlh/runtime/go/bin:$PATH"
|
||||
EOF
|
||||
chmod +x /etc/profile.d/zlh-go.sh
|
||||
|
||||
@ -1,62 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "[java] starting java devcontainer install"
|
||||
RUNTIME="java"
|
||||
ARCHIVE_FILE="jdk-${DEST_VERSION}.tar.gz"
|
||||
|
||||
# --------------------------------------------------
|
||||
# Config
|
||||
# --------------------------------------------------
|
||||
RUNTIME_ROOT="/opt/zlh/runtime/java"
|
||||
ARTIFACT_ROOT="/opt/zlh/devcontainer/java"
|
||||
ARTIFACT_BASE_URL="${ZLH_ARTIFACT_BASE_URL:-http://10.60.0.251:8080}"
|
||||
DEST_DIR="${RUNTIME_ROOT}/${DEST_VERSION}"
|
||||
ARCHIVE_FILE="jdk-${DEST_VERSION}.tar.gz"
|
||||
URL="${ARTIFACT_BASE_URL%/}/devcontainer/java/${DEST_VERSION}/${ARCHIVE_FILE}"
|
||||
|
||||
VERSION="${RUNTIME_VERSION:-17}"
|
||||
mkdir -p "${RUNTIME_ROOT}"
|
||||
|
||||
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}"
|
||||
if [ ! -d "${DEST_DIR}" ]; then
|
||||
curl -fL "${URL}" -o /tmp/${ARCHIVE_FILE}
|
||||
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}"
|
||||
tar -xf /tmp/${ARCHIVE_FILE} -C "${DEST_DIR}" --strip-components=1
|
||||
fi
|
||||
|
||||
# --------------------------------------------------
|
||||
# Symlink current
|
||||
# --------------------------------------------------
|
||||
ln -sfn "${DEST_DIR}" "${SYMLINK}"
|
||||
ln -sfn "${DEST_DIR}" "${RUNTIME_ROOT}/current"
|
||||
ln -sfn "${DEST_DIR}/bin" "${RUNTIME_ROOT}/bin"
|
||||
|
||||
# --------------------------------------------------
|
||||
# Verify
|
||||
# --------------------------------------------------
|
||||
"${SYMLINK}/bin/java" -version
|
||||
|
||||
echo "[java] java ${VERSION} install complete"
|
||||
cat >/etc/profile.d/zlh-java.sh <<'EOF'
|
||||
export PATH="/opt/zlh/runtime/java/bin:$PATH"
|
||||
EOF
|
||||
chmod +x /etc/profile.d/zlh-java.sh
|
||||
|
||||
@ -1,40 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
RUNTIME_ROOT="/opt/zlh/runtime/node"
|
||||
ARTIFACT_ROOT="/opt/zlh/devcontainer/node"
|
||||
RUNTIME="node"
|
||||
RUNTIME_ROOT="/opt/zlh/runtime/${RUNTIME}"
|
||||
DEST_VERSION="${RUNTIME_VERSION:?RUNTIME_VERSION required}"
|
||||
|
||||
VERSION="${RUNTIME_VERSION:-24}"
|
||||
ARTIFACT_BASE_URL="${ZLH_ARTIFACT_BASE_URL:-http://10.60.0.251:8080}"
|
||||
ARCHIVE_FILE="${RUNTIME}-${DEST_VERSION}.tar.xz"
|
||||
URL="${ARTIFACT_BASE_URL%/}/devcontainer/${RUNTIME}/${DEST_VERSION}/${ARCHIVE_FILE}"
|
||||
|
||||
ARCHIVE="node-v${VERSION}.*-linux-x64.tar.xz"
|
||||
SRC_DIR="${ARTIFACT_ROOT}/${VERSION}"
|
||||
DEST_DIR="${RUNTIME_ROOT}/${VERSION}"
|
||||
|
||||
echo "[node] Installing Node.js version ${VERSION}"
|
||||
DEST_DIR="${RUNTIME_ROOT}/${DEST_VERSION}"
|
||||
|
||||
echo "[${RUNTIME}] Installing ${RUNTIME} ${DEST_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}"
|
||||
if [ ! -d "${DEST_DIR}" ]; then
|
||||
curl -fL "${URL}" -o /tmp/${ARCHIVE_FILE}
|
||||
mkdir -p "${DEST_DIR}"
|
||||
tar -xJf "${ARCHIVE_PATH}" -C "${DEST_DIR}" --strip-components=1
|
||||
tar -xf /tmp/${ARCHIVE_FILE} -C "${DEST_DIR}" --strip-components=1
|
||||
fi
|
||||
|
||||
# Update stable symlink
|
||||
ln -sfn "${DEST_DIR}" "${RUNTIME_ROOT}/current"
|
||||
ln -sfn "${DEST_DIR}/bin" "${RUNTIME_ROOT}/bin"
|
||||
|
||||
cat >/etc/profile.d/zlh-${RUNTIME}.sh <<EOF
|
||||
export PATH="/opt/zlh/runtime/${RUNTIME}/bin:\$PATH"
|
||||
EOF
|
||||
chmod +x /etc/profile.d/zlh-${RUNTIME}.sh
|
||||
|
||||
# Permissions sanity
|
||||
chmod -R 755 "${DEST_DIR}"
|
||||
|
||||
echo "[node] Node ${VERSION} installed successfully"
|
||||
|
||||
@ -1,72 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "[python] starting python devcontainer install"
|
||||
RUNTIME="python"
|
||||
ARCHIVE_FILE="python-${DEST_VERSION}.tar.xz"
|
||||
|
||||
# --------------------------------------------------
|
||||
# Config
|
||||
# --------------------------------------------------
|
||||
RUNTIME_ROOT="/opt/zlh/runtime/python"
|
||||
ARTIFACT_ROOT="/opt/zlh/devcontainer/python"
|
||||
|
||||
VERSION="${RUNTIME_VERSION:-3.12}"
|
||||
ARTIFACT_BASE_URL="${ZLH_ARTIFACT_BASE_URL:-http://10.60.0.251:8080}"
|
||||
|
||||
SRC_DIR="${ARTIFACT_ROOT}/${VERSION}"
|
||||
DEST_DIR="${RUNTIME_ROOT}/${VERSION}"
|
||||
SYMLINK="${RUNTIME_ROOT}/current"
|
||||
DEST_DIR="${RUNTIME_ROOT}/${DEST_VERSION}"
|
||||
TMP_DIR="/tmp/zlh-python-install"
|
||||
ARCHIVE_FILE="python-${DEST_VERSION}.tar.xz"
|
||||
URL="${ARTIFACT_BASE_URL%/}/devcontainer/python/${DEST_VERSION}/${ARCHIVE_FILE}"
|
||||
|
||||
# --------------------------------------------------
|
||||
# Guards
|
||||
# --------------------------------------------------
|
||||
if [ ! -d "${SRC_DIR}" ]; then
|
||||
echo "[python][ERROR] artifact directory not found: ${SRC_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
echo "[python] Installing Python ${DEST_VERSION}"
|
||||
|
||||
if [ -x "${DEST_DIR}/bin/python3" ]; then
|
||||
echo "[python] python ${VERSION} already installed"
|
||||
mkdir -p "${RUNTIME_ROOT}"
|
||||
|
||||
# Idempotency
|
||||
if [ -d "${DEST_DIR}" ]; then
|
||||
echo "[python] Python ${DEST_VERSION} already installed at ${DEST_DIR}"
|
||||
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}"
|
||||
echo "[python] Downloading ${URL}"
|
||||
rm -rf "${TMP_DIR}"
|
||||
mkdir -p "${TMP_DIR}"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fL "${URL}" -o "${TMP_DIR}/${ARCHIVE_FILE}"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -O "${TMP_DIR}/${ARCHIVE_FILE}" "${URL}"
|
||||
else
|
||||
echo "[python][ERROR] curl or wget is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[python] building python ${VERSION}"
|
||||
echo "[python] Extracting ${ARCHIVE_FILE} -> ${DEST_DIR}"
|
||||
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}"
|
||||
tar -xf "${TMP_DIR}/${ARCHIVE_FILE}" -C "${DEST_DIR}" --strip-components=1
|
||||
fi
|
||||
|
||||
# --------------------------------------------------
|
||||
# Symlink current
|
||||
# --------------------------------------------------
|
||||
ln -sfn "${DEST_DIR}" "${SYMLINK}"
|
||||
# Stable symlinks (same model as Node)
|
||||
ln -sfn "${DEST_DIR}" "${RUNTIME_ROOT}/current"
|
||||
ln -sfn "${DEST_DIR}/bin" "${RUNTIME_ROOT}/bin"
|
||||
|
||||
# --------------------------------------------------
|
||||
# Verify
|
||||
# --------------------------------------------------
|
||||
"${SYMLINK}/bin/python3" --version
|
||||
"${SYMLINK}/bin/pip3" --version
|
||||
# System-wide PATH export
|
||||
cat >/etc/profile.d/zlh-python.sh <<'EOF'
|
||||
export PATH="/opt/zlh/runtime/python/bin:$PATH"
|
||||
EOF
|
||||
|
||||
echo "[python] python ${VERSION} install complete"
|
||||
chmod +x /etc/profile.d/zlh-python.sh
|
||||
|
||||
# Permissions sanity
|
||||
chmod -R 755 "${DEST_DIR}"
|
||||
|
||||
echo "[python] Python ${DEST_VERSION} installed successfully"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user