From 67c92bc0749caa99484157a1d76226541e406387 Mon Sep 17 00:00:00 2001 From: "Arnaud (Arhuman) ASSAD" Date: Sat, 16 Aug 2025 06:45:50 +0200 Subject: [PATCH] improve install process/documentation --- Dockerfile.console | 19 +- Dockerfile.minion | 18 +- Dockerfile.nexus | 23 +- Makefile | 18 +- README.md | 43 ++- docker-compose.yml | 16 +- integration_test.go | 12 +- internal/certs/certs.go | 4 + internal/certs/files/ca.crt | 31 ++ internal/certs/files/ca.key | 52 +++ internal/certs/files/console.crt | 31 ++ internal/certs/files/console.key | 52 +++ internal/certs/files/server.crt | 32 ++ internal/certs/files/server.key | 52 +++ internal/config/config.go | 105 ++---- internal/web/assets.go | 8 +- internal/web/data.go | 1 + internal/web/handlers.go | 7 + internal/web/templates/dashboard.html | 56 --- internal/web/templates/install_minion.sh | 239 ------------- .../web/{ => webroot}/static/css/main.css | 0 .../web/{ => webroot}/static/css/theme.css | 0 .../{ => webroot}/static/images/favicon.ico | 0 .../web/{ => webroot}/static/images/logo.svg | 0 internal/web/{ => webroot}/static/js/api.js | 0 .../web/{ => webroot}/static/js/dashboard.js | 46 +++ .../web/{ => webroot}/templates/base.html | 0 internal/web/webroot/templates/dashboard.html | 85 +++++ .../web/webroot/templates/install_minion.sh | 53 +++ run_tests.sh | 8 +- webroot/static/css/main.css | 246 -------------- webroot/static/css/theme.css | 171 ---------- webroot/static/images/favicon.ico | 1 - webroot/static/images/logo.svg | 29 -- webroot/static/js/api.js | 183 ---------- webroot/static/js/dashboard.js | 320 ------------------ webroot/templates/base.html | 30 -- webroot/templates/dashboard.html | 56 --- 38 files changed, 554 insertions(+), 1493 deletions(-) create mode 100644 internal/certs/files/ca.crt create mode 100644 internal/certs/files/ca.key create mode 100644 internal/certs/files/console.crt create mode 100644 internal/certs/files/console.key create mode 100644 internal/certs/files/server.crt create mode 100644 internal/certs/files/server.key delete mode 100644 internal/web/templates/dashboard.html delete mode 100644 internal/web/templates/install_minion.sh rename internal/web/{ => webroot}/static/css/main.css (100%) rename internal/web/{ => webroot}/static/css/theme.css (100%) rename internal/web/{ => webroot}/static/images/favicon.ico (100%) rename internal/web/{ => webroot}/static/images/logo.svg (100%) rename internal/web/{ => webroot}/static/js/api.js (100%) rename internal/web/{ => webroot}/static/js/dashboard.js (87%) rename internal/web/{ => webroot}/templates/base.html (100%) create mode 100644 internal/web/webroot/templates/dashboard.html create mode 100644 internal/web/webroot/templates/install_minion.sh delete mode 100644 webroot/static/css/main.css delete mode 100644 webroot/static/css/theme.css delete mode 100644 webroot/static/images/favicon.ico delete mode 100644 webroot/static/images/logo.svg delete mode 100644 webroot/static/js/api.js delete mode 100644 webroot/static/js/dashboard.js delete mode 100644 webroot/templates/base.html delete mode 100644 webroot/templates/dashboard.html diff --git a/Dockerfile.console b/Dockerfile.console index 9b16ce5..b818c7d 100644 --- a/Dockerfile.console +++ b/Dockerfile.console @@ -19,20 +19,11 @@ COPY protogen/ protogen/ RUN CGO_ENABLED=0 GOOS=linux GOFLAGS="-ldflags=-s -ldflags=-w" go build -o console ./cmd/console/ # Runtime stage -FROM debian:bookworm-slim - -ENV TZ=Europe/Zurich - -# Add ca-certificates and basic tools needed for interactive console -RUN apt-get update && apt-get install --no-install-recommends -y \ - ca-certificates \ - jq \ - readline-common \ - && rm -rf /var/lib/apt/lists/* +FROM alpine:latest # Create user and group -RUN groupadd dop && \ - useradd -r --uid 1001 -g dop dop +RUN addgroup -g 1001 dop && \ + adduser -D -u 1001 -G dop dop # Set working directory RUN mkdir -p /app @@ -44,8 +35,8 @@ COPY .env.${MINEXUS_ENV} ./.env.${MINEXUS_ENV} # Copy the binary from builder COPY --from=builder /app/console /app/console -# Create entrypoint script -RUN echo '#!/bin/sh\n/app/console' > /app/docker-entrypoint.sh && \ +# Create entrypoint script (use printf for proper newlines) +RUN printf '#!/bin/sh\nexec /app/console\n' > /app/docker-entrypoint.sh && \ chmod +x /app/docker-entrypoint.sh # Set ownership for Kubernetes compatibility diff --git a/Dockerfile.minion b/Dockerfile.minion index 3ce6020..019cae4 100644 --- a/Dockerfile.minion +++ b/Dockerfile.minion @@ -19,19 +19,11 @@ COPY protogen/ protogen/ RUN CGO_ENABLED=0 GOOS=linux GOFLAGS="-ldflags=-s -ldflags=-w" go build -o minion ./cmd/minion/ # Runtime stage -FROM debian:bookworm-slim - -ENV TZ=Europe/Zurich - -# Add ca-certificates and basic tools -RUN apt-get update && apt-get install --no-install-recommends -y \ - ca-certificates \ - jq \ - && rm -rf /var/lib/apt/lists/* +FROM alpine:latest # Create user and group -RUN groupadd dop && \ - useradd -r --uid 1001 -g dop dop +RUN addgroup -g 1001 dop && \ + adduser -D -u 1001 -G dop dop # Set working directory RUN mkdir -p /app @@ -43,8 +35,8 @@ COPY .env.${MINEXUS_ENV} ./.env.${MINEXUS_ENV} # Copy the binary from builder COPY --from=builder /app/minion /app/minion -# Create entrypoint script -RUN echo '#!/bin/sh\n/app/minion' > /app/docker-entrypoint.sh && \ +# Create entrypoint script (use printf for proper newlines) +RUN printf '#!/bin/sh\nexec /app/minion\n' > /app/docker-entrypoint.sh && \ chmod +x /app/docker-entrypoint.sh # Set ownership for Kubernetes compatibility diff --git a/Dockerfile.nexus b/Dockerfile.nexus index 73f1860..77308f1 100644 --- a/Dockerfile.nexus +++ b/Dockerfile.nexus @@ -19,20 +19,11 @@ COPY protogen/ protogen/ RUN CGO_ENABLED=0 GOOS=linux GOFLAGS="-ldflags=-s -ldflags=-w" go build -o nexus ./cmd/nexus/ # Runtime stage -FROM debian:bookworm-slim +FROM alpine:latest -ENV TZ=Europe/Zurich - -# Add ca-certificates and basic tools -RUN apt-get update && apt-get install --no-install-recommends -y \ - netcat-traditional \ - ca-certificates \ - jq \ - && rm -rf /var/lib/apt/lists/* - -# Create user and group -RUN groupadd dop && \ - useradd -r --uid 1001 -g dop dop +# Create user and group (use -G for group assignment in Alpine) +RUN addgroup -g 1001 dop && \ + adduser -D -u 1001 -G dop dop # Set working directory RUN mkdir -p /app @@ -45,10 +36,10 @@ COPY .env.${MINEXUS_ENV} ./.env.${MINEXUS_ENV} COPY --from=builder /app/nexus /app/nexus # Copy webroot directory for web server assets -COPY webroot/ /app/webroot/ +COPY internal/web/webroot/ /app/webroot/ -# Create entrypoint script -RUN echo '#!/bin/sh\n/app/nexus' > /app/docker-entrypoint.sh && \ +# Create entrypoint script (use printf for proper newlines) +RUN printf '#!/bin/sh\nexec /app/nexus\n' > /app/docker-entrypoint.sh && \ chmod +x /app/docker-entrypoint.sh # Set ownership for Kubernetes compatibility diff --git a/Makefile b/Makefile index 9f504cc..00dbeb2 100644 --- a/Makefile +++ b/Makefile @@ -99,26 +99,29 @@ build-test: ## compose-build: Build Docker images for specified environment (default: test) compose-build: @export MINEXUS_ENV=$${MINEXUS_ENV:-test}; \ - set -a; . ./.env.$$MINEXUS_ENV; set +a; \ echo "Building Docker images for $$MINEXUS_ENV environment..."; \ [ "$$MINEXUS_ENV" = "prod" ] && $(MAKE) certs-prod || true; \ - docker compose build + docker compose --env-file .env.$$MINEXUS_ENV build ## compose-run: Run application in specified environment (default: test) compose-run: @export MINEXUS_ENV=$${MINEXUS_ENV:-test}; \ - set -a; . ./.env.$$MINEXUS_ENV; set +a; \ echo "Starting application in $$MINEXUS_ENV mode..."; \ $(MAKE) compose-stop; \ $(MAKE) compose-build; \ - docker compose up -d + docker compose --env-file .env.$$MINEXUS_ENV up -d ## compose-stop: Stop services for specified environment (default: test) compose-stop: @export MINEXUS_ENV=$${MINEXUS_ENV:-test}; \ - set -a; . ./.env.$$MINEXUS_ENV; set +a; \ echo "Stopping $$MINEXUS_ENV environment..."; \ - docker compose down --remove-orphans + docker compose --env-file .env.$$MINEXUS_ENV down --remove-orphans + +## compose-up: Start services without rebuilding for specified environment (default: test) +compose-up: + @export MINEXUS_ENV=$${MINEXUS_ENV:-test}; \ + echo "Starting $$MINEXUS_ENV environment without rebuild..."; \ + docker compose --env-file .env.$$MINEXUS_ENV up ## certs-clean: remove copied certificates from root certs directory certs-clean: @@ -258,9 +261,8 @@ local: compose-run ## logs-docker: Follow logs for specified environment (default: test) logs-docker: @export MINEXUS_ENV=$${MINEXUS_ENV:-test}; \ - set -a; . ./.env.$$MINEXUS_ENV; set +a; \ echo "Following logs for $$MINEXUS_ENV environment..."; \ - docker compose logs -f + docker compose --env-file .env.$$MINEXUS_ENV logs -f ## minion: build minion client (production environment) minion: diff --git a/README.md b/README.md index 36ee952..87ebaa6 100644 --- a/README.md +++ b/README.md @@ -66,17 +66,46 @@ So I decided to make this agent (minion) the server (nexus) and start by impleme ## Quick Start -create an .env.prod file -```cp env.sample .env.prod``` +### 1. Setup Nexus Server + +Create an .env.prod file: +```bash +cp env.sample .env.prod +``` Modify the .env.prod (DON'T KEEP the default password unchanged...) -Then launch the nexus -```MINEXUS_ENV=prod make compose-run``` +Then launch the nexus: +```bash +MINEXUS_ENV=prod make compose-run +``` + +### 2. Install Minion Clients + +Once Nexus is running, visit the web dashboard at `http://yournexus.address.com:8086` to see quick installation commands with copy buttons. + +Alternatively, use these commands directly: + +**Linux/macOS:** +```bash +curl -sSL http://yournexus.address.com:8086/install_minion.sh | sh +``` + +**Windows PowerShell:** +```powershell +iwr -useb http://yournexus.address.com:8086/download/minion/windows-amd64.exe -OutFile minion.exe; .\minion.exe +``` + +**Options:** +- For systemd installation on Linux: `curl -sSL http://yournexus.address.com:8086/install_minion.sh | sh -s -- --systemd` +- For Windows with custom environment variables: + ```powershell + $env:NEXUS_SERVER="yournexus.address.com" + $env:NEXUS_MINION_PORT="11972" + .\minion.exe + ``` -From now on the hosts where you want to install minion, just -```curl http://yournexus.address.com:8086/install_minion.sh | sh``` -This will download and run the right minion for your OS/ARCH +The installation script automatically detects your OS and architecture, downloading the appropriate minion binary. ## Project Structure diff --git a/docker-compose.yml b/docker-compose.yml index 2be6b59..e5c2513 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,13 +24,13 @@ services: - default # Nexus server - depends on database being healthy - nexus_server: + nexus: build: context: . dockerfile: Dockerfile.nexus args: MINEXUS_ENV: ${MINEXUS_ENV:-test} - container_name: nexus_server + container_name: nexus env_file: - .env.${MINEXUS_ENV:-test} restart: always @@ -55,8 +55,8 @@ services: networks: - default healthcheck: - #test: ["CMD-SHELL", "nc -z nexus_server ${NEXUS_MINION_PORT:-11972} && nc -z nexus_server ${NEXUS_CONSOLE_PORT:-11973} && nc -z nexus_server ${NEXUS_WEB_PORT:-8086} && echo 'All three ports accessible' || exit 1"] - test: ["CMD-SHELL", "nc -z nexus_server ${NEXUS_MINION_PORT:-11972} && nc -z nexus_server ${NEXUS_CONSOLE_PORT:-11973} && echo 'All three ports accessible' || exit 1"] + #test: ["CMD-SHELL", "nc -z nexus ${NEXUS_MINION_PORT:-11972} && nc -z nexus ${NEXUS_CONSOLE_PORT:-11973} && nc -z nexus ${NEXUS_WEB_PORT:-8086} && echo 'All three ports accessible' || exit 1"] + test: ["CMD-SHELL", "nc -z nexus ${NEXUS_MINION_PORT:-11972} && nc -z nexus ${NEXUS_CONSOLE_PORT:-11973} && echo 'All three ports accessible' || exit 1"] interval: 3s timeout: 5s retries: 10 @@ -77,14 +77,14 @@ services: - MINEXUS_ENV=${MINEXUS_ENV:-test} - DEBUG=true - MINION_ID=${MINION_ID:-docker-minion} - - NEXUS_SERVER=nexus_server + - NEXUS_SERVER=nexus - NEXUS_MINION_PORT=${NEXUS_MINION_PORT:-11972} - HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-60} - INITIAL_RECONNECT_DELAY=${INITIAL_RECONNECT_DELAY:-1} - MAX_RECONNECT_DELAY=${MAX_RECONNECT_DELAY:-3600} - CONNECT_TIMEOUT=${CONNECT_TIMEOUT:-3} depends_on: - nexus_server: + nexus: condition: service_healthy networks: - default @@ -102,11 +102,11 @@ services: environment: - MINEXUS_ENV=${MINEXUS_ENV:-test} - DEBUG=${DEBUG:-false} - - NEXUS_SERVER=nexus_server + - NEXUS_SERVER=nexus - NEXUS_CONSOLE_PORT=${NEXUS_CONSOLE_PORT:-11973} - CONNECT_TIMEOUT=${CONNECT_TIMEOUT:-3} depends_on: - nexus_server: + nexus: condition: service_healthy networks: - default diff --git a/integration_test.go b/integration_test.go index 7cc1a8a..c68bc6b 100644 --- a/integration_test.go +++ b/integration_test.go @@ -123,7 +123,7 @@ const ( // // Required Services: // - nexus_db: PostgreSQL database -// - nexus_server: Nexus gRPC dual-port server (port 11972 for minions, 11973 for console) +// - nexus: Nexus gRPC dual-port server (port 11972 for minions, 11973 for console) // - minion_1: Test minion client // // Test Categories: @@ -385,7 +385,7 @@ func setupDockerServices(t *testing.T) { parseDuration := time.Since(parseStart) t.Logf("TIMING: Docker status parsing took %v", parseDuration) - requiredServices := []string{"nexus_db", "nexus_server", "minion"} + requiredServices := []string{"nexus_db", "nexus", "minion"} missingServices := []string{} for _, service := range requiredServices { @@ -399,7 +399,7 @@ func setupDockerServices(t *testing.T) { // Start services serviceStartStart := time.Now() - cmd = exec.Command("docker", "compose", "up", "-d", "nexus_server", "minion") + cmd = exec.Command("docker", "compose", "up", "-d", "nexus", "minion") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -432,9 +432,9 @@ func parseDockerComposePS(output string) map[string]string { services["nexus_db"] = "running" } } - if strings.Contains(line, "nexus_server") { + if strings.Contains(line, "nexus") { if strings.Contains(line, "running") { - services["nexus_server"] = "running" + services["nexus"] = "running" } } if strings.Contains(line, "minion") { @@ -2128,7 +2128,7 @@ func waitForCommandCompletion(t *testing.T, commandID string, maxAttempts int, s // executeNexusRestart executes the nexus server restart command func executeNexusRestart() error { - restartCmd := exec.Command("docker", "compose", "restart", "nexus_server") + restartCmd := exec.Command("docker", "compose", "restart", "nexus") restartCmd.Stdout = os.Stdout restartCmd.Stderr = os.Stderr return restartCmd.Run() diff --git a/internal/certs/certs.go b/internal/certs/certs.go index 3996058..6a7fdb8 100644 --- a/internal/certs/certs.go +++ b/internal/certs/certs.go @@ -4,6 +4,10 @@ import _ "embed" // Static certificate files embedded at build time // These certificates provide a consistent PKI across all builds and deployments +// +// Note: The files in internal/certs/files/ are placeholder test certificates +// that prevent build errors. They are automatically replaced with proper +// certificates by the Makefile during build/test processes. var ( // Certificate Authority diff --git a/internal/certs/files/ca.crt b/internal/certs/files/ca.crt new file mode 100644 index 0000000..8087db1 --- /dev/null +++ b/internal/certs/files/ca.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFQzCCAyugAwIBAgIUfDOkqVEWwK4zsOFYy1mcSyklk1IwDQYJKoZIhvcNAQEL +BQAwMTEYMBYGA1UEAwwPTWluZXh1cy10ZXN0IENBMRUwEwYDVQQKDAxNaW5leHVz +LXRlc3QwHhcNMjUwNjIyMTEwOTMyWhcNMzUwNjIwMTEwOTMyWjAxMRgwFgYDVQQD +DA9NaW5leHVzLXRlc3QgQ0ExFTATBgNVBAoMDE1pbmV4dXMtdGVzdDCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMD2+RNwkj66jtxKU3HAXvyNBhoKnVWw +lnJPTV7AqbGRok/9uDJqYsQ9zLbGvNRTMgle5yuHqV6v19iHDaU7uUXgY8vCoTIc +DZG9n1khIAnMSRXzSacn0Wbv0B8jeEx+Dz81vHpJlw8XV9Qqu1gFTKz9Csv/cefE +heAEfD7AS5gIYHycrAdz/s1QwJrqwHL8tGZb+rCfLcCMMj20FPjbX8djJFSNtdSu +UFAiiAXAygiKlgcbP9+T0mO0l6Gpyv16b5G+8VGQYVUMQg/BGaklJ2HfqQ4yCw2L +4s0EdQ0DF3YKJ1AQUi1iNl7SdbjoYdKqkgyC3PvsIiV98JOxV+OV1Utxjo1bYl5U +8yaQ/n46T1BMtqKojnvgvLOjIaAQ/XD1jcEgBcVP9fPbi/onM76rx74f3T+6Gm+E +PtEz4VN2WnIqFQNp+f/4Nd5g97q7SmLhMNjfCOWO6O4Jj1/BL9R3+NgIXnwV+10T +fxsHSK5N2pFMEEE0Oo7aYlRlpSbIaEOG/DE3z9OJbuh1oX5u2zQB/39vrqwacZKA +yZp77J00s/2kPJdfACQY13mDNzppSIdMzZwfJotEZm2NAEfE5c/Y8E3kgOBq/HnM +Zqc9V7Y3eV1BLWxRvQyJW604cMC43yq1dRXEuR3h9kolwO89FfxW3bcrm/oON8a0 +LsabTaAyHefTAgMBAAGjUzBRMB0GA1UdDgQWBBSzKR/t3uHznVxtKUjGwjjkpz0C +STAfBgNVHSMEGDAWgBSzKR/t3uHznVxtKUjGwjjkpz0CSTAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAfdc28NYw/Wm6O//pijFUNE5H/DUBOhOw/ +tcHyNB3xlAX5genhdUSRU9NvGm01h+KZqCRpZybFaaLMMgJJ7NLttScsuCib5v2t +DYFSzBI4OUYZM2JPiYSM15dybKWlmUh1WVtWhG3NmgZhsilQMzmh419qeiCP93F+ +7sCPk3yqelP5gw3F4LHt57SDtXyxNvmLKpsvNiIDF2E8y+Y3WZx3ua31KCKNl2z6 +6/tpKZQEHzv6eSvWnq/BQxi/PKw83Hd6bt71wKbT9HdTDwwVWVc6bJFiKUFmuu7e +CmePatNt6BEk94cV6BOAvUZCFueXgc6Jglxwc9Z4AVDAOEE9v809d5Kfdu46dz2I +C+Q6Dcjdsr9BAhETfZDWZkTSBw4849H9RNSNNHfMyJqPsByvKhNgdYBqNgMq3xaJ +wasxQQ+TEx/mzOmSX9Z4dzVUbnGE9bnvVvItVm3MgpCTkWGdWccIcDQqbP7ZQznA +EjZ/4oXjzVsNbxgEEzJDlr+db473rRE1/yihFOEUgbAv+HU+RTkKRf+gq+OMUxjQ +goAIPV+eL4O64jDI/jBNY81J4mJdz04O+gVpZZ/5Q5LT2Nxca5mdK3PYHx1lKJQH +/OODakzvT4baCNJv5kOwf4j1ijrM2Okum9H1IbMRgrD5BV4Y/oo43wUXKzBJpiZi +O9VC828vaA== +-----END CERTIFICATE----- diff --git a/internal/certs/files/ca.key b/internal/certs/files/ca.key new file mode 100644 index 0000000..d891ba9 --- /dev/null +++ b/internal/certs/files/ca.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +2X5Q65ylDFI0PI7QDqzblo1Ik6fX +-----END PRIVATE KEY----- diff --git a/internal/certs/files/console.crt b/internal/certs/files/console.crt new file mode 100644 index 0000000..a37f7a4 --- /dev/null +++ b/internal/certs/files/console.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIUAuxMBA3qX5+01uYDJzBI3Kr/+0EwDQYJKoZIhvcNAQEL +BQAwMTEYMBYGA1UEAwwPTWluZXh1cy10ZXN0IENBMRUwEwYDVQQKDAxNaW5leHVz +LXRlc3QwHhcNMjUwNjI5MTAzNzEzWhcNMzUwNjI3MTAzNzEzWjAkMRAwDgYDVQQD +DAdjb25zb2xlMRAwDgYDVQQKDAdNaW5leHVzMIICIjANBgkqhkiG9w0BAQEFAAOC +Ag8AMIICCgKCAgEAsjfm5iceN69V2QSL+SPK6ofiCoILhUOlZD/QKfJsd2z9dJ6c +aykbktwdw12RNIcHOlVWtCO5qq2f2vc/r70mPc9IiQdeMgM/nKywmnDqSoGwt4pt +jNmOWnxGfzF1F/5F/mNq1wIsvwT9bivaH0WfOjKEOwMtIUO+9EVncRq4a4d2NLyA +8c8AQnzh0TFzinMKqGJ0cySyOm6i4H51CJEhNtxuftd25CT1RIfTYiK4D1RCDcDI +SAgbwqYR8IvzEJfdXjgqkGOUERRz34Li79WzAG7nz7/uMGT9z4fl9DZxdCptqq6H +pQ0HLB93FwVVtmRJDTjInc1NiYRWKEYHl5mM/VxQOE/A57nqzMIrjrKRKARzKA3A +kSOdjUpkev+1Iqq/mWQHybWjxSLSA+Vq0aSclwUgTVjOx60ngMA8zRZaMUpm2D+H +j8I7xMvFtrv7QI4ddzYWXQ0/CxjpLLP3cZ0WD6O7cViu1rKUIChJhyT2AZH0OF9C +yzMRO29ojzpM4EohAYdHSLYo9p+XUChO0CjMSSTWmMUPX8rF2NB6AnCubxWOK1xq +d58dhQ96LOjU3bh0r5Jhxra/TlsmKFb+0GqflMj8+FLP6hxtFcMapLxD4qAT/dtD +NpdAtqZJJvRaRAzltoh0dnSUETabzVvBH7ATA3YwMQD/8IlszYE7VktkdccCAwEA +AaNkMGIwCwYDVR0PBAQDAgSwMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQW +BBS5MVFUXh7BvGLOBF1t1Rhn0+CItzAfBgNVHSMEGDAWgBSzKR/t3uHznVxtKUjG +wjjkpz0CSTANBgkqhkiG9w0BAQsFAAOCAgEAJokAajK+q6n712MH5nnXEDqkYbi+ +MGb2sdjOOZTsw2VFCC4pqfEnAi8EDmmMPd/906ivSEvqu8pS5nb/7CzWdNkyariK ++fUnUVcX91rAXBCfCuc2zTYKQKMIQbHiOWZBrjXZeV7DSpi9mCyHcZQrG6Cx5Jua ++JU47Ltike0kBOVjlcdFRqfdwGsI1Qc3I48aTv9OaZMc7T00BIgyLab9SwhMMLw6 +mvSi8Sw/QRZxsumGSBXocpXxWEkSj181t2t86/6sLg9SKKSLCkYpSjL+gxrPyyS2 +5fDZ1+liqeeJfW1WEV0QJY7D60USz+AIVfWFXU+T0x/eEP1JZfVRUuPg7bYtb/9S +hRNj5csLodk20pa/8aN2DrvTa61Z7/zDSd+CK3glMlgH1OibUb8xYigU4YlalZuK +JfRKeZN+wKy3HC5tY/zl+et0lffjDmI4Q2hu+Z/jOU3d1OHEkJXKE3E+jbA4c1ww +295ed4s7VudYmYttMwQZPWJQPAI7ShiXdIsaCXd2qpud+3UbEnwKT8PMhl708wy/ +Q5EH6jOGzl+43RvVodDlKuAGzrNcQbbEqXm48ftzukvc/e52Z2HDUuJqKj2hyBah +T0s8yHlXNL3wIVpT4NcjefsFDoRonQT4GP3PD2gnoDYD1Gxj/0s+U/yNm9KfGBJ5 +D0HIusaYliouet8= +-----END CERTIFICATE----- diff --git a/internal/certs/files/console.key b/internal/certs/files/console.key new file mode 100644 index 0000000..e3740a9 --- /dev/null +++ b/internal/certs/files/console.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +ZIPKmVjHAtc1hwe8pXRUE5CWg7rt1g== +-----END PRIVATE KEY----- diff --git a/internal/certs/files/server.crt b/internal/certs/files/server.crt new file mode 100644 index 0000000..486c9b3 --- /dev/null +++ b/internal/certs/files/server.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFjzCCA3egAwIBAgIURm45CeK+CFDK2yR9QZSkRw4l9TowDQYJKoZIhvcNAQEL +BQAwMTEYMBYGA1UEAwwPTWluZXh1cy10ZXN0IENBMRUwEwYDVQQKDAxNaW5leHVz +LXRlc3QwHhcNMjUwNjI5MTU1MzMzWhcNMzUwNjI3MTU1MzMzWjAnMQ4wDAYDVQQD +DAVuZXh1czEVMBMGA1UECgwMTWluZXh1cy10ZXN0MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAry54ZgAwTLnV1QNXxQin9gtvdS6IkLmVeYPzYd5e3gQX +mH1uxpeNMAQIGq1fuU73NWupCBYnrWp2PJcL/aIb2k8DjZbT6c5m8DEHD+4wQQkL +Z6r9nvE6breAWZtkAs++zyoWJHt5JNDblYUORlpe9aicgO+cFQI/1UqTQfCqdhEj +lfHswPQN5xXNBqJx3Vbmx0tnYlszFlOXE/quiWTrxer3vcGncbj8uCCgZm86fWKe +UhxguwW0Y3WQ/4MNnDU/CaSQkTp2YGqoGue8/DnXgeK9gEwLQxGtsRzv1OadzsM9 +JpMiiCXd0okUwDdQ5oxbDm6jVy8nkstJnrWl6FA7sEMyltE4VhBvXO1yDpwQuSNU +2F5qt0UCqZDJcmk+7NFhVQEXGDSweFN0iljnvJRaf13Zi2Gc3W/lRobfB1zL6VGx +GOlhAl/YB2Kk3Fh8wLP0Rtfi5yRddCAqWKkmn+SMlBwIIa19BAIDsstQd/loit1w +ghRPzZQM5wBiVI1hCSPN11/fQ6DQz0TjhqGo8/GFcX0aCRRW/+Y/do2nTnLSJ7BG +DmxGg+o5AXcoQCd0IAt5j8EtjJJ4H9R6kZNpAnzccf7l2GI1yEwC76KLwwji1X94 +Q60fFisBuaZjscMR7tOTnGTGg7pNU1Z1ifAfjWQOh4ENfD7vtKtS8nNg7DMy7XcC +AwEAAaOBqDCBpTALBgNVHQ8EBAMCBLAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwQQYD +VR0RBDowOIIFbmV4dXOCDG5leHVzX3NlcnZlcoIJbG9jYWxob3N0hwR/AAABhxAA +AAAAAAAAAAAAAAAAAAABMB0GA1UdDgQWBBSwCoDni2OVlLkbCTCrdK4xD2YhkTAf +BgNVHSMEGDAWgBSzKR/t3uHznVxtKUjGwjjkpz0CSTANBgkqhkiG9w0BAQsFAAOC +AgEAb5UzvPJ9skhuwB/6usObpOX3wG44DhFMaCwbbhLkK4S860Ddb3nr+a4ohYcr +Z9yeyfDM/PcVggMJftuorH5kwDBILM3BIN6m973Ywrn3ADbWERzvk3fBToJ3WsOV +ETtFNaiuE25BSEi6JJAMbpUiEo9mFCWfcm3yiFL1QtJhOpAzFoYebs5tTlbytslh +PetVjItZn7FsyEzfjMDo49na6tYlxTB/56fmB137mh2hLtUTMdIDl97TGM6c1/T8 +ZkpFNdaVatS5XaQE9ajFMvqqRJsNxVSDdw5fFsykPaZvtK1DXyFvv4CHr/sTGDO+ +CEvT85BJsd2puHFU5Op9Y8NXG+QTjUNY0L6Bi4aeDDnLRs6ppm6cL2bssJDjrWA3 +7FxNNf8+4tSBRL7OpHSqGAez9oG01APexY7D+j4aMEYwwKzJlfbpterB5iosjZ3O +bp05nud7KvnG93B8hwIBdBWUUho248sNeT3k4Gw9ClIB+0V4A5C7ndD3LuRen09A +hKWdq3ebCHDfYRoZYCoCvEPmW36WLN8PhOByN1iQ6/OJnZi+jW+4KDIzyGFVV7Qo +nrB2PFR9Rztek6QvhoHJASlaPpZxVanfZTc+7SKJ0BKWfOjHbrxl0KzxiRUiQ3eO +I80PW9kj2RR84aBOG2K8JP7jnWRSNky/GBeN0sGpvh/MYzI= +-----END CERTIFICATE----- diff --git a/internal/certs/files/server.key b/internal/certs/files/server.key new file mode 100644 index 0000000..023fffe --- /dev/null +++ b/internal/certs/files/server.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +qq07dCFQYAWTYeSYHXDOgumMLAi/rg== +-----END PRIVATE KEY----- diff --git a/internal/config/config.go b/internal/config/config.go index d52d5ab..83c3c34 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,3 +1,6 @@ +// Package config provides configuration loading and validation for the Minexus application +// It supports environment-specific configurations, command line flags, and strict validation +// to ensure correct application behavior across different environments. package config import ( @@ -60,90 +63,28 @@ func (e ValidationError) Error() string { return fmt.Sprintf("configuration validation failed for %s=%s: %s", e.Field, e.Value, e.Message) } -// ConfigLoader provides unified configuration loading with priority handling -type ConfigLoader struct { +// Loader provides unified configuration loading with priority handling +type Loader struct { envVars map[string]string logger *zap.Logger } // NewConfigLoader creates a new configuration loader -func NewConfigLoader() *ConfigLoader { - return &ConfigLoader{ +func NewConfigLoader() *Loader { + return &Loader{ envVars: make(map[string]string), } } // WithLogger sets the logger for the config loader -func (cl *ConfigLoader) WithLogger(logger *zap.Logger) *ConfigLoader { +func (cl *Loader) WithLogger(logger *zap.Logger) *Loader { cl.logger = logger return cl } -// LoadEnvFile loads environment variables from .env file (LEGACY - use LoadEnvironmentFile) -func (cl *ConfigLoader) LoadEnvFile(filename string) error { - file, err := os.Open(filename) - if err != nil { - // File doesn't exist, not an error - if cl.logger != nil { - cl.logger.Debug("Environment file not found", zap.String("file", filename)) - } - return nil - } - defer file.Close() - - scanner := bufio.NewScanner(file) - lineNum := 0 - for scanner.Scan() { - lineNum++ - line := strings.TrimSpace(scanner.Text()) - - // Skip empty lines and comments - if line == "" || strings.HasPrefix(line, "#") { - continue - } - - // Parse KEY=VALUE format - parts := strings.SplitN(line, "=", 2) - if len(parts) != 2 { - if cl.logger != nil { - cl.logger.Warn("Invalid line in env file", - zap.String("file", filename), - zap.Int("line", lineNum), - zap.String("content", line)) - } - continue - } - - key := strings.TrimSpace(parts[0]) - value := strings.TrimSpace(parts[1]) - - // Remove quotes if present - if len(value) >= 2 { - if (strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`)) || - (strings.HasPrefix(value, `'`) && strings.HasSuffix(value, `'`)) { - value = value[1 : len(value)-1] - } - } - - cl.envVars[key] = value - } - - if err := scanner.Err(); err != nil { - return fmt.Errorf("error reading env file %s: %w", filename, err) - } - - if cl.logger != nil { - cl.logger.Debug("Loaded environment file", - zap.String("file", filename), - zap.Int("variables", len(cl.envVars))) - } - - return nil -} - // LoadEnvironmentFile loads environment variables from environment-specific file // Uses strict validation with no fallback - panics if environment file is missing -func (cl *ConfigLoader) LoadEnvironmentFile() error { +func (cl *Loader) LoadEnvironmentFile() error { env := DetectEnvironment() // Panics on invalid environment filename := GetEnvironmentFileName() @@ -208,7 +149,7 @@ func (cl *ConfigLoader) LoadEnvironmentFile() error { } // GetString gets string value with priority: flags → env → file → default -func (cl *ConfigLoader) GetString(key, defaultValue string) string { +func (cl *Loader) GetString(key, defaultValue string) string { // Check environment variables first (highest priority after flags) if value := os.Getenv(key); value != "" { return value @@ -224,7 +165,7 @@ func (cl *ConfigLoader) GetString(key, defaultValue string) string { } // GetInt gets int value with validation -func (cl *ConfigLoader) GetInt(key string, defaultValue int) (int, error) { +func (cl *Loader) GetInt(key string, defaultValue int) (int, error) { value := cl.GetString(key, "") if value == "" { return defaultValue, nil @@ -243,17 +184,17 @@ func (cl *ConfigLoader) GetInt(key string, defaultValue int) (int, error) { } // GetIntInRange gets int value with range validation -func (cl *ConfigLoader) GetIntInRange(key string, defaultValue, min, max int) (int, error) { +func (cl *Loader) GetIntInRange(key string, defaultValue, minValue, maxValue int) (int, error) { value, err := cl.GetInt(key, defaultValue) if err != nil { return 0, err } - if value < min || value > max { + if value < minValue || value > maxValue { return 0, ValidationError{ Field: key, Value: strconv.Itoa(value), - Message: fmt.Sprintf("must be between %d and %d", min, max), + Message: fmt.Sprintf("must be between %d and %d", minValue, maxValue), } } @@ -261,7 +202,7 @@ func (cl *ConfigLoader) GetIntInRange(key string, defaultValue, min, max int) (i } // GetBool gets bool value with validation -func (cl *ConfigLoader) GetBool(key string, defaultValue bool) (bool, error) { +func (cl *Loader) GetBool(key string, defaultValue bool) (bool, error) { value := cl.GetString(key, "") if value == "" { return defaultValue, nil @@ -280,7 +221,7 @@ func (cl *ConfigLoader) GetBool(key string, defaultValue bool) (bool, error) { } // GetDuration gets duration value with validation -func (cl *ConfigLoader) GetDuration(key string, defaultValue time.Duration) (time.Duration, error) { +func (cl *Loader) GetDuration(key string, defaultValue time.Duration) (time.Duration, error) { value := cl.GetString(key, "") if value == "" { return defaultValue, nil @@ -304,7 +245,7 @@ func (cl *ConfigLoader) GetDuration(key string, defaultValue time.Duration) (tim } // ValidateNetworkAddress validates a network address -func (cl *ConfigLoader) ValidateNetworkAddress(key, value string) error { +func (cl *Loader) ValidateNetworkAddress(key, value string) error { if value == "" { return ValidationError{ Field: key, @@ -344,7 +285,7 @@ func (cl *ConfigLoader) ValidateNetworkAddress(key, value string) error { } // ValidateHostname validates a hostname (without port) -func (cl *ConfigLoader) ValidateHostname(key, value string) error { +func (cl *Loader) ValidateHostname(key, value string) error { if value == "" { return ValidationError{ Field: key, @@ -366,7 +307,7 @@ func (cl *ConfigLoader) ValidateHostname(key, value string) error { } // ValidateRequired ensures a required field is not empty -func (cl *ConfigLoader) ValidateRequired(key, value string) error { +func (cl *Loader) ValidateRequired(key, value string) error { if value == "" { return ValidationError{ Field: key, @@ -378,7 +319,7 @@ func (cl *ConfigLoader) ValidateRequired(key, value string) error { } // ValidateDirectory ensures a directory path is valid -func (cl *ConfigLoader) ValidateDirectory(key, value string) error { +func (cl *Loader) ValidateDirectory(key, value string) error { if value == "" { return ValidationError{ Field: key, @@ -782,7 +723,7 @@ func LoadMinionConfig() (*MinionConfig, error) { } // loadMinionEnvConfig loads configuration from environment variables -func loadMinionEnvConfig(loader *ConfigLoader, config *MinionConfig, validationErrors *[]error) { +func loadMinionEnvConfig(loader *Loader, config *MinionConfig, validationErrors *[]error) { // Load and validate server hostname nexusServer := loader.GetString("NEXUS_SERVER", "localhost") if err := loader.ValidateHostname("NEXUS_SERVER", nexusServer); err != nil { @@ -813,7 +754,7 @@ func loadMinionEnvConfig(loader *ConfigLoader, config *MinionConfig, validationE } // loadMinionTimeouts loads timeout-related configuration from environment variables -func loadMinionTimeouts(loader *ConfigLoader, config *MinionConfig, validationErrors *[]error) { +func loadMinionTimeouts(loader *Loader, config *MinionConfig, validationErrors *[]error) { timeoutConfigs := []struct { envVar string target *int @@ -865,7 +806,7 @@ func parseMinionFlags(config *MinionConfig) *minionFlagValues { } // applyMinionFlags applies command line flag values to the configuration -func applyMinionFlags(loader *ConfigLoader, config *MinionConfig, flags *minionFlagValues, validationErrors *[]error) { +func applyMinionFlags(loader *Loader, config *MinionConfig, flags *minionFlagValues, validationErrors *[]error) { // Apply and validate server address if err := loader.ValidateNetworkAddress("server", *flags.serverAddr); err != nil { *validationErrors = append(*validationErrors, err) diff --git a/internal/web/assets.go b/internal/web/assets.go index 08b0760..17a4662 100644 --- a/internal/web/assets.go +++ b/internal/web/assets.go @@ -9,22 +9,22 @@ import ( // Embedded webroot assets - included at build time var ( // HTML Templates - //go:embed templates/*.html + //go:embed webroot/templates/*.html templatesFS embed.FS // Static assets (CSS, JS, images) - //go:embed static/* + //go:embed webroot/static/* staticFS embed.FS ) // GetTemplates loads and parses embedded HTML templates func GetTemplates() (*template.Template, error) { - return template.ParseFS(templatesFS, "templates/*.html") + return template.ParseFS(templatesFS, "webroot/templates/*.html") } // GetStaticFS returns the embedded static file system func GetStaticFS() fs.FS { - staticSubFS, err := fs.Sub(staticFS, "static") + staticSubFS, err := fs.Sub(staticFS, "webroot/static") if err != nil { panic("failed to create static subdirectory: " + err.Error()) } diff --git a/internal/web/data.go b/internal/web/data.go index 051f2cd..d63412a 100644 --- a/internal/web/data.go +++ b/internal/web/data.go @@ -14,6 +14,7 @@ type DashboardData struct { MinionPort int `json:"minion_port"` ConsolePort int `json:"console_port"` WebPort int `json:"web_port"` + ServerHost string `json:"server_host"` Minions []MinionInfo `json:"minions"` } diff --git a/internal/web/handlers.go b/internal/web/handlers.go index 6b092e3..9401461 100644 --- a/internal/web/handlers.go +++ b/internal/web/handlers.go @@ -247,6 +247,12 @@ func (ws *WebServer) buildDashboardData() DashboardData { systemStatus = "warning" } + // Get server host from environment variable or use default + serverHost := os.Getenv("NEXUS_SERVER") + if serverHost == "" { + serverHost = "localhost" + } + return DashboardData{ Title: "Dashboard", Version: version.Component("Nexus"), @@ -256,6 +262,7 @@ func (ws *WebServer) buildDashboardData() DashboardData { MinionPort: ws.config.MinionPort, ConsolePort: ws.config.ConsolePort, WebPort: ws.config.WebPort, + ServerHost: serverHost, Minions: minions, } } diff --git a/internal/web/templates/dashboard.html b/internal/web/templates/dashboard.html deleted file mode 100644 index 5ea5cad..0000000 --- a/internal/web/templates/dashboard.html +++ /dev/null @@ -1,56 +0,0 @@ -{{define "content"}} -
-
-
-

System Status

-
{{.SystemStatus}}
-

Version: {{.Version}}

-

Uptime: {{.Uptime}}

-
- -
-

Connected Minions

-
{{.MinionCount}}
-
- {{range .Minions}} -
- {{.ID}} - {{.Status}} -
- {{end}} -
-
- -
-

Server Ports

-
-
- Minion (gRPC): {{.MinionPort}} - -
-
- Console (mTLS): {{.ConsolePort}} - -
-
- Web (HTTP): {{.WebPort}} - -
-
-
- -
-

Quick Actions

- -
-
-
-{{end}} - -{{define "scripts"}} - -{{end}} \ No newline at end of file diff --git a/internal/web/templates/install_minion.sh b/internal/web/templates/install_minion.sh deleted file mode 100644 index d30e865..0000000 --- a/internal/web/templates/install_minion.sh +++ /dev/null @@ -1,239 +0,0 @@ -#!/bin/bash - -# Script d'installation de Minion -# Usage: curl {{.BaseURL}}/install_minion.sh | sh - -set -e - -# Couleurs pour l'affichage -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -# Configuration -BASE_URL="{{.BaseURL}}" -BINARY_NAME="minion" - -# Fonction pour afficher les messages -log_info() { - echo -e "${GREEN}[INFO]${NC} $1" -} - -log_warn() { - echo -e "${YELLOW}[WARN]${NC} $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -# Fonction pour détecter l'OS -detect_os() { - case "$(uname -s)" in - Linux*) OS="linux" ;; - Darwin*) OS="darwin" ;; - CYGWIN*|MINGW*|MSYS*) OS="windows" ;; - *) OS="unknown" ;; - esac - - if [ "$OS" = "unknown" ]; then - log_error "OS non supporté: $(uname -s)" - exit 1 - fi - - log_info "OS détecté: $OS" -} - -# Fonction pour détecter l'architecture -detect_arch() { - ARCH=$(uname -m) - - case "$ARCH" in - x86_64|amd64) ARCH="amd64" ;; - aarch64|arm64) ARCH="arm64" ;; - armv7l) ARCH="arm" ;; - i386|i686) ARCH="386" ;; - *) - log_error "Architecture non supportée: $ARCH" - exit 1 - ;; - esac - - log_info "Architecture détectée: $ARCH" -} - -# Fonction pour construire le nom du binaire -build_binary_name() { - local suffix="" - - # Déterminer le suffixe selon la plateforme - case "$OS" in - linux) - if [ "$ARCH" = "amd64" ]; then - suffix="-linux" - fi - ;; - windows) - suffix=".exe" - if [ "$ARCH" = "amd64" ]; then - suffix="-windows.exe" - fi - ;; - darwin) - if [ "$ARCH" = "amd64" ]; then - suffix="-darwin" - fi - ;; - esac - - BINARY_FILE="${BINARY_NAME}${suffix}" - DOWNLOAD_URL="${BASE_URL}/download/minion/${OS}-${ARCH}${suffix}" - - log_info "Binaire cible: $BINARY_FILE" - log_info "URL de téléchargement: $DOWNLOAD_URL" -} - -# Fonction pour télécharger le binaire -download_binary() { - local temp_file="/tmp/${BINARY_FILE}" - - log_info "Téléchargement du binaire..." - - # Utiliser curl ou wget selon ce qui est disponible - if command -v curl >/dev/null 2>&1; then - if ! curl -L -o "$temp_file" "$DOWNLOAD_URL"; then - log_error "Échec du téléchargement avec curl" - exit 1 - fi - elif command -v wget >/dev/null 2>&1; then - if ! wget -O "$temp_file" "$DOWNLOAD_URL"; then - log_error "Échec du téléchargement avec wget" - exit 1 - fi - else - log_error "curl ou wget requis pour le téléchargement" - exit 1 - fi - - # Vérifier que le fichier a été téléchargé - if [ ! -f "$temp_file" ]; then - log_error "Le fichier n'a pas été téléchargé" - exit 1 - fi - - # Vérifier la taille du fichier - if [ ! -s "$temp_file" ]; then - log_error "Le fichier téléchargé est vide" - rm -f "$temp_file" - exit 1 - fi - - log_info "Téléchargement terminé avec succès" - - # Déplacer le binaire vers le répertoire de destination - local install_dir="/usr/local/bin" - local final_path="${install_dir}/${BINARY_NAME}" - - # Créer le répertoire s'il n'existe pas - if [ ! -d "$install_dir" ]; then - log_warn "Création du répertoire $install_dir" - sudo mkdir -p "$install_dir" - fi - - # Copier et rendre exécutable - log_info "Installation du binaire vers $final_path" - sudo cp "$temp_file" "$final_path" - sudo chmod +x "$final_path" - - # Nettoyer le fichier temporaire - rm -f "$temp_file" - - log_info "Installation terminée avec succès" -} - -# Fonction pour vérifier l'installation -verify_installation() { - if command -v "$BINARY_NAME" >/dev/null 2>&1; then - log_info "Vérification de l'installation..." - local version_output - if version_output=$("$BINARY_NAME" --version 2>/dev/null); then - log_info "Version installée: $version_output" - else - log_warn "Impossible d'obtenir la version, mais le binaire est installé" - fi - return 0 - else - log_error "Le binaire n'est pas accessible dans le PATH" - return 1 - fi -} - -# Fonction pour exécuter le binaire -run_minion() { - log_info "Démarrage de Minion..." - - # Vérifier si des paramètres ont été passés via des variables d'environnement - local minion_args="" - - if [ -n "$NEXUS_SERVER" ]; then - minion_args="$minion_args --server $NEXUS_SERVER" - fi - - if [ -n "$MINION_NAME" ]; then - minion_args="$minion_args --name $MINION_NAME" - fi - - if [ -n "$MINION_TAGS" ]; then - minion_args="$minion_args --tags $MINION_TAGS" - fi - - if [ -z "$minion_args" ]; then - log_info "Aucune configuration fournie. Utilisation des paramètres par défaut." - log_info "Variables d'environnement disponibles:" - log_info " NEXUS_SERVER - Adresse du serveur Nexus" - log_info " MINION_NAME - Nom du minion" - log_info " MINION_TAGS - Tags du minion" - log_info "" - log_info "Exemple:" - log_info " NEXUS_SERVER=nexus.example.com:8080 MINION_NAME=web-server-01 $0" - fi - - # Exécuter le minion - log_info "Commande exécutée: $BINARY_NAME $minion_args" - exec "$BINARY_NAME" $minion_args -} - -# Fonction principale -main() { - log_info "=== Installation de Minion ===" - - # Vérifier les prérequis - if [ "$EUID" -eq 0 ]; then - log_warn "Attention: exécution en tant que root" - fi - - # Détecter l'environnement - detect_os - detect_arch - - # Construire les informations de téléchargement - build_binary_name - - # Télécharger et installer - download_binary - - # Vérifier l'installation - if verify_installation; then - log_info "=== Installation réussie ===" - - # Exécuter le minion - run_minion - else - log_error "=== Échec de l'installation ===" - exit 1 - fi -} - -# Exécuter le script principal -main "$@" \ No newline at end of file diff --git a/internal/web/static/css/main.css b/internal/web/webroot/static/css/main.css similarity index 100% rename from internal/web/static/css/main.css rename to internal/web/webroot/static/css/main.css diff --git a/internal/web/static/css/theme.css b/internal/web/webroot/static/css/theme.css similarity index 100% rename from internal/web/static/css/theme.css rename to internal/web/webroot/static/css/theme.css diff --git a/internal/web/static/images/favicon.ico b/internal/web/webroot/static/images/favicon.ico similarity index 100% rename from internal/web/static/images/favicon.ico rename to internal/web/webroot/static/images/favicon.ico diff --git a/internal/web/static/images/logo.svg b/internal/web/webroot/static/images/logo.svg similarity index 100% rename from internal/web/static/images/logo.svg rename to internal/web/webroot/static/images/logo.svg diff --git a/internal/web/static/js/api.js b/internal/web/webroot/static/js/api.js similarity index 100% rename from internal/web/static/js/api.js rename to internal/web/webroot/static/js/api.js diff --git a/internal/web/static/js/dashboard.js b/internal/web/webroot/static/js/dashboard.js similarity index 87% rename from internal/web/static/js/dashboard.js rename to internal/web/webroot/static/js/dashboard.js index 0569459..21014e9 100644 --- a/internal/web/static/js/dashboard.js +++ b/internal/web/webroot/static/js/dashboard.js @@ -317,4 +317,50 @@ document.addEventListener('DOMContentLoaded', function () { showError('Connection lost'); stopAutoRefresh(); }); + // Copy installation command to clipboard + function copyCommand(elementId) { + // Support both old and new usage + const id = elementId || 'install-command'; + const commandElement = document.getElementById(id); + if (!commandElement) return; + + const command = commandElement.textContent; + + // Try modern clipboard API first + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(command) + .then(() => { + showSuccess('Command copied to clipboard!'); + }) + .catch(() => { + fallbackCopy(command); + }); + } else { + // Fallback for older browsers + fallbackCopy(command); + } + } + + function fallbackCopy(text) { + const textArea = document.createElement('textarea'); + textArea.value = text; + textArea.style.position = 'fixed'; + textArea.style.left = '-999999px'; + textArea.style.top = '-999999px'; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + document.execCommand('copy'); + showSuccess('Command copied to clipboard!'); + } catch (err) { + showError('Failed to copy command'); + } + + document.body.removeChild(textArea); + } + + // Make copyCommand globally accessible + window.copyCommand = copyCommand; }); \ No newline at end of file diff --git a/internal/web/templates/base.html b/internal/web/webroot/templates/base.html similarity index 100% rename from internal/web/templates/base.html rename to internal/web/webroot/templates/base.html diff --git a/internal/web/webroot/templates/dashboard.html b/internal/web/webroot/templates/dashboard.html new file mode 100644 index 0000000..1bc699b --- /dev/null +++ b/internal/web/webroot/templates/dashboard.html @@ -0,0 +1,85 @@ +{{define "content"}} +
+
+
+

System Status

+
{{.SystemStatus}}
+

Version: {{.Version}}

+

Uptime: {{.Uptime}}

+
+ +
+

Connected Minions

+
{{.MinionCount}}
+
+ {{range .Minions}} +
+ {{.ID}} + {{.Status}} +
+ {{end}} +
+
+ +
+

Server Ports

+
+
+ Minion (gRPC): {{.MinionPort}} + +
+
+ Console (mTLS): {{.ConsolePort}} + +
+
+ Web (HTTP): {{.WebPort}} + +
+
+
+ +
+

Quick Actions

+ +
+ +
+

Quick Minion Installation

+

Linux/macOS:

+
+ curl -sSL http://{{.ServerHost}}:{{.WebPort}}/install_minion.sh | sh + +
+ +

Windows PowerShell:

+
+ iwr -useb http://{{.ServerHost}}:{{.WebPort}}/download/minion/windows-amd64.exe -OutFile minion.exe; .\minion.exe + +
+ +

+ Options:
+ • Linux systemd: add | sh -s -- --systemd
+ • Windows with env vars: $env:NEXUS_SERVER="{{.ServerHost}}"; $env:NEXUS_MINION_PORT="{{.MinionPort}}"; .\minion.exe +

+
+
+
+{{end}} + +{{define "scripts"}} + +{{end}} \ No newline at end of file diff --git a/internal/web/webroot/templates/install_minion.sh b/internal/web/webroot/templates/install_minion.sh new file mode 100644 index 0000000..ea06e13 --- /dev/null +++ b/internal/web/webroot/templates/install_minion.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# Minion Installation Script +# Generated on: {{.Date}} +# Server: {{.ServerURL}} + +set -e + +# Configuration +NEXUS_SERVER="{{.ServerURL}}" +MINION_PORT="{{.MinionPort}}" +MINION_ID="${MINION_ID:-{{.MinionID}}}" + +echo "Installing Minion client..." +echo "Nexus Server: $NEXUS_SERVER" +echo "Minion Port: $MINION_PORT" +echo "Minion ID: $MINION_ID" + +# Download and install minion binary +echo "Downloading minion binary..." +curl -o minion "http://$NEXUS_SERVER:{{.WebPort}}/binaries/minion/$(uname -s)-$(uname -m)" || { + echo "Failed to download minion binary" + exit 1 +} + +chmod +x minion + +# Create systemd service or run directly +if [ "$1" = "--systemd" ]; then + cat > /etc/systemd/system/minion.service < - - - - - - - - - - - - - - - - - - - - - - - - - - MINEXUS - Network Control - \ No newline at end of file diff --git a/webroot/static/js/api.js b/webroot/static/js/api.js deleted file mode 100644 index ab5d5c5..0000000 --- a/webroot/static/js/api.js +++ /dev/null @@ -1,183 +0,0 @@ -class MinexusAPI { - constructor(baseURL = '') { - this.baseURL = baseURL; - } - - async getStatus() { - try { - const response = await fetch(`${this.baseURL}/api/status`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return await response.json(); - } catch (error) { - console.error('Failed to fetch status:', error); - throw error; - } - } - - async getMinions() { - try { - const response = await fetch(`${this.baseURL}/api/minions`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return await response.json(); - } catch (error) { - console.error('Failed to fetch minions:', error); - throw error; - } - } - - async getHealth() { - try { - const response = await fetch(`${this.baseURL}/api/health`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return await response.json(); - } catch (error) { - console.error('Failed to fetch health:', error); - throw error; - } - } -} - -// Create global API instance -const api = new MinexusAPI(); - -// Utility functions for formatting -function formatUptime(uptimeSeconds) { - const days = Math.floor(uptimeSeconds / 86400); - const hours = Math.floor((uptimeSeconds % 86400) / 3600); - const minutes = Math.floor((uptimeSeconds % 3600) / 60); - const seconds = uptimeSeconds % 60; - - let result = ''; - if (days > 0) result += `${days}d `; - if (hours > 0) result += `${hours}h `; - if (minutes > 0 || result === '') result += `${minutes}m `; - if (seconds > 0 && days === 0) result += `${seconds}s`; - - return result.trim(); -} - -function formatTimestamp(timestamp) { - return new Date(timestamp).toLocaleString(); -} - -function getStatusClass(status) { - switch (status?.toLowerCase()) { - case 'healthy': - case 'running': - case 'active': - return 'success'; - case 'warning': - return 'warning'; - case 'error': - case 'unhealthy': - case 'stopped': - case 'inactive': - return 'error'; - default: - return 'secondary'; - } -} - -// Theme management -function getTheme() { - return localStorage.getItem('theme') || 'light'; -} - -function setTheme(theme) { - localStorage.setItem('theme', theme); - document.documentElement.setAttribute('data-theme', theme); -} - -function toggleTheme() { - const currentTheme = getTheme(); - const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; - setTheme(newTheme); -} - -// Initialize theme on load -document.addEventListener('DOMContentLoaded', function () { - setTheme(getTheme()); -}); - -// Error handling utilities -function showError(message, container = document.body) { - const errorDiv = document.createElement('div'); - errorDiv.className = 'error-message'; - errorDiv.style.cssText = ` - position: fixed; - top: 1rem; - right: 1rem; - background: var(--error); - color: white; - padding: 1rem; - border-radius: 6px; - box-shadow: var(--shadow); - z-index: 1000; - max-width: 300px; - `; - errorDiv.textContent = message; - - container.appendChild(errorDiv); - - // Auto-remove after 5 seconds - setTimeout(() => { - if (errorDiv.parentNode) { - errorDiv.parentNode.removeChild(errorDiv); - } - }, 5000); - - // Click to dismiss - errorDiv.addEventListener('click', () => { - if (errorDiv.parentNode) { - errorDiv.parentNode.removeChild(errorDiv); - } - }); -} - -function showSuccess(message, container = document.body) { - const successDiv = document.createElement('div'); - successDiv.className = 'success-message'; - successDiv.style.cssText = ` - position: fixed; - top: 1rem; - right: 1rem; - background: var(--success); - color: white; - padding: 1rem; - border-radius: 6px; - box-shadow: var(--shadow); - z-index: 1000; - max-width: 300px; - `; - successDiv.textContent = message; - - container.appendChild(successDiv); - - // Auto-remove after 3 seconds - setTimeout(() => { - if (successDiv.parentNode) { - successDiv.parentNode.removeChild(successDiv); - } - }, 3000); -} - -// Loading indicator utilities -function showLoading(element) { - if (element) { - element.classList.add('loading'); - element.innerHTML = ''; - } -} - -function hideLoading(element, originalContent) { - if (element) { - element.classList.remove('loading'); - element.innerHTML = originalContent || ''; - } -} \ No newline at end of file diff --git a/webroot/static/js/dashboard.js b/webroot/static/js/dashboard.js deleted file mode 100644 index 0569459..0000000 --- a/webroot/static/js/dashboard.js +++ /dev/null @@ -1,320 +0,0 @@ -document.addEventListener('DOMContentLoaded', function () { - let refreshInterval; - let isAutoRefreshEnabled = true; - - // Initialize dashboard - initializeDashboard(); - setupEventListeners(); - startAutoRefresh(); - - function initializeDashboard() { - // Add theme toggle button if not exists - addThemeToggle(); - - // Add auto-refresh indicator - addAutoRefreshIndicator(); - - // Initial data load - refreshDashboard(); - } - - function setupEventListeners() { - // Theme toggle - const themeToggle = document.getElementById('theme-toggle'); - if (themeToggle) { - themeToggle.addEventListener('click', toggleTheme); - } - - // Auto-refresh toggle - const autoRefreshToggle = document.getElementById('auto-refresh-toggle'); - if (autoRefreshToggle) { - autoRefreshToggle.addEventListener('click', toggleAutoRefresh); - } - - // Manual refresh button - const refreshButton = document.getElementById('refresh-button'); - if (refreshButton) { - refreshButton.addEventListener('click', () => { - refreshDashboard(); - showSuccess('Dashboard refreshed'); - }); - } - - // Handle visibility change (pause refresh when tab not visible) - document.addEventListener('visibilitychange', function () { - if (document.hidden) { - stopAutoRefresh(); - } else if (isAutoRefreshEnabled) { - startAutoRefresh(); - } - }); - } - - function addThemeToggle() { - if (document.getElementById('theme-toggle')) return; - - const themeToggle = document.createElement('button'); - themeToggle.id = 'theme-toggle'; - themeToggle.className = 'theme-toggle'; - themeToggle.innerHTML = '🌙'; - themeToggle.title = 'Toggle dark mode'; - document.body.appendChild(themeToggle); - - // Update icon based on current theme - updateThemeToggleIcon(); - } - - function updateThemeToggleIcon() { - const themeToggle = document.getElementById('theme-toggle'); - if (themeToggle) { - const isDark = getTheme() === 'dark'; - themeToggle.innerHTML = isDark ? '☀️' : '🌙'; - themeToggle.title = isDark ? 'Switch to light mode' : 'Switch to dark mode'; - } - } - - function addAutoRefreshIndicator() { - if (document.getElementById('auto-refresh')) return; - - const indicator = document.createElement('div'); - indicator.id = 'auto-refresh'; - indicator.className = 'auto-refresh'; - indicator.innerHTML = ` - - 🔄 Auto-refresh: ON - - - `; - document.body.appendChild(indicator); - } - - function startAutoRefresh() { - stopAutoRefresh(); // Clear any existing interval - - let countdown = 30; // 30 seconds - updateRefreshCountdown(countdown); - - refreshInterval = setInterval(() => { - countdown--; - updateRefreshCountdown(countdown); - - if (countdown <= 0) { - refreshDashboard(); - countdown = 30; // Reset countdown - } - }, 1000); - - updateAutoRefreshStatus(true); - } - - function stopAutoRefresh() { - if (refreshInterval) { - clearInterval(refreshInterval); - refreshInterval = null; - } - updateRefreshCountdown(0); - } - - function toggleAutoRefresh() { - isAutoRefreshEnabled = !isAutoRefreshEnabled; - - if (isAutoRefreshEnabled) { - startAutoRefresh(); - } else { - stopAutoRefresh(); - } - - updateAutoRefreshStatus(isAutoRefreshEnabled); - } - - function updateAutoRefreshStatus(enabled) { - const status = document.getElementById('refresh-status'); - const indicator = document.getElementById('auto-refresh'); - - if (status) { - status.textContent = enabled ? 'ON' : 'OFF'; - } - - if (indicator) { - indicator.className = enabled ? 'auto-refresh active' : 'auto-refresh'; - } - } - - function updateRefreshCountdown(seconds) { - const countdown = document.getElementById('refresh-countdown'); - if (countdown) { - if (seconds > 0) { - countdown.textContent = `(${seconds}s)`; - } else { - countdown.textContent = ''; - } - } - } - - async function refreshDashboard() { - try { - // Show loading state - showLoadingState(); - - // Fetch all data concurrently - const [statusData, minionsData] = await Promise.all([ - api.getStatus(), - api.getMinions() - ]); - - // Update dashboard sections - updateSystemStatus(statusData); - updateMinionStatus(minionsData); - updateServerPorts(statusData); - - // Hide loading state - hideLoadingState(); - - } catch (error) { - console.error('Failed to refresh dashboard:', error); - showError('Failed to refresh dashboard data'); - hideLoadingState(); - } - } - - function showLoadingState() { - const statusCards = document.querySelectorAll('.status-card'); - statusCards.forEach(card => { - const indicator = card.querySelector('.status-indicator'); - if (indicator) { - indicator.classList.add('loading'); - } - }); - } - - function hideLoadingState() { - const statusCards = document.querySelectorAll('.status-card'); - statusCards.forEach(card => { - const indicator = card.querySelector('.status-indicator'); - if (indicator) { - indicator.classList.remove('loading'); - } - }); - } - - function updateSystemStatus(data) { - const versionElement = document.querySelector('.status-card p:nth-of-type(1)'); - const uptimeElement = document.querySelector('.status-card p:nth-of-type(2)'); - const statusIndicator = document.querySelector('.status-indicator'); - - if (versionElement) { - versionElement.textContent = `Version: ${data.version || 'Unknown'}`; - } - - if (uptimeElement) { - uptimeElement.textContent = `Uptime: ${data.uptime || 'Unknown'}`; - } - - if (statusIndicator) { - const isHealthy = data.servers && - data.servers.minion?.status === 'running' && - data.servers.console?.status === 'running'; - const status = isHealthy ? 'healthy' : 'warning'; - - statusIndicator.className = `status-indicator ${status}`; - statusIndicator.textContent = status.toUpperCase(); - } - } - - function updateMinionStatus(data) { - const countElement = document.querySelector('.minion-count'); - const listElement = document.querySelector('.minion-list'); - - if (countElement) { - countElement.textContent = data.count || 0; - } - - if (listElement) { - listElement.innerHTML = ''; - - if (data.minions && data.minions.length > 0) { - data.minions.forEach(minion => { - const minionItem = document.createElement('div'); - minionItem.className = 'minion-item'; - minionItem.innerHTML = ` - ${minion.id} - ${minion.status} - `; - listElement.appendChild(minionItem); - }); - } else { - const noMinions = document.createElement('div'); - noMinions.className = 'minion-item'; - noMinions.innerHTML = 'No minions connected'; - listElement.appendChild(noMinions); - } - } - } - - function updateServerPorts(data) { - const portList = document.querySelector('.port-list'); - if (!portList || !data.servers) return; - - const ports = [ - { name: 'Minion (gRPC)', port: data.servers.minion?.port, status: data.servers.minion?.status }, - { name: 'Console (mTLS)', port: data.servers.console?.port, status: data.servers.console?.status }, - { name: 'Web (HTTP)', port: data.servers.web?.port, status: data.servers.web?.status } - ]; - - portList.innerHTML = ''; - - ports.forEach(portInfo => { - const portItem = document.createElement('div'); - portItem.className = 'port-item'; - portItem.innerHTML = ` - ${portInfo.name}: ${portInfo.port || 'Unknown'} - - `; - portList.appendChild(portItem); - }); - } - - // Override theme toggle to update icon - const originalToggleTheme = window.toggleTheme; - window.toggleTheme = function () { - if (originalToggleTheme) { - originalToggleTheme(); - } - updateThemeToggleIcon(); - }; - - // Add keyboard shortcuts - document.addEventListener('keydown', function (e) { - // Ctrl/Cmd + R for refresh - if ((e.ctrlKey || e.metaKey) && e.key === 'r') { - e.preventDefault(); - refreshDashboard(); - showSuccess('Dashboard refreshed'); - } - - // Ctrl/Cmd + D for dark mode toggle - if ((e.ctrlKey || e.metaKey) && e.key === 'd') { - e.preventDefault(); - toggleTheme(); - } - - // Space to toggle auto-refresh - if (e.code === 'Space' && e.target === document.body) { - e.preventDefault(); - toggleAutoRefresh(); - } - }); - - // Handle connection errors gracefully - window.addEventListener('online', function () { - showSuccess('Connection restored'); - if (isAutoRefreshEnabled) { - startAutoRefresh(); - } - }); - - window.addEventListener('offline', function () { - showError('Connection lost'); - stopAutoRefresh(); - }); -}); \ No newline at end of file diff --git a/webroot/templates/base.html b/webroot/templates/base.html deleted file mode 100644 index 8690a7b..0000000 --- a/webroot/templates/base.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - {{.Title}} - Minexus - - - - - - -
- -
-
- {{template "content" .}} -
-
-

Minexus v{{.Version}} | Uptime: {{.Uptime}}

-
- - {{template "scripts" .}} - - - \ No newline at end of file diff --git a/webroot/templates/dashboard.html b/webroot/templates/dashboard.html deleted file mode 100644 index 5ea5cad..0000000 --- a/webroot/templates/dashboard.html +++ /dev/null @@ -1,56 +0,0 @@ -{{define "content"}} -
-
-
-

System Status

-
{{.SystemStatus}}
-

Version: {{.Version}}

-

Uptime: {{.Uptime}}

-
- -
-

Connected Minions

-
{{.MinionCount}}
-
- {{range .Minions}} -
- {{.ID}} - {{.Status}} -
- {{end}} -
-
- -
-

Server Ports

-
-
- Minion (gRPC): {{.MinionPort}} - -
-
- Console (mTLS): {{.ConsolePort}} - -
-
- Web (HTTP): {{.WebPort}} - -
-
-
- -
-

Quick Actions

- -
-
-
-{{end}} - -{{define "scripts"}} - -{{end}} \ No newline at end of file