Initial SDP skeleton
Sandbox Deployment Platform — Go control plane + agents, NextJS dashboard, nginx reverse proxy. Cross-compile via Docker; deploy via sshpass to 172.18.136.92 (micro) and 172.18.139.186 (gateway). - control-plane: HTTP API, WS hub, SQLite (modernc.org/sqlite) for progress, .log files for log persistence - agent-micro / agent-gateway: alpine:3.20 + bind-mounted repo, binary exec'd in container, no Dockerfile build step - dashboard: NextJS static export + shadcn/ui components, single WebSocket hook - docker-compose.yml: three services on alpine:latest with docker socket bind for agents - scripts/: build.sh (golang:1.23-alpine cross-compile), deploy.sh, patch-nginx.sh (idempotent nginx splice), ssh wrappers Runtime model: pass-through Bitbucket creds per deploy, never logged or persisted on the agent. Control plane never touches git or docker directly — agents do all the work locally.
This commit is contained in:
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build all three Go binaries for Linux/amd64 and the dashboard.
|
||||
# Output goes to ./bin/ and ./dashboard/out/.
|
||||
#
|
||||
# Uses a golang:1.23-alpine container so we get a reproducible toolchain
|
||||
# without needing Go installed locally. Cross-compile via GOOS/GOARCH +
|
||||
# CGO_ENABLED=0 — produces a static binary that runs in the alpine
|
||||
# containers defined in docker-compose.yml.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
REPO_ROOT="$(pwd)"
|
||||
|
||||
OUT="$REPO_ROOT/bin"
|
||||
mkdir -p "$OUT"
|
||||
|
||||
GO_IMAGE="${GO_IMAGE:-golang:1.23-alpine}"
|
||||
|
||||
# ponytail: bind-mount a persistent gocache so module downloads + build cache
|
||||
# survive across runs. Otherwise every build re-downloads the world from
|
||||
# the GOPROXY — slow on a flaky office link, and uses up the proxy quota.
|
||||
GOCACHE_VOL="sdp-gocache"
|
||||
docker volume create "$GOCACHE_VOL" >/dev/null 2>&1 || true
|
||||
|
||||
echo "==> building control-plane, agent-micro, agent-gateway (linux/amd64)"
|
||||
docker run --rm \
|
||||
-v "$REPO_ROOT":/src \
|
||||
-v "$OUT":/out \
|
||||
-v "$GOCACHE_VOL":/gocache \
|
||||
-w /src \
|
||||
-e CGO_ENABLED=0 \
|
||||
-e GOOS=linux \
|
||||
-e GOARCH=amd64 \
|
||||
-e GOCACHE=/gocache \
|
||||
-e GOFLAGS="-mod=mod" \
|
||||
"$GO_IMAGE" \
|
||||
sh -c '
|
||||
set -e
|
||||
# -trimpath: strip absolute paths from the binary (reproducible builds).
|
||||
# -ldflags="-s -w": drop symbol table + DWARF, smaller binary.
|
||||
go build -trimpath -ldflags="-s -w" -o /out/control-plane ./control-plane/cmd/control-plane
|
||||
go build -trimpath -ldflags="-s -w" -o /out/agent-micro ./agent-micro/cmd/agent-micro
|
||||
go build -trimpath -ldflags="-s -w" -o /out/agent-gateway ./agent-gateway/cmd/agent-gateway
|
||||
'
|
||||
|
||||
echo
|
||||
echo "==> binaries:"
|
||||
ls -lh "$OUT"
|
||||
chmod +x "$OUT"/*
|
||||
|
||||
# Verify the binaries are actually linux/amd64. ponytail: catches a mistake
|
||||
# where someone removes the GOOS/GOARCH env and ships a darwin binary to
|
||||
# the alpine container.
|
||||
echo
|
||||
echo "==> sanity check (file type):"
|
||||
file "$OUT"/*
|
||||
|
||||
# ---- dashboard ----
|
||||
if [[ -d "$REPO_ROOT/dashboard" ]]; then
|
||||
echo
|
||||
echo "==> building dashboard"
|
||||
if [[ ! -d "$REPO_ROOT/dashboard/node_modules" ]]; then
|
||||
(cd "$REPO_ROOT/dashboard" && npm install)
|
||||
fi
|
||||
(cd "$REPO_ROOT/dashboard" && npm run build)
|
||||
echo "dashboard built at $REPO_ROOT/dashboard/out"
|
||||
fi
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
# Push the built binaries and dashboard to both SDP VMs.
|
||||
#
|
||||
# 92 (micro): ~/SDP/agent-micro
|
||||
# 186 (gateway): ~/SDP/control-plane, ~/SDP/agent-gateway, ~/SDP/dashboard
|
||||
#
|
||||
# On 186 we also splice the SDP location into nginx's existing default site
|
||||
# and reload. Run scripts/build.sh first.
|
||||
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
REPO_ROOT="$(pwd)"
|
||||
|
||||
# ponytail: paths can be overridden by env so the same script works from CI.
|
||||
HOST_92="${SDP_92_HOST:-administrator@172.18.136.92}"
|
||||
PASS_92="${SDP_92_PASS:-password}"
|
||||
HOST_186="${SDP_186_HOST:-administrator@172.18.139.186}"
|
||||
PASS_186="${SDP_186_PASS:-Bre@kthrough2312}"
|
||||
|
||||
if ! command -v sshpass >/dev/null 2>&1; then
|
||||
echo "sshpass not found. Install with: brew install sshpass" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SSH_92="sshpass -p $PASS_92 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
|
||||
SCP_92="sshpass -p $PASS_92 scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
|
||||
SSH_186="sshpass -p $PASS_186 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
|
||||
SCP_186="sshpass -p $PASS_186 scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
|
||||
|
||||
# ponytail: Wipe-and-replace. The deploys are stateful on the VM only via
|
||||
# SQLite + .log files in ~/SDP/data — we keep that. Binaries and the
|
||||
# dashboard are replaced cleanly.
|
||||
REMOTE_RESET='rm -rf ~/SDP/bin ~/SDP/dashboard && mkdir -p ~/SDP/bin ~/SDP/dashboard'
|
||||
|
||||
echo "==> 92: $HOST_92"
|
||||
$SSH_92 "$HOST_92" "$REMOTE_RESET"
|
||||
$SCP_92 "$REPO_ROOT/bin/agent-micro" "$HOST_92:~/SDP/bin/agent-micro"
|
||||
$SSH_92 "$HOST_92" "chmod +x ~/SDP/bin/agent-micro"
|
||||
echo " agent-micro copied"
|
||||
|
||||
echo
|
||||
echo "==> 186: $HOST_186"
|
||||
$SSH_186 "$HOST_186" "$REMOTE_RESET"
|
||||
$SCP_186 "$REPO_ROOT/bin/control-plane" "$HOST_186:~/SDP/bin/control-plane"
|
||||
$SCP_186 "$REPO_ROOT/bin/agent-gateway" "$HOST_186:~/SDP/bin/agent-gateway"
|
||||
$SCP_186 -r "$REPO_ROOT/dashboard/out/." "$HOST_186:~/SDP/dashboard/"
|
||||
$SSH_186 "$HOST_186" "chmod +x ~/SDP/bin/control-plane ~/SDP/bin/agent-gateway"
|
||||
echo " control-plane, agent-gateway, dashboard copied"
|
||||
|
||||
# Patch nginx on 186
|
||||
echo
|
||||
echo "==> 186: patching nginx"
|
||||
"$REPO_ROOT/scripts/patch-nginx.sh"
|
||||
|
||||
echo
|
||||
echo "done."
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
# Splice the SDP dashboard location into the existing nginx default site on
|
||||
# 172.18.139.186. Idempotent: re-running won't duplicate the block.
|
||||
#
|
||||
# We don't replace the file — we insert before the closing `}` of the
|
||||
# existing `server { ... }` block. The block is guarded by a sentinel
|
||||
# comment so subsequent runs are no-ops.
|
||||
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
HOST_186="${SDP_186_HOST:-administrator@172.18.139.186}"
|
||||
PASS_186="${SDP_186_PASS:-Bre@kthrough2312}"
|
||||
|
||||
if ! command -v sshpass >/dev/null 2>&1; then
|
||||
echo "sshpass not found. Install with: brew install sshpass" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SSH="sshpass -p $PASS_186 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
|
||||
|
||||
NGINX_SITE=/etc/nginx/sites-available/default
|
||||
SDP_MARKER='# >>> sdp >>>'
|
||||
|
||||
$SSH "$HOST_186" bash -s <<REMOTE
|
||||
set -e
|
||||
NGINX_SITE=$NGINX_SITE
|
||||
SDP_MARKER='$SDP_MARKER'
|
||||
|
||||
if grep -qF "\$SDP_MARKER" "\$NGINX_SITE"; then
|
||||
echo "sdp block already present, skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cp "\$NGINX_SITE" "\$NGINX_SITE.bak.\$(date +%s)"
|
||||
|
||||
python3 - <<'PY'
|
||||
import re
|
||||
path = "/etc/nginx/sites-available/default"
|
||||
src = open(path).read()
|
||||
block = """
|
||||
\t# >>> sdp >>>
|
||||
\t# Sandbox Deployment Platform dashboard
|
||||
\tlocation /api/ {
|
||||
\t\tproxy_pass http://127.0.0.1:8080;
|
||||
\t\tproxy_set_header Host $host;
|
||||
\t\tproxy_set_header X-Real-IP $remote_addr;
|
||||
\t\tproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
\t}
|
||||
|
||||
\tlocation /ws/ {
|
||||
\t\tproxy_pass http://127.0.0.1:8080;
|
||||
\t\tproxy_http_version 1.1;
|
||||
\t\tproxy_set_header Upgrade $http_upgrade;
|
||||
\t\tproxy_set_header Connection "upgrade";
|
||||
\t\tproxy_read_timeout 3600s;
|
||||
\t\tproxy_send_timeout 3600s;
|
||||
\t}
|
||||
|
||||
\tlocation / {
|
||||
\t\troot /home/administrator/SDP/dashboard;
|
||||
\t\tindex index.html;
|
||||
\t\ttry_files \$uri \$uri/ \$uri.html /index.html;
|
||||
\t}
|
||||
\t# <<< sdp <<<
|
||||
"""
|
||||
|
||||
def find_server_end(s):
|
||||
i = s.find("server")
|
||||
if i < 0: return -1
|
||||
j = s.find("{", i)
|
||||
if j < 0: return -1
|
||||
depth = 1
|
||||
k = j + 1
|
||||
in_str = None
|
||||
while k < len(s):
|
||||
c = s[k]
|
||||
if in_str:
|
||||
if c == in_str and s[k-1] != "\\":
|
||||
in_str = None
|
||||
else:
|
||||
if c in ('"', "'"):
|
||||
in_str = c
|
||||
elif c == "{":
|
||||
depth += 1
|
||||
elif c == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return k
|
||||
k += 1
|
||||
return -1
|
||||
|
||||
end = find_server_end(src)
|
||||
if end < 0:
|
||||
raise SystemExit("could not find server block end")
|
||||
|
||||
new = src[:end] + block + src[end:]
|
||||
open(path, "w").write(new)
|
||||
PY
|
||||
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
echo "nginx patched and reloaded"
|
||||
REMOTE
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# SSH into the gateway VM (172.18.139.186). Wraps ssh with the known password.
|
||||
# Usage: scripts/ssh-186.sh [extra ssh args...]
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
HOST="${SDP_186_HOST:-administrator@172.18.139.186}"
|
||||
PASS="${SDP_186_PASS:-Bre@kthrough2312}"
|
||||
|
||||
if ! command -v sshpass >/dev/null 2>&1; then
|
||||
echo "sshpass not found. Install with: brew install sshpass" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec sshpass -p "$PASS" ssh \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR \
|
||||
"$HOST" "$@"
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# SSH into the micro VM (172.18.136.92). Wraps ssh with the known password.
|
||||
# Usage: scripts/ssh-92.sh [extra ssh args...]
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
HOST="${SDP_92_HOST:-administrator@172.18.136.92}"
|
||||
PASS="${SDP_92_PASS:-password}"
|
||||
|
||||
if ! command -v sshpass >/dev/null 2>&1; then
|
||||
echo "sshpass not found. Install with: brew install sshpass" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec sshpass -p "$PASS" ssh \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR \
|
||||
"$HOST" "$@"
|
||||
Reference in New Issue
Block a user