devcontainer type and code addition 12-14-25
This commit is contained in:
parent
5319b594e0
commit
463baf80e0
@ -12,6 +12,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"zlh-agent/internal/provision"
|
"zlh-agent/internal/provision"
|
||||||
|
"zlh-agent/internal/provision/devcontainer"
|
||||||
|
"zlh-agent/internal/provision/devcontainer/goenv"
|
||||||
|
"zlh-agent/internal/provision/devcontainer/java"
|
||||||
|
"zlh-agent/internal/provision/devcontainer/node"
|
||||||
|
"zlh-agent/internal/provision/devcontainer/python"
|
||||||
"zlh-agent/internal/provision/minecraft"
|
"zlh-agent/internal/provision/minecraft"
|
||||||
"zlh-agent/internal/state"
|
"zlh-agent/internal/state"
|
||||||
"zlh-agent/internal/system"
|
"zlh-agent/internal/system"
|
||||||
@ -37,14 +42,13 @@ func runProvisionPipeline(cfg *state.Config) error {
|
|||||||
state.SetState(state.StateInstalling)
|
state.SetState(state.StateInstalling)
|
||||||
state.SetInstallStep("provision_all")
|
state.SetInstallStep("provision_all")
|
||||||
|
|
||||||
// Installer (downloads files, patches, configs, etc.)
|
|
||||||
if err := provision.ProvisionAll(*cfg); err != nil {
|
if err := provision.ProvisionAll(*cfg); err != nil {
|
||||||
state.SetError(err)
|
state.SetError(err)
|
||||||
state.SetState(state.StateError)
|
state.SetState(state.StateError)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extra Minecraft verification
|
// 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)
|
||||||
@ -59,9 +63,43 @@ func runProvisionPipeline(cfg *state.Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------
|
||||||
ensureProvisioned() — idempotent, no goto
|
ensureProvisioned() — idempotent, unified
|
||||||
----------------------------------------------------------------------------*/
|
----------------------------------------------------------------------------*/
|
||||||
func ensureProvisioned(cfg *state.Config) error {
|
func ensureProvisioned(cfg *state.Config) error {
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------
|
||||||
|
DEV CONTAINERS (FIRST-CLASS)
|
||||||
|
--------------------------------------------------------- */
|
||||||
|
if cfg.ContainerType == "dev" {
|
||||||
|
|
||||||
|
// If not provisioned yet, install
|
||||||
|
if !devcontainer.IsProvisioned() {
|
||||||
|
return runProvisionPipeline(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify runtime
|
||||||
|
switch strings.ToLower(cfg.Runtime) {
|
||||||
|
|
||||||
|
case "node":
|
||||||
|
return node.Verify(*cfg)
|
||||||
|
|
||||||
|
case "python":
|
||||||
|
return python.Verify(*cfg)
|
||||||
|
|
||||||
|
case "go":
|
||||||
|
return goenv.Verify(*cfg)
|
||||||
|
|
||||||
|
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)
|
||||||
@ -82,7 +120,7 @@ func ensureProvisioned(cfg *state.Config) error {
|
|||||||
libraries := filepath.Join(dir, "libraries")
|
libraries := filepath.Join(dir, "libraries")
|
||||||
|
|
||||||
if fileExists(runSh) && dirExists(libraries) {
|
if fileExists(runSh) && dirExists(libraries) {
|
||||||
return nil // already provisioned
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return runProvisionPipeline(cfg)
|
return runProvisionPipeline(cfg)
|
||||||
@ -91,7 +129,7 @@ func ensureProvisioned(cfg *state.Config) error {
|
|||||||
// ---------- VANILLA / PAPER / PURPUR / FABRIC ----------
|
// ---------- VANILLA / PAPER / PURPUR / FABRIC ----------
|
||||||
jar := filepath.Join(dir, "server.jar")
|
jar := filepath.Join(dir, "server.jar")
|
||||||
if fileExists(jar) {
|
if fileExists(jar) {
|
||||||
return nil // already provisioned
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return runProvisionPipeline(cfg)
|
return runProvisionPipeline(cfg)
|
||||||
@ -119,7 +157,6 @@ func handleConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run provision + server start asynchronously
|
|
||||||
go func(c state.Config) {
|
go func(c state.Config) {
|
||||||
log.Println("[agent] async provision+start begin")
|
log.Println("[agent] async provision+start begin")
|
||||||
|
|
||||||
@ -128,15 +165,18 @@ func handleConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dev containers may not start a server process
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Println("[agent] async provision+start complete")
|
log.Println("[agent] async provision+start complete")
|
||||||
}(*&cfg)
|
}(cfg)
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusAccepted)
|
w.WriteHeader(http.StatusAccepted)
|
||||||
@ -144,7 +184,7 @@ func handleConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------
|
||||||
/start — manual UI start (does NOT re-provision)
|
/start — manual UI 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()
|
||||||
@ -153,6 +193,11 @@ func handleStart(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.ContainerType == "dev" {
|
||||||
|
http.Error(w, "dev containers do not support manual start", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := system.StartServer(cfg); err != nil {
|
if err := system.StartServer(cfg); err != nil {
|
||||||
http.Error(w, "start error: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "start error: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@ -183,6 +228,11 @@ func handleRestart(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.ContainerType == "dev" {
|
||||||
|
http.Error(w, "dev containers do not support restart", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
_ = system.StopServer()
|
_ = system.StopServer()
|
||||||
|
|
||||||
if err := system.StartServer(cfg); err != nil {
|
if err := system.StartServer(cfg); err != nil {
|
||||||
@ -236,7 +286,7 @@ func handleSendCommand(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------
|
||||||
Router + WebSocket
|
Router
|
||||||
----------------------------------------------------------------------------*/
|
----------------------------------------------------------------------------*/
|
||||||
func NewMux() *http.ServeMux {
|
func NewMux() *http.ServeMux {
|
||||||
m := http.NewServeMux()
|
m := http.NewServeMux()
|
||||||
|
|||||||
@ -0,0 +1,56 @@
|
|||||||
|
package devcontainer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dev container provisioning helpers.
|
||||||
|
|
||||||
|
This file is intentionally small and boring.
|
||||||
|
It exists to support idempotency and shared paths
|
||||||
|
across dev container runtimes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MarkerDir is where devcontainer state is stored.
|
||||||
|
MarkerDir = "/opt/zlh/.zlh"
|
||||||
|
|
||||||
|
// ReadyMarker is written after a dev container is fully provisioned.
|
||||||
|
ReadyMarker = "devcontainer_ready.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadyMarkerPath returns the absolute path to the ready marker file.
|
||||||
|
func ReadyMarkerPath() string {
|
||||||
|
return filepath.Join(MarkerDir, ReadyMarker)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsProvisioned returns true if the dev container has already been installed.
|
||||||
|
func IsProvisioned() bool {
|
||||||
|
_, err := os.Stat(ReadyMarkerPath())
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteReadyMarker records successful dev container provisioning.
|
||||||
|
// This should be called by runtime installers AFTER all install steps succeed.
|
||||||
|
func WriteReadyMarker(runtime string) error {
|
||||||
|
|
||||||
|
if err := os.MkdirAll(MarkerDir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"runtime": runtime,
|
||||||
|
"ready_at": time.Now().UTC().Format(time.RFC3339),
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := json.MarshalIndent(data, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(ReadyMarkerPath(), raw, 0644)
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package devcontainer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"zlh-agent/internal/state"
|
||||||
|
|
||||||
|
devgo "zlh-agent/internal/provision/devcontainer/go"
|
||||||
|
"zlh-agent/internal/provision/devcontainer/java"
|
||||||
|
"zlh-agent/internal/provision/devcontainer/node"
|
||||||
|
"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)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "python":
|
||||||
|
if err := python.Install(cfg); err != nil {
|
||||||
|
return fmt.Errorf("python devcontainer install failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "go":
|
||||||
|
if err := devgo.Install(cfg); err != nil {
|
||||||
|
return fmt.Errorf("go devcontainer install failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "java":
|
||||||
|
if err := java.Install(cfg); err != nil {
|
||||||
|
return fmt.Errorf("java devcontainer install failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported dev container runtime: %s", runtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO NOT VERIFY HERE.
|
||||||
|
// Verification happens in ensureProvisioned().
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package goenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"zlh-agent/internal/provision"
|
||||||
|
"zlh-agent/internal/provision/devcontainer"
|
||||||
|
"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 {
|
||||||
|
|
||||||
|
// Idempotency guard
|
||||||
|
if devcontainer.IsProvisioned() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptPath := filepath.Join(
|
||||||
|
provision.ScriptsRoot,
|
||||||
|
"devcontainer",
|
||||||
|
"go",
|
||||||
|
"install.sh",
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := provision.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
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package goenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"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
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package java
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"zlh-agent/internal/provision"
|
||||||
|
"zlh-agent/internal/provision/devcontainer"
|
||||||
|
"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() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptPath := filepath.Join(
|
||||||
|
provision.ScriptsRoot,
|
||||||
|
"devcontainer",
|
||||||
|
"java",
|
||||||
|
"install.sh",
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := provision.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
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package java
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"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
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"zlh-agent/internal/provision"
|
||||||
|
"zlh-agent/internal/provision/devcontainer"
|
||||||
|
"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 {
|
||||||
|
|
||||||
|
// Idempotency guard
|
||||||
|
if devcontainer.IsProvisioned() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local script path (inside agent repo / container)
|
||||||
|
scriptPath := filepath.Join(
|
||||||
|
provision.ScriptsRoot,
|
||||||
|
"devcontainer",
|
||||||
|
"node",
|
||||||
|
"install.sh",
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := provision.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
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"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
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
46
internal/provision/devcontainer/python/install.go
Normal file
46
internal/provision/devcontainer/python/install.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package python
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"zlh-agent/internal/provision"
|
||||||
|
"zlh-agent/internal/provision/devcontainer"
|
||||||
|
"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 {
|
||||||
|
|
||||||
|
// Idempotency guard
|
||||||
|
if devcontainer.IsProvisioned() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptPath := filepath.Join(
|
||||||
|
provision.ScriptsRoot,
|
||||||
|
"devcontainer",
|
||||||
|
"python",
|
||||||
|
"install.sh",
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := provision.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
|
||||||
|
}
|
||||||
35
internal/provision/devcontainer/python/verify.go
Normal file
35
internal/provision/devcontainer/python/verify.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package python
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"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
|
||||||
|
}
|
||||||
@ -5,18 +5,28 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"zlh-agent/internal/state"
|
"zlh-agent/internal/state"
|
||||||
|
"zlh-agent/internal/provision/devcontainer"
|
||||||
"zlh-agent/internal/provision/minecraft"
|
"zlh-agent/internal/provision/minecraft"
|
||||||
"zlh-agent/internal/provision/steam"
|
"zlh-agent/internal/provision/steam"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
ProvisionAll — unified entrypoint for MC + Steam.
|
ProvisionAll — unified entrypoint for provisioning.
|
||||||
IMPORTANT:
|
IMPORTANT:
|
||||||
- This function ONLY performs installation.
|
- This function ONLY performs installation.
|
||||||
- Validation/verification happens in ensureProvisioned().
|
- Validation/verification happens in ensureProvisioned().
|
||||||
|
- state.Config is treated as immutable desired state.
|
||||||
*/
|
*/
|
||||||
func ProvisionAll(cfg state.Config) error {
|
func ProvisionAll(cfg state.Config) error {
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------
|
||||||
|
DEV CONTAINERS
|
||||||
|
--------------------------------------------------------- */
|
||||||
|
if cfg.ContainerType == "dev" {
|
||||||
|
return devcontainer.Provision(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy / default behavior assumes game containers
|
||||||
game := strings.ToLower(cfg.Game)
|
game := strings.ToLower(cfg.Game)
|
||||||
variant := strings.ToLower(cfg.Variant)
|
variant := strings.ToLower(cfg.Variant)
|
||||||
|
|
||||||
@ -64,7 +74,6 @@ func ProvisionAll(cfg state.Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DO NOT VERIFY HERE.
|
// DO NOT VERIFY HERE.
|
||||||
// Verification happens in ensureProvisioned(), AFTER install.
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,12 +119,16 @@ func ProvisionAll(cfg state.Config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DO NOT VERIFY HERE (Steam verification TBD later)
|
// DO NOT VERIFY HERE.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------
|
/* ---------------------------------------------------------
|
||||||
UNKNOWN GAME TYPE
|
UNKNOWN CONTAINER TYPE
|
||||||
--------------------------------------------------------- */
|
--------------------------------------------------------- */
|
||||||
return fmt.Errorf("unsupported game type: %s", game)
|
return fmt.Errorf(
|
||||||
|
"unsupported container identity (containerType=%q game=%q)",
|
||||||
|
cfg.ContainerType,
|
||||||
|
cfg.Game,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
10
scripts/devcontainer/go/install.sh
Normal file
10
scripts/devcontainer/go/install.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "[devcontainer:<runtime>] starting install"
|
||||||
|
|
||||||
|
# install runtime
|
||||||
|
# install basic tooling
|
||||||
|
# ensure binaries land in PATH
|
||||||
|
|
||||||
|
echo "[devcontainer:<runtime>] install complete"
|
||||||
10
scripts/devcontainer/java/install.sh
Normal file
10
scripts/devcontainer/java/install.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "[devcontainer:<runtime>] starting install"
|
||||||
|
|
||||||
|
# install runtime
|
||||||
|
# install basic tooling
|
||||||
|
# ensure binaries land in PATH
|
||||||
|
|
||||||
|
echo "[devcontainer:<runtime>] install complete"
|
||||||
11
scripts/devcontainer/node/install.sh
Normal file
11
scripts/devcontainer/node/install.sh
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Installing Node.js dev container runtime"
|
||||||
|
|
||||||
|
# example (placeholder)
|
||||||
|
apt update
|
||||||
|
apt install -y nodejs npm
|
||||||
|
|
||||||
|
echo "Node version:"
|
||||||
|
node --version
|
||||||
10
scripts/devcontainer/python/install.sh
Normal file
10
scripts/devcontainer/python/install.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "[devcontainer:<runtime>] starting install"
|
||||||
|
|
||||||
|
# install runtime
|
||||||
|
# install basic tooling
|
||||||
|
# ensure binaries land in PATH
|
||||||
|
|
||||||
|
echo "[devcontainer:<runtime>] install complete"
|
||||||
Loading…
Reference in New Issue
Block a user