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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extra Minecraft verification ONLY for Minecraft
|
|
||||||
if strings.ToLower(cfg.Game) == "minecraft" {
|
if strings.ToLower(cfg.Game) == "minecraft" {
|
||||||
if err := minecraft.VerifyMinecraftInstallWithRepair(*cfg); err != nil {
|
if err := minecraft.VerifyMinecraftInstallWithRepair(*cfg); err != nil {
|
||||||
state.SetError(err)
|
state.SetError(err)
|
||||||
@ -67,39 +66,40 @@ func runProvisionPipeline(cfg *state.Config) error {
|
|||||||
----------------------------------------------------------------------------*/
|
----------------------------------------------------------------------------*/
|
||||||
func ensureProvisioned(cfg *state.Config) error {
|
func ensureProvisioned(cfg *state.Config) error {
|
||||||
|
|
||||||
/* ---------------------------------------------------------
|
if cfg.ContainerType == "dev" {
|
||||||
DEV CONTAINERS (FIRST-CLASS)
|
|
||||||
--------------------------------------------------------- */
|
|
||||||
if cfg.ContainerType == "dev" {
|
|
||||||
|
|
||||||
// If not provisioned yet, install
|
if !devcontainer.IsProvisioned() {
|
||||||
if !devcontainer.IsProvisioned() {
|
if err := runProvisionPipeline(cfg); err != nil {
|
||||||
return runProvisionPipeline(cfg)
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify runtime
|
var err error
|
||||||
switch strings.ToLower(cfg.Runtime) {
|
|
||||||
|
|
||||||
case "node":
|
switch strings.ToLower(cfg.Runtime) {
|
||||||
return node.Verify(*cfg)
|
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":
|
if err != nil {
|
||||||
return python.Verify(*cfg)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
case "go":
|
// ✅ DEV READY = RUNNING
|
||||||
return goenv.Verify(*cfg)
|
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)
|
dir := provision.ServerDir(*cfg)
|
||||||
game := strings.ToLower(cfg.Game)
|
game := strings.ToLower(cfg.Game)
|
||||||
variant := strings.ToLower(cfg.Variant)
|
variant := strings.ToLower(cfg.Variant)
|
||||||
@ -109,12 +109,10 @@ func ensureProvisioned(cfg *state.Config) error {
|
|||||||
isSteam := provision.IsSteamGame(game)
|
isSteam := provision.IsSteamGame(game)
|
||||||
isForgeLike := variant == "forge" || variant == "neoforge"
|
isForgeLike := variant == "forge" || variant == "neoforge"
|
||||||
|
|
||||||
// ---------- STEAM GAMES ALWAYS REQUIRE PROVISION ----------
|
|
||||||
if isSteam {
|
if isSteam {
|
||||||
return runProvisionPipeline(cfg)
|
return runProvisionPipeline(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- FORGE / NEOFORGE ----------
|
|
||||||
if isForgeLike {
|
if isForgeLike {
|
||||||
runSh := filepath.Join(dir, "run.sh")
|
runSh := filepath.Join(dir, "run.sh")
|
||||||
libraries := filepath.Join(dir, "libraries")
|
libraries := filepath.Join(dir, "libraries")
|
||||||
@ -122,11 +120,9 @@ func ensureProvisioned(cfg *state.Config) error {
|
|||||||
if fileExists(runSh) && dirExists(libraries) {
|
if fileExists(runSh) && dirExists(libraries) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return runProvisionPipeline(cfg)
|
return runProvisionPipeline(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- VANILLA / PAPER / PURPUR / FABRIC ----------
|
|
||||||
jar := filepath.Join(dir, "server.jar")
|
jar := filepath.Join(dir, "server.jar")
|
||||||
if fileExists(jar) {
|
if fileExists(jar) {
|
||||||
return nil
|
return nil
|
||||||
@ -165,14 +161,54 @@ func handleConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dev containers may not start a server process
|
|
||||||
if c.ContainerType != "dev" {
|
if c.ContainerType != "dev" {
|
||||||
|
|
||||||
if err := system.StartServer(&c); err != nil {
|
if err := system.StartServer(&c); err != nil {
|
||||||
log.Println("[agent] start error:", err)
|
log.Println("[agent] start error:", err)
|
||||||
state.SetError(err)
|
state.SetError(err)
|
||||||
state.SetState(state.StateError)
|
state.SetState(state.StateError)
|
||||||
return
|
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")
|
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) {
|
func handleStart(w http.ResponseWriter, r *http.Request) {
|
||||||
cfg, err := state.LoadConfig()
|
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) {
|
func handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
cfg, _ := state.LoadConfig()
|
cfg, _ := state.LoadConfig()
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package goenv
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"zlh-agent/internal/provision/executil"
|
"zlh-agent/internal/provision/executil"
|
||||||
"zlh-agent/internal/provision/markers"
|
"zlh-agent/internal/provision/markers"
|
||||||
@ -16,14 +15,9 @@ func Install(cfg state.Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptPath := filepath.Join(
|
if err := executil.RunEmbeddedScript(
|
||||||
executil.ScriptsRoot,
|
"devcontainer/go/install.sh",
|
||||||
"devcontainer",
|
); err != nil {
|
||||||
"go",
|
|
||||||
"install.sh",
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := executil.RunScript(scriptPath); err != nil {
|
|
||||||
return fmt.Errorf("go devcontainer install failed: %w", err)
|
return fmt.Errorf("go devcontainer install failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,14 +2,22 @@ package goenv
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"zlh-agent/internal/state"
|
"zlh-agent/internal/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const goBin = "/opt/zlh/runtime/go/bin/go"
|
||||||
|
|
||||||
func Verify(cfg state.Config) error {
|
func Verify(cfg state.Config) error {
|
||||||
if _, err := exec.LookPath("go"); err != nil {
|
if _, err := os.Stat(goBin); err != nil {
|
||||||
return fmt.Errorf("go binary not found in PATH")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package java
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"zlh-agent/internal/provision/executil"
|
"zlh-agent/internal/provision/executil"
|
||||||
"zlh-agent/internal/provision/markers"
|
"zlh-agent/internal/provision/markers"
|
||||||
@ -10,21 +9,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Install(cfg state.Config) error {
|
func Install(cfg state.Config) error {
|
||||||
|
const marker = "devcontainer-java"
|
||||||
|
|
||||||
if markers.IsPresent("devcontainer-java") {
|
if markers.IsPresent(marker) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptPath := filepath.Join(
|
if err := executil.RunEmbeddedScript(
|
||||||
executil.ScriptsRoot,
|
"devcontainer/java/install.sh",
|
||||||
"devcontainer",
|
); err != nil {
|
||||||
"java",
|
|
||||||
"install.sh",
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := executil.RunScript(scriptPath); err != nil {
|
|
||||||
return fmt.Errorf("java devcontainer install failed: %w", err)
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"zlh-agent/internal/state"
|
"zlh-agent/internal/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const javaBin = "/opt/zlh/runtime/java/bin/java"
|
||||||
|
|
||||||
func Verify(cfg state.Config) error {
|
func Verify(cfg state.Config) error {
|
||||||
if _, err := exec.LookPath("java"); err != nil {
|
if _, err := os.Stat(javaBin); err != nil {
|
||||||
return fmt.Errorf("java binary not found in PATH")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ func Install(cfg state.Config) error {
|
|||||||
|
|
||||||
// Execute embedded installer (mirrors game server model)
|
// Execute embedded installer (mirrors game server model)
|
||||||
if err := executil.RunEmbeddedScript(
|
if err := executil.RunEmbeddedScript(
|
||||||
"scripts/devcontainer/node/install.sh",
|
"devcontainer/node/install.sh",
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("node devcontainer install failed: %w", err)
|
return fmt.Errorf("node devcontainer install failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,21 +2,34 @@ package node
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"zlh-agent/internal/state"
|
"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 {
|
func Verify(cfg state.Config) error {
|
||||||
|
|
||||||
// Version is optional at verify-time; existence is authoritative
|
// Node must exist
|
||||||
if _, err := exec.LookPath("node"); err != nil {
|
if _, err := os.Stat(nodeBin); err != nil {
|
||||||
return fmt.Errorf("node binary not found in PATH")
|
return fmt.Errorf("node binary missing at %s", nodeBin)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := exec.LookPath("npm"); err != nil {
|
// Node must execute
|
||||||
return fmt.Errorf("npm binary not found in PATH")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package python
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"zlh-agent/internal/provision/executil"
|
"zlh-agent/internal/provision/executil"
|
||||||
"zlh-agent/internal/provision/markers"
|
"zlh-agent/internal/provision/markers"
|
||||||
@ -16,14 +15,9 @@ func Install(cfg state.Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptPath := filepath.Join(
|
if err := executil.RunEmbeddedScript(
|
||||||
executil.ScriptsRoot,
|
"devcontainer/python/install.sh",
|
||||||
"devcontainer",
|
); err != nil {
|
||||||
"python",
|
|
||||||
"install.sh",
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := executil.RunScript(scriptPath); err != nil {
|
|
||||||
return fmt.Errorf("python devcontainer install failed: %w", err)
|
return fmt.Errorf("python devcontainer install failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,17 +2,34 @@ package python
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"zlh-agent/internal/state"
|
"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 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"zlh-agent/scripts"
|
"zlh-agent/scripts"
|
||||||
)
|
)
|
||||||
@ -12,7 +13,8 @@ import (
|
|||||||
// RunEmbeddedScript executes an embedded script via bash by piping its contents to stdin.
|
// 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.
|
// This mirrors RunScript's stdout/stderr behavior without requiring any files on disk.
|
||||||
func RunEmbeddedScript(path string) error {
|
func RunEmbeddedScript(path string) error {
|
||||||
data, err := scripts.FS.ReadFile(path)
|
normalized := normalizeEmbeddedPath(path)
|
||||||
|
data, err := scripts.FS.ReadFile(normalized)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("embedded script not found: %s", path)
|
return fmt.Errorf("embedded script not found: %s", path)
|
||||||
}
|
}
|
||||||
@ -26,3 +28,7 @@ func RunEmbeddedScript(path string) error {
|
|||||||
|
|
||||||
return cmd.Run()
|
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
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
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}"
|
mkdir -p "${RUNTIME_ROOT}"
|
||||||
DEST_DIR="${RUNTIME_ROOT}/${VERSION}"
|
|
||||||
SYMLINK="${RUNTIME_ROOT}/current"
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
if [ ! -d "${DEST_DIR}" ]; then
|
||||||
# Guards
|
curl -fL "${URL}" -o /tmp/${ARCHIVE_FILE}
|
||||||
# --------------------------------------------------
|
|
||||||
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}"
|
mkdir -p "${DEST_DIR}"
|
||||||
|
tar -xf /tmp/${ARCHIVE_FILE} -C "${DEST_DIR}" --strip-components=1
|
||||||
tar -xzf "${ARCHIVE}" -C "${DEST_DIR}" --strip-components=1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --------------------------------------------------
|
ln -sfn "${DEST_DIR}" "${RUNTIME_ROOT}/current"
|
||||||
# Symlink current
|
ln -sfn "${DEST_DIR}/bin" "${RUNTIME_ROOT}/bin"
|
||||||
# --------------------------------------------------
|
|
||||||
ln -sfn "${DEST_DIR}" "${SYMLINK}"
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
cat >/etc/profile.d/zlh-go.sh <<'EOF'
|
||||||
# Verify
|
export PATH="/opt/zlh/runtime/go/bin:$PATH"
|
||||||
# --------------------------------------------------
|
EOF
|
||||||
"${SYMLINK}/bin/go" version
|
chmod +x /etc/profile.d/zlh-go.sh
|
||||||
|
|
||||||
echo "[go] go ${VERSION} install complete"
|
|
||||||
|
|||||||
@ -1,62 +1,26 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo "[java] starting java devcontainer install"
|
RUNTIME="java"
|
||||||
|
ARCHIVE_FILE="jdk-${DEST_VERSION}.tar.gz"
|
||||||
|
|
||||||
# --------------------------------------------------
|
ARTIFACT_BASE_URL="${ZLH_ARTIFACT_BASE_URL:-http://10.60.0.251:8080}"
|
||||||
# Config
|
DEST_DIR="${RUNTIME_ROOT}/${DEST_VERSION}"
|
||||||
# --------------------------------------------------
|
ARCHIVE_FILE="jdk-${DEST_VERSION}.tar.gz"
|
||||||
RUNTIME_ROOT="/opt/zlh/runtime/java"
|
URL="${ARTIFACT_BASE_URL%/}/devcontainer/java/${DEST_VERSION}/${ARCHIVE_FILE}"
|
||||||
ARTIFACT_ROOT="/opt/zlh/devcontainer/java"
|
|
||||||
|
|
||||||
VERSION="${RUNTIME_VERSION:-17}"
|
mkdir -p "${RUNTIME_ROOT}"
|
||||||
|
|
||||||
SRC_DIR="${ARTIFACT_ROOT}/${VERSION}"
|
if [ ! -d "${DEST_DIR}" ]; then
|
||||||
DEST_DIR="${RUNTIME_ROOT}/${VERSION}"
|
curl -fL "${URL}" -o /tmp/${ARCHIVE_FILE}
|
||||||
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}"
|
mkdir -p "${DEST_DIR}"
|
||||||
|
tar -xf /tmp/${ARCHIVE_FILE} -C "${DEST_DIR}" --strip-components=1
|
||||||
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
|
fi
|
||||||
|
|
||||||
# --------------------------------------------------
|
ln -sfn "${DEST_DIR}" "${RUNTIME_ROOT}/current"
|
||||||
# Symlink current
|
ln -sfn "${DEST_DIR}/bin" "${RUNTIME_ROOT}/bin"
|
||||||
# --------------------------------------------------
|
|
||||||
ln -sfn "${DEST_DIR}" "${SYMLINK}"
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
cat >/etc/profile.d/zlh-java.sh <<'EOF'
|
||||||
# Verify
|
export PATH="/opt/zlh/runtime/java/bin:$PATH"
|
||||||
# --------------------------------------------------
|
EOF
|
||||||
"${SYMLINK}/bin/java" -version
|
chmod +x /etc/profile.d/zlh-java.sh
|
||||||
|
|
||||||
echo "[java] java ${VERSION} install complete"
|
|
||||||
|
|||||||
@ -1,40 +1,33 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
RUNTIME_ROOT="/opt/zlh/runtime/node"
|
RUNTIME="node"
|
||||||
ARTIFACT_ROOT="/opt/zlh/devcontainer/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}"
|
mkdir -p "${RUNTIME_ROOT}"
|
||||||
|
|
||||||
# Idempotency check
|
if [ ! -d "${DEST_DIR}" ]; then
|
||||||
if [ -d "${DEST_DIR}" ]; then
|
curl -fL "${URL}" -o /tmp/${ARCHIVE_FILE}
|
||||||
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}"
|
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
|
fi
|
||||||
|
|
||||||
# Update stable symlink
|
|
||||||
ln -sfn "${DEST_DIR}" "${RUNTIME_ROOT}/current"
|
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}"
|
chmod -R 755 "${DEST_DIR}"
|
||||||
|
|
||||||
echo "[node] Node ${VERSION} installed successfully"
|
|
||||||
|
|||||||
@ -1,72 +1,55 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
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}/${DEST_VERSION}"
|
||||||
DEST_DIR="${RUNTIME_ROOT}/${VERSION}"
|
TMP_DIR="/tmp/zlh-python-install"
|
||||||
SYMLINK="${RUNTIME_ROOT}/current"
|
ARCHIVE_FILE="python-${DEST_VERSION}.tar.xz"
|
||||||
|
URL="${ARTIFACT_BASE_URL%/}/devcontainer/python/${DEST_VERSION}/${ARCHIVE_FILE}"
|
||||||
|
|
||||||
# --------------------------------------------------
|
echo "[python] Installing Python ${DEST_VERSION}"
|
||||||
# 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
|
mkdir -p "${RUNTIME_ROOT}"
|
||||||
echo "[python] python ${VERSION} already installed"
|
|
||||||
|
# Idempotency
|
||||||
|
if [ -d "${DEST_DIR}" ]; then
|
||||||
|
echo "[python] Python ${DEST_VERSION} already installed at ${DEST_DIR}"
|
||||||
else
|
else
|
||||||
ARCHIVE="$(ls ${SRC_DIR}/Python-*.tgz 2>/dev/null | head -n1)"
|
echo "[python] Downloading ${URL}"
|
||||||
if [ -z "${ARCHIVE}" ]; then
|
rm -rf "${TMP_DIR}"
|
||||||
echo "[python][ERROR] no Python archive found in ${SRC_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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[python] building python ${VERSION}"
|
echo "[python] Extracting ${ARCHIVE_FILE} -> ${DEST_DIR}"
|
||||||
mkdir -p "${DEST_DIR}"
|
mkdir -p "${DEST_DIR}"
|
||||||
|
tar -xf "${TMP_DIR}/${ARCHIVE_FILE}" -C "${DEST_DIR}" --strip-components=1
|
||||||
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
|
fi
|
||||||
|
|
||||||
# --------------------------------------------------
|
# Stable symlinks (same model as Node)
|
||||||
# Symlink current
|
ln -sfn "${DEST_DIR}" "${RUNTIME_ROOT}/current"
|
||||||
# --------------------------------------------------
|
ln -sfn "${DEST_DIR}/bin" "${RUNTIME_ROOT}/bin"
|
||||||
ln -sfn "${DEST_DIR}" "${SYMLINK}"
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
# System-wide PATH export
|
||||||
# Verify
|
cat >/etc/profile.d/zlh-python.sh <<'EOF'
|
||||||
# --------------------------------------------------
|
export PATH="/opt/zlh/runtime/python/bin:$PATH"
|
||||||
"${SYMLINK}/bin/python3" --version
|
EOF
|
||||||
"${SYMLINK}/bin/pip3" --version
|
|
||||||
|
|
||||||
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