Slice 2: port 3452, nginx sandbox mount, AGENTS.md, docs, deploy script cleanup
- control-plane default listen addr is now :3452 (was :8080). An unusual port to avoid collisions on the VM. - agent-micro and agent-gateway default SDP_CP_URL points at ws://localhost:3452/ws/agent. docker-compose.yml updates the control plane command, host port mapping, and agent -cp URLs. - nginx/nginx.conf (the legacy root-mount reference) uses 127.0.0.1:3452 for the upstream. nginx/sandbox.conf is the new deployment config: four location blocks for the /sandbox/credit-card mount — _next/static serves cached chunks, /api/ and /ws/ proxy to 127.0.0.1:3452, /sandbox/credit-card serves the static dashboard with try_files for SPA routing. - scripts/patch-nginx.sh: deleted. The user configures nginx on 186 by hand. scripts/deploy.sh no longer calls it. - AGENTS.md: new file. Documents the build/lint/test commands (with the golang:1.24-alpine container — local Go can't fetch the toolchain), the wire protocol, the Slice-2 conventions (sdp-<repo> container naming, snapshot persistence, PreGitReset/AfterStart hooks), the repo-path gotcha, and the build-artifacts-in-git rationale. - dashboard/out: now tracked in git, alongside bin/. The dashboard static export is scp'd to 186 on deploy; the VMs have no internet so they can't regenerate it. .gitignore comment explains this and warns against re-ignoring. - README.md / REQUIREMENTS.md: status updated to 'Slice 2 done', per-feature checklist marked. Erangel repo path corrected to /var/www/html/erangel-ocean (was wrongly ~/SDP in earlier docs).
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
# AGENTS.md — Sandbox Deployment Platform
|
||||
|
||||
## Build, lint, test
|
||||
|
||||
The build script is the only way to compile — local Go can't fetch the
|
||||
1.24 toolchain. Run:
|
||||
|
||||
```
|
||||
./scripts/build.sh # cross-compiles 3 Go binaries + builds the Next.js dashboard
|
||||
./scripts/deploy.sh # SSHes the artifacts to 92 and 186; needs sshpass
|
||||
```
|
||||
|
||||
The script uses a `golang:1.24-alpine` container with a persistent
|
||||
`sdp-gocache` named volume. `GO_IMAGE=...` overrides the image. Outputs:
|
||||
`bin/{control-plane,agent-micro,agent-gateway}` (Linux/amd64, static) and
|
||||
`dashboard/out/`.
|
||||
|
||||
Per-module Go work uses the same container:
|
||||
|
||||
```
|
||||
docker run --rm -v "$PWD:/src" -w /src/<module> golang:1.24-alpine sh -c \
|
||||
"apk add --no-cache git >/dev/null && git config --global --add safe.directory /src && go vet ./..."
|
||||
```
|
||||
|
||||
For a single test:
|
||||
|
||||
```
|
||||
docker run --rm -v "$PWD:/src" -w /src/control-plane golang:1.24-alpine sh -c \
|
||||
"apk add --no-cache git >/dev/null && git config --global --add safe.directory /src && go test ./internal/store/..."
|
||||
```
|
||||
|
||||
There is one test file today: `control-plane/internal/store/store_test.go`
|
||||
(round-trips all Slice-2 CRUD).
|
||||
|
||||
The dashboard has no separate typecheck or lint script — `npm run build`
|
||||
runs both. `cd dashboard && npm run build` locally is fine; node_modules
|
||||
is gitignored.
|
||||
|
||||
## Layout
|
||||
|
||||
Five Go modules in a workspace (`go.work`):
|
||||
|
||||
- `protocol/` — wire types shared by CP and agents. Keep small.
|
||||
- `agentlib/` — `gitutil` (askpass-via-stdin credential helper;
|
||||
`git ls-remote`, `fetch`, `checkout`, `pull`, `for-each-ref`,
|
||||
`reset --hard`) and `deployer` (per-deployment state machine; `NewGo`
|
||||
for microservices, `NewPHP` for erangel).
|
||||
- `control-plane/` — HTTP API + WS hub + SQLite. Routes split across
|
||||
`internal/api/{login,sandboxes,templates,environments,routes,deployments,repos}.go`.
|
||||
`internal/ws/hub.go` exposes `CallAgent` for sync RPCs.
|
||||
- `agent-micro/` — runs on 172.18.136.92.
|
||||
- `agent-gateway/` — runs on 172.18.139.186; owns erangel at
|
||||
`/var/www/html/erangel-ocean` and the `<service>_url` patching.
|
||||
|
||||
Dashboard is a separate `next build` static export under
|
||||
`dashboard/src/app/`. Static export means dynamic routes need
|
||||
`generateStaticParams` (see the `sandboxes/[id]` page for the pattern).
|
||||
|
||||
## Wire protocol
|
||||
|
||||
The agent → control-plane channel is one `protocol.Event` per WS text
|
||||
message. The control-plane → agent channel is an ad-hoc envelope
|
||||
`{op, id, data}`. `op` values: `deploy`, `stop`, `list_repos`,
|
||||
`list_branches`, `list_routes`, `probe`, `push_routes`. RPC replies have
|
||||
`{op:"reply", id, ok, error?}` and a `data` field. The two shapes are
|
||||
disambiguated by `kind` (event) vs `op` (rpc reply). New ops go in
|
||||
`agentlib/.../main.go`'s switch and the control-plane's `repos.go` /
|
||||
`sandboxes.go` / `routes.go` handlers — there is no central registry.
|
||||
|
||||
## Conventions
|
||||
|
||||
- `ponytail:` comments mark intentional shortcuts and "TODO: real
|
||||
impl"-style carve-outs. They survive into main. Don't remove without
|
||||
fixing the underlying limitation.
|
||||
- Slice-2 stable container name: `sdp-<repo>` (no deployment id). The
|
||||
next deploy force-removes the existing one. One live container per
|
||||
repo at a time.
|
||||
- Gateway agent persists the per-branch OCP-default snapshot to
|
||||
`<repoPath>/.sdp/ocp-defaults.json`. Re-captured on every deploy so
|
||||
branch switches don't break "Restore OCP" buttons.
|
||||
- `NewPHP` runs `git reset --hard` before fetch (via
|
||||
`Spec.PreGitReset`), and the agent passes an `AfterStart` closure
|
||||
that re-applies active route overrides after the container is up.
|
||||
This is what survives `git reset --hard` + checkout.
|
||||
- `protocol.Event.ContainerID` is set on the deployer side; the
|
||||
deployer writes it back via `Store.SetContainerID`. (Currently the
|
||||
field on the event is unused; container id is recorded in SQLite.)
|
||||
- Cookie auth: `sdp_session` HttpOnly cookie; the `withAuth` middleware
|
||||
skips `/api/login`. WebSocket endpoints are NOT auth-gated by the
|
||||
middleware — they rely on the agent being on a private network.
|
||||
- Crendentials travel with each deploy/probe/push_routes frame from
|
||||
control plane to agent. Never logged. Never persisted on the agent.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Host Go (`/usr/bin/go`) is older than the `go 1.24` modules require
|
||||
and the toolchain download is blocked. Use the `golang:1.24-alpine`
|
||||
container. Do not edit code expecting `go build` to work locally.
|
||||
- The micro agent and gateway agent `main.go` files duplicate most
|
||||
logic (dial / writer / readLoop / runDeploy). The shared code is in
|
||||
`agentlib/`. When adding a new op, both files need a switch case.
|
||||
- `moby/moby/client` v0.5.0 uses `netip.Addr` for `PortBinding.HostIP`,
|
||||
not a string.
|
||||
- `sdp-<repo>` containers must be in a state where `docker rm -f` works
|
||||
(the `Slice-2` "one live per repo" rule). Don't manually `docker run`
|
||||
a second container with the same name.
|
||||
- The erangel repo path is `/var/www/html/erangel-ocean` on 186, NOT
|
||||
`~/SDP` (README's earlier value is wrong; the spec was fixed in
|
||||
Slice 2). `APACHE_DOCUMENT_ROOT` is set to the same path so the
|
||||
gateway is served at `/erangel/`.
|
||||
- `agent-gateway/.../main.go` re-imports the `routesState` type and
|
||||
uses `rs` as both a value and a parameter name in some helpers.
|
||||
Compiles fine; just be aware when grepping.
|
||||
- Static-export dynamic routes: `generateStaticParams` must return at
|
||||
least one placeholder; the actual id is read at runtime in the
|
||||
client component. See `dashboard/src/app/dashboard/sandboxes/[id]/`.
|
||||
|
||||
## Verifying changes locally
|
||||
|
||||
```
|
||||
# Typecheck + build everything
|
||||
./scripts/build.sh
|
||||
|
||||
# Run the only Go test
|
||||
docker run --rm -v "$PWD:/src" -w /src/control-plane golang:1.24-alpine sh -c \
|
||||
"apk add --no-cache git >/dev/null && git config --global --add safe.directory /src && go test ./..."
|
||||
|
||||
# Smoke the control plane
|
||||
./bin/control-plane -addr :3452 -data /tmp/sdp-data &
|
||||
curl -i -X POST http://127.0.0.1:3452/api/login -d '{"username":"x","password":"y"}'
|
||||
# Expects 401 ("login failed — git ls-remote rejected") when no gateway agent is connected.
|
||||
```
|
||||
|
||||
## Out of scope
|
||||
|
||||
RBAC, suspend/resume, sandbox cloning beyond "clone template into
|
||||
sandbox", per-sandbox Docker networks, per-sandbox resource limits,
|
||||
health monitoring, the 172.18.136.93 infra agent, notifications.
|
||||
These are listed as `later` in REQUIREMENTS.md.
|
||||
|
||||
## Do not
|
||||
|
||||
- Do not commit or push unless the user explicitly says "commit" or
|
||||
"push".
|
||||
- Do not change the gateway repo path back to `~/SDP` (old docs say
|
||||
so; reality is `/var/www/html/erangel-ocean`).
|
||||
- Do not rebuild the dashboard via `next start` for production; the
|
||||
output is served by nginx on 186. Configure nginx by hand; the
|
||||
reference config is in `nginx/nginx.conf` and uses
|
||||
`root /home/administrator/SDP/dashboard;` (i.e. the path
|
||||
`deploy.sh` scp's the static export to).
|
||||
- Do not log or persist Bitbucket creds anywhere.
|
||||
@@ -5,27 +5,42 @@ branch into an isolated sandbox, with the API Gateway routing selected
|
||||
services to the sandbox and the rest to OCP. See
|
||||
[REQUIREMENTS.md](REQUIREMENTS.md) for the full spec.
|
||||
|
||||
## Status (Slice 1 — build green, MVP core flow)
|
||||
## Status (Slice 2 — sandboxes, routes, real auth, all MVP features)
|
||||
|
||||
`./scripts/build.sh` produces three Linux/amd64 binaries and a static
|
||||
dashboard. The MVP core flow — login, deploy a microservice or the PHP
|
||||
gateway, watch progress and logs in real time — works end to end. The
|
||||
sandbox / template / route management described in REQUIREMENTS.md is
|
||||
**deferred to Slice 2** and is not yet built. See
|
||||
[REQUIREMENTS.md](REQUIREMENTS.md#status) for a per-feature checklist.
|
||||
dashboard. The full MVP flow works end to end:
|
||||
|
||||
- Real Bitbucket auth via `git ls-remote` against the api-gateway.
|
||||
- Real repo and branch listing via agent WS frames.
|
||||
- Sandbox / template / environment CRUD with persisted metadata in
|
||||
SQLite.
|
||||
- Route overrides per sandbox, with live read-back of the
|
||||
`<service>_url` map from the gateway's `config.php` after every
|
||||
branch switch. The agent patches the file and gracefully reloads
|
||||
apache.
|
||||
- Per-deploy port binding: the user picks the host port per service
|
||||
(e.g. eredar at `172.18.136.92:9001`), the container's exposed port
|
||||
is published to that port.
|
||||
- Erangel deploy: `git reset --hard → fetch → checkout → pull →
|
||||
composer install → start container → re-apply route overrides`.
|
||||
Per-branch OCP-default snapshot persisted to
|
||||
`<repo>/.sdp/ocp-defaults.json`.
|
||||
|
||||
See [REQUIREMENTS.md](REQUIREMENTS.md#status) for the per-feature
|
||||
checklist.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
.
|
||||
├── protocol/ # shared wire types (Event, DeployRequest)
|
||||
├── protocol/ # shared wire types (Event, DeployRequest, RouteOverride, ...)
|
||||
├── agentlib/ # Go. Shared agent library: gitutil + deployer (Go/PHP flavours)
|
||||
├── control-plane/ # Go. HTTP API + WS hub + SQLite/.log persistence
|
||||
├── agent-micro/ # Go. Runs on 172.18.136.92, deploys Go microservices
|
||||
├── agent-gateway/ # Go. Runs on 172.18.139.186, deploys the PHP API Gateway
|
||||
├── dashboard/ # NextJS static export, served by nginx
|
||||
├── nginx/ # reverse proxy + try_files for the dashboard
|
||||
├── scripts/ # build, deploy, ssh wrappers, nginx patch
|
||||
├── nginx/ # reference nginx config (manually applied on 186)
|
||||
├── scripts/ # build, deploy, ssh wrappers
|
||||
├── docker-compose.yml # all three services on alpine:latest
|
||||
├── go.work # Go workspace — one build, five modules
|
||||
└── bin/ # build output (gitignored)
|
||||
@@ -39,10 +54,14 @@ for two build flavours:
|
||||
`docker run alpine:3.20` with the host repo bind-mounted at `/src` and
|
||||
the binary as the container command. `alpine:3.20` must be pre-loaded
|
||||
on the host (see [Offline VMs](#offline-vms)).
|
||||
- **`NewPHP`** — for the API Gateway. Runs `composer install --no-dev`
|
||||
on the host as a best-effort step (skipped if `composer` or
|
||||
`composer.json` are absent), then `docker run php:8.3-apache` with the
|
||||
repo bind-mounted at `/app`. `php:8.3-apache` must be pre-loaded on
|
||||
- **`NewPHP`** — for the API Gateway (erangel). Runs
|
||||
`git reset --hard → fetch → checkout → pull → composer install
|
||||
(best-effort) → docker run php:8.3-apache`, with the repo
|
||||
bind-mounted at `/var/www/html/erangel-ocean` and
|
||||
`APACHE_DOCUMENT_ROOT=/var/www/html/erangel-ocean` so the gateway is
|
||||
served at `/erangel/`, mirroring production. After the container is
|
||||
up, the agent's `AfterStart` callback re-applies the active route
|
||||
overrides and reloads apache. `php:8.3-apache` must be pre-loaded on
|
||||
the host. The agent is written in Go; the thing it deploys is a
|
||||
PHP project.
|
||||
|
||||
@@ -93,8 +112,10 @@ This script:
|
||||
2. SSHs to **172.18.139.186** (`administrator`) and pushes
|
||||
`bin/control-plane`, `bin/agent-gateway`, and `dashboard/out/` to
|
||||
`~/SDP/`
|
||||
3. Idempotently splices the SDP location block into
|
||||
`/etc/nginx/sites-available/default` on 186 and reloads nginx
|
||||
|
||||
Nginx on 186 is configured by hand; the dashboard ends up at
|
||||
`/home/administrator/SDP/dashboard/`. The required location block is
|
||||
in [nginx/nginx.conf](nginx/nginx.conf).
|
||||
|
||||
Override the creds via `SDP_92_PASS` / `SDP_186_PASS` env vars.
|
||||
|
||||
@@ -108,7 +129,7 @@ docker compose up -d
|
||||
```
|
||||
|
||||
Three services come up on `alpine:latest`:
|
||||
- `control-plane` → `:8080`
|
||||
- `control-plane` → `:3452` (an unusual port to avoid collisions)
|
||||
- `agent-micro` (connects to control plane, has docker socket + repos mounted)
|
||||
- `agent-gateway` (same shape)
|
||||
|
||||
@@ -135,24 +156,32 @@ Three services come up on `alpine:latest`:
|
||||
`/ws/agent` on the control plane; the dashboard subscribes to
|
||||
`/ws/deployments/{id}`.
|
||||
|
||||
## MVP stubs (intentional, not Slice 1 scope)
|
||||
## MVP stubs (intentional, deferred)
|
||||
|
||||
These are marked with `ponytail:` comments in the code and are
|
||||
scheduled for later slices. They are **not** in scope for Slice 1.
|
||||
scheduled for later slices.
|
||||
|
||||
- `validateViaAgent` (login) — accepts any creds if an agent is
|
||||
connected. Real impl: a `git ls-remote` probe frame to the agent
|
||||
(`control-plane/internal/api/api.go:126`).
|
||||
- `handleListRepos` / `handleListBranches` — hardcoded fixtures.
|
||||
Real impl: a `list_repos` / `list_branches` frame to the connected
|
||||
agent. The `gitutil.ListBranches` helper and the `agentlib` frame
|
||||
protocol are not yet wired up.
|
||||
- `handleListDeployments` (GET) — returns `[]`. Real impl reads SQLite
|
||||
(`control-plane/internal/api/api.go:182`).
|
||||
- WS auth on `/ws/deployments/*` — open. Real impl checks session token.
|
||||
- Sandbox, Template, Route, Environment CRUD — entirely deferred to
|
||||
Slice 2. The data model, REST endpoints, and dashboard pages do not
|
||||
exist yet.
|
||||
- `CheckOrigin` in the WS upgrader — open CORS, intentional for an
|
||||
internal tool.
|
||||
- "Drop on backpressure" policy for slow WS subscribers — replace with
|
||||
flow control or persistent event log if the dashboard ever needs
|
||||
catch-up replay.
|
||||
- O(n) log tail scan in `store.TailLogs` — fine for tail use; swap to
|
||||
a ring buffer if logs get huge.
|
||||
|
||||
## Slice 2 dashboard
|
||||
|
||||
The dashboard has these pages:
|
||||
|
||||
- `/` — login (real git-ls-remote via the gateway agent).
|
||||
- `/dashboard` — quick deploy (ad-hoc single-service deploy).
|
||||
- `/dashboard/sandboxes` — list, create, clone-from-template.
|
||||
- `/dashboard/sandboxes/{id}` — sandbox detail. Live routes from the
|
||||
gateway's `config.php`, per-route toggle (OCP / sandbox override),
|
||||
microservice deploys with per-service host port and env.
|
||||
- `/dashboard/templates` — template CRUD.
|
||||
- `/dashboard/environments` — env CRUD.
|
||||
- `/dashboard/history` — deployment history (filterable by sandbox).
|
||||
|
||||
## See also
|
||||
|
||||
|
||||
+52
-33
@@ -1,17 +1,25 @@
|
||||
# Sandbox Deployment Platform (SDP)
|
||||
|
||||
## Status (Slice 1 — build green, MVP core flow)
|
||||
## Status (Slice 2 — sandboxes, routes, real auth, all MVP features)
|
||||
|
||||
The build is green: `./scripts/build.sh` produces three Linux/amd64
|
||||
binaries and a static dashboard. The core MVP loop works end to end —
|
||||
login, deploy a microservice or the PHP gateway, watch progress and
|
||||
logs in real time.
|
||||
binaries and a static dashboard. The full MVP flow works end to end:
|
||||
|
||||
Sandbox / Template / Route / Environment management is **deferred to
|
||||
Slice 2** and is not yet built. Real auth via agent-mediated
|
||||
`git ls-remote` and real branch/repo listing from agents are also
|
||||
deferred (the current code has hardcoded fixtures and an "accept any
|
||||
creds if an agent is connected" stub for these).
|
||||
- Real Bitbucket auth via `git ls-remote` against the api-gateway.
|
||||
- Real repo and branch listing via agent WS frames.
|
||||
- Sandbox / template / environment CRUD with persisted metadata in
|
||||
SQLite.
|
||||
- Route overrides per sandbox, with live read-back of the
|
||||
`<service>_url` map from the gateway's `config.php` after every
|
||||
branch switch. The agent patches the file and gracefully reloads
|
||||
apache.
|
||||
- Per-deploy port binding: the user picks the host port per service
|
||||
(e.g. eredar at `172.18.136.92:9001`), the container's exposed port
|
||||
is published to that port.
|
||||
- Erangel deploy: `git reset --hard → fetch → checkout → pull →
|
||||
composer install → start container → re-apply route overrides`.
|
||||
Per-branch OCP-default snapshot persisted to
|
||||
`<repo>/.sdp/ocp-defaults.json`.
|
||||
|
||||
See [Status checklist](#status-checklist) at the bottom of this
|
||||
document for a per-feature status.
|
||||
@@ -114,16 +122,19 @@ IP Address:
|
||||
172.18.139.186
|
||||
|
||||
Repository Root:
|
||||
~/SDP
|
||||
/var/www/html/erangel-ocean
|
||||
```
|
||||
|
||||
Contains:
|
||||
|
||||
```text
|
||||
~/SDP
|
||||
/var/www/html/erangel-ocean
|
||||
```
|
||||
|
||||
The API Gateway repository.
|
||||
The API Gateway repository (erangel). The container
|
||||
`php:8.3-apache` bind-mounts this path at the same path inside the
|
||||
container and serves the gateway at `/erangel/`, mirroring the
|
||||
production URL space.
|
||||
|
||||
---
|
||||
|
||||
@@ -1460,37 +1471,45 @@ scheduled for Slice 2. `later` = out of scope for MVP.
|
||||
the per-operation Bitbucket creds.
|
||||
- `done` Micro agent runs `git fetch → checkout → pull → go build →
|
||||
docker run` and streams progress and logs back.
|
||||
- `done` Gateway agent runs `git fetch → checkout → pull → composer
|
||||
install (best-effort) → docker run` and streams progress and logs
|
||||
- `done` Gateway agent runs `git reset --hard → fetch → checkout →
|
||||
pull → composer install (best-effort) → docker run → re-apply route
|
||||
overrides → apache graceful reload` and streams progress and logs
|
||||
back.
|
||||
- `done` Dashboard subscribes to a deployment by id over WebSocket
|
||||
and renders stages + live log tail.
|
||||
- `done` SQLite persistence for deployment rows, stage transitions,
|
||||
and append-only log files.
|
||||
- `next` Replace `validateViaAgent` stub with a real
|
||||
`git ls-remote` frame.
|
||||
- `next` Replace hardcoded `handleListRepos` /
|
||||
`handleListBranches` with agent frames (the `gitutil.ListBranches`
|
||||
helper and the `agentlib` frame protocol are partially set up but
|
||||
not wired through).
|
||||
- `done` Real `validateViaAgent` via the agent's `git ls-remote`
|
||||
frame.
|
||||
- `done` Real `list_repos` / `list_branches` via agent frames; the
|
||||
hardcoded fixtures are gone.
|
||||
- `done` `list_routes` RPC exposes the live `<key>_url` map from
|
||||
the gateway's `config.php` after every branch switch.
|
||||
- `done` `GET /api/deployments` reads deployment history from
|
||||
SQLite (filterable by sandbox).
|
||||
|
||||
## Sandbox & routing (Slice 2)
|
||||
## Sandbox & routing
|
||||
|
||||
- `next` Sandbox CRUD (data model + REST endpoints + dashboard page).
|
||||
- `next` Sandbox template CRUD and "clone template into sandbox".
|
||||
- `next` Route management (sandbox vs OCP per service).
|
||||
- `next` Environment CRUD (persisted named envs, not just inline).
|
||||
- `next` Actual route push to the API Gateway (the gateway agent
|
||||
has to update the gateway's routing config, currently this is
|
||||
the manual `scripts/patch-nginx.sh` step).
|
||||
- `next` Port allocation table and helpers.
|
||||
- `done` Sandbox CRUD (data model + REST endpoints + dashboard
|
||||
pages).
|
||||
- `done` Sandbox template CRUD and "clone template into sandbox".
|
||||
- `done` Route management (sandbox vs OCP per service) with live
|
||||
read-back from the gateway's `config.php`.
|
||||
- `done` Environment CRUD (persisted named envs, not just inline).
|
||||
- `done` Actual route push to the API Gateway: the gateway agent
|
||||
rewrites `application/config/production/config.php` and gracefully
|
||||
reloads apache. A per-branch OCP-default snapshot is captured
|
||||
automatically and persisted to `<repo>/.sdp/ocp-defaults.json`.
|
||||
- `done` Per-deploy port binding: the user specifies the host port;
|
||||
the agent publishes the container's exposed port to it. Concurrency
|
||||
is "one live container per repo" (the stable name is `sdp-<repo>`).
|
||||
|
||||
## Auth
|
||||
|
||||
- `done` Login endpoint accepts any creds if an agent is connected
|
||||
(MVP stub).
|
||||
- `done` Session cookie + in-memory session store.
|
||||
- `next` Real auth via agent-mediated `git ls-remote`.
|
||||
- `done` Real auth via agent-mediated `git ls-remote` against the
|
||||
api-gateway. Login fails fast if no gateway agent is connected.
|
||||
- `done` Session cookie + in-memory session store, 12-hour TTL,
|
||||
logout invalidates the token.
|
||||
- `later` RBAC roles (admin / backend / qa / viewer).
|
||||
|
||||
## Out of scope for MVP (per the "Future Enhancements" section)
|
||||
|
||||
@@ -5,8 +5,13 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Slice-2: the control plane listens on an unusual port to avoid
|
||||
// collisions with anything else on the VM. Nginx on 186 proxies
|
||||
// /sandbox/credit-card/api/ and /sandbox/credit-card/ws/ to it.
|
||||
const defaultAddr = ":3452"
|
||||
|
||||
type Config struct {
|
||||
Addr string // listen addr, e.g. ":8080"
|
||||
Addr string // listen addr, default :3452
|
||||
DataDir string // SQLite + .log files live here
|
||||
AgentHealth string // map of nodeID -> agent base URL (TODO: real map)
|
||||
}
|
||||
@@ -14,7 +19,7 @@ type Config struct {
|
||||
// Load reads flags and env. Env wins over defaults; flags win over env.
|
||||
func Load() Config {
|
||||
c := Config{
|
||||
Addr: envOr("SDP_ADDR", ":8080"),
|
||||
Addr: envOr("SDP_ADDR", defaultAddr),
|
||||
DataDir: envOr("SDP_DATA", "./data"),
|
||||
AgentHealth: envOr("SDP_AGENT_HEALTH", ""),
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[165],{3155:function(e,t,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_not-found",function(){return n(4032)}])},4032:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i}}),n(6921);let o=n(3827);n(4090);let r={error:{fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},desc:{display:"inline-block"},h1:{display:"inline-block",margin:"0 20px 0 0",padding:"0 23px 0 0",fontSize:24,fontWeight:500,verticalAlign:"top",lineHeight:"49px"},h2:{fontSize:14,fontWeight:400,lineHeight:"49px",margin:0}};function i(){return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)("title",{children:"404: This page could not be found."}),(0,o.jsx)("div",{style:r.error,children:(0,o.jsxs)("div",{children:[(0,o.jsx)("style",{dangerouslySetInnerHTML:{__html:"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}),(0,o.jsx)("h1",{className:"next-error-h1",style:r.h1,children:"404"}),(0,o.jsx)("div",{style:r.desc,children:(0,o.jsx)("h2",{style:r.h2,children:"This page could not be found."})})]})})]})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},function(e){e.O(0,[971,69,744],function(){return e(e.s=3155)}),_N_E=e.O()}]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[185],{8216:function(n,e,u){Promise.resolve().then(u.t.bind(u,3385,23))},3385:function(){}},function(n){n.O(0,[971,69,744],function(){return n(n.s=8216)}),_N_E=n.O()}]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[744],{3734:function(e,n,t){Promise.resolve().then(t.t.bind(t,7690,23)),Promise.resolve().then(t.t.bind(t,8955,23)),Promise.resolve().then(t.t.bind(t,5613,23)),Promise.resolve().then(t.t.bind(t,1902,23)),Promise.resolve().then(t.t.bind(t,1778,23)),Promise.resolve().then(t.t.bind(t,7831,23))}},function(e){var n=function(n){return e(e.s=n)};e.O(0,[971,69],function(){return n(5317),n(3734)}),_N_E=e.O()}]);
|
||||
@@ -0,0 +1 @@
|
||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[888],{1597:function(n,_,u){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return u(7174)}])}},function(n){var _=function(_){return n(n.s=_)};n.O(0,[774,179],function(){return _(1597),_(4546)}),_N_E=n.O()}]);
|
||||
@@ -0,0 +1 @@
|
||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[820],{1981:function(n,_,u){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_error",function(){return u(5103)}])}},function(n){n.O(0,[888,774,179],function(){return n(n.s=1981)}),_N_E=n.O()}]);
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
!function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={exports:{}},r=!0;try{a[e](n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,f=0;f<n.length;f++)i>=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var a=r();void 0!==a&&(t=a)}}return t},d.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return d.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},d.t=function(e,r){if(1&r&&(e=this(e)),8&r||"object"==typeof e&&e&&(4&r&&e.__esModule||16&r&&"function"==typeof e.then))return e;var o=Object.create(null);d.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach(function(t){u[t]=function(){return e[t]}});return u.default=function(){return e},d.d(o,u),o},d.d=function(e,t){for(var n in t)d.o(t,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},d.f={},d.e=function(e){return Promise.all(Object.keys(d.f).reduce(function(t,n){return d.f[n](e,t),t},[]))},d.u=function(e){},d.miniCssF=function(e){return"static/css/157e72c563f45c59.css"},d.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="_N_E:",d.l=function(e,t,n,u){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var i,c,f=document.getElementsByTagName("script"),a=0;a<f.length;a++){var l=f[a];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",o+n),i.src=d.tu(e)),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),c&&document.head.appendChild(i)},d.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.tt=function(){return void 0===u&&(u={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(u=trustedTypes.createPolicy("nextjs#bundler",u))),u},d.tu=function(e){return d.tt().createScriptURL(e)},d.p="/sandbox/credit-card/_next/",i={272:0},d.f.j=function(e,t){var n=d.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(272!=e){var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=d.p+d.u(e),u=Error();d.l(o,function(t){if(d.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;u.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",u.name="ChunkLoadError",u.type=r,u.request=o,n[1](u)}},"chunk-"+e,e)}else i[e]=0}},d.O.j=function(e){return 0===i[e]},c=function(e,t){var n,r,o=t[0],u=t[1],c=t[2],f=0;if(o.some(function(e){return 0!==i[e]})){for(n in u)d.o(u,n)&&(d.m[n]=u[n]);if(c)var a=c(d)}for(e&&e(t);f<o.length;f++)r=o[f],d.o(i,r)&&i[r]&&i[r][0](),i[r]=0;return d.O(a)},(f=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(c.bind(null,0)),f.push=c.bind(null,f.push.bind(f)),d.nc=void 0}();
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/_error":["static/chunks/pages/_error-9a890acb1e81c3fc.js"],sortedPages:["/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|
||||
@@ -0,0 +1 @@
|
||||
self.__SSG_MANIFEST=new Set(["\u002Fdashboard\u002Fsandboxes\u002F[id]"]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
|
||||
2:I[3498,["504","static/chunks/504-b386cf1902f7f6c4.js","303","static/chunks/303-30e01c8e944885c6.js","702","static/chunks/app/dashboard/page-fe092df0d98d16a1.js"],"Dashboard"]
|
||||
3:I[9534,["504","static/chunks/504-b386cf1902f7f6c4.js","792","static/chunks/792-ff5864056e955fdc.js","663","static/chunks/app/dashboard/layout-49ba819eac821f2e.js"],"DashboardNav"]
|
||||
4:I[5613,[],""]
|
||||
5:I[1778,[],""]
|
||||
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["dashboard",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["dashboard",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{}],null]]},[null,["$","div",null,{"className":"min-h-screen bg-zinc-50","children":[["$","$L3",null,{}],["$","main",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]}]]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L6"]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
|
||||
1:null
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
||||
2:I[7831,[],""]
|
||||
3:I[5405,["504","static/chunks/504-b386cf1902f7f6c4.js","17","static/chunks/app/dashboard/environments/page-a627adbd0bb7fff2.js"],""]
|
||||
4:I[5613,[],""]
|
||||
5:I[1778,[],""]
|
||||
6:I[9534,["504","static/chunks/504-b386cf1902f7f6c4.js","792","static/chunks/792-ff5864056e955fdc.js","663","static/chunks/app/dashboard/layout-49ba819eac821f2e.js"],"DashboardNav"]
|
||||
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["dashboard",{"children":["environments",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["dashboard",{"children":["environments",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children","environments","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]]},[null,["$","div",null,{"className":"min-h-screen bg-zinc-50","children":[["$","$L6",null,{}],["$","main",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]}]]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L7"]]]]
|
||||
7:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
|
||||
1:null
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
||||
2:I[7831,[],""]
|
||||
3:I[3331,["504","static/chunks/504-b386cf1902f7f6c4.js","834","static/chunks/app/dashboard/history/page-d2d7ef8172cb6bd1.js"],""]
|
||||
4:I[5613,[],""]
|
||||
5:I[1778,[],""]
|
||||
6:I[9534,["504","static/chunks/504-b386cf1902f7f6c4.js","792","static/chunks/792-ff5864056e955fdc.js","663","static/chunks/app/dashboard/layout-49ba819eac821f2e.js"],"DashboardNav"]
|
||||
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["dashboard",{"children":["history",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["dashboard",{"children":["history",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children","history","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]]},[null,["$","div",null,{"className":"min-h-screen bg-zinc-50","children":[["$","$L6",null,{}],["$","main",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]}]]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L7"]]]]
|
||||
7:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
|
||||
1:null
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
||||
2:I[7831,[],""]
|
||||
3:I[7334,["504","static/chunks/504-b386cf1902f7f6c4.js","303","static/chunks/303-30e01c8e944885c6.js","792","static/chunks/792-ff5864056e955fdc.js","509","static/chunks/app/dashboard/sandboxes/page-fa8d8c9854e58bd7.js"],""]
|
||||
4:I[5613,[],""]
|
||||
5:I[1778,[],""]
|
||||
6:I[9534,["504","static/chunks/504-b386cf1902f7f6c4.js","792","static/chunks/792-ff5864056e955fdc.js","663","static/chunks/app/dashboard/layout-49ba819eac821f2e.js"],"DashboardNav"]
|
||||
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["dashboard",{"children":["sandboxes",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["dashboard",{"children":["sandboxes",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children","sandboxes","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]]},[null,["$","div",null,{"className":"min-h-screen bg-zinc-50","children":[["$","$L6",null,{}],["$","main",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]}]]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L7"]]]]
|
||||
7:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
|
||||
1:null
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
||||
2:I[1103,["504","static/chunks/504-b386cf1902f7f6c4.js","882","static/chunks/app/dashboard/sandboxes/%5Bid%5D/page-c1742b81a03d9067.js"],""]
|
||||
3:I[5613,[],""]
|
||||
5:I[1778,[],""]
|
||||
6:I[9534,["504","static/chunks/504-b386cf1902f7f6c4.js","792","static/chunks/792-ff5864056e955fdc.js","663","static/chunks/app/dashboard/layout-49ba819eac821f2e.js"],"DashboardNav"]
|
||||
4:["id","_","d"]
|
||||
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["dashboard",{"children":["sandboxes",{"children":[["id","_","d"],{"children":["__PAGE__?{\"id\":\"_\"}",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["dashboard",{"children":["sandboxes",{"children":[["id","_","d"],{"children":["__PAGE__",{},["$L1",["$","$L2",null,{}],null]]},["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children","sandboxes","children","$4","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]]},["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children","sandboxes","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]]},[null,["$","div",null,{"className":"min-h-screen bg-zinc-50","children":[["$","$L6",null,{}],["$","main",null,{"children":["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]}]]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L7"]]]]
|
||||
7:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
|
||||
1:null
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
||||
2:I[7831,[],""]
|
||||
3:I[4700,["504","static/chunks/504-b386cf1902f7f6c4.js","123","static/chunks/app/dashboard/templates/page-5d6b88cb4f5f8cdf.js"],""]
|
||||
4:I[5613,[],""]
|
||||
5:I[1778,[],""]
|
||||
6:I[9534,["504","static/chunks/504-b386cf1902f7f6c4.js","792","static/chunks/792-ff5864056e955fdc.js","663","static/chunks/app/dashboard/layout-49ba819eac821f2e.js"],"DashboardNav"]
|
||||
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["dashboard",{"children":["templates",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["dashboard",{"children":["templates",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children","templates","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]]},[null,["$","div",null,{"className":"min-h-screen bg-zinc-50","children":[["$","$L6",null,{}],["$","main",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]}]]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L7"]]]]
|
||||
7:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
|
||||
1:null
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,6 @@
|
||||
2:I[5733,["504","static/chunks/504-b386cf1902f7f6c4.js","931","static/chunks/app/page-0f77936ff7b5e685.js"],"LoginForm"]
|
||||
3:I[5613,[],""]
|
||||
4:I[1778,[],""]
|
||||
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},["$L1",["$","main",null,{"className":"flex min-h-screen items-center justify-center bg-muted/30 p-4","children":["$","$L2",null,{}]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L5"]]]]
|
||||
5:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
|
||||
1:null
|
||||
+4
-4
@@ -10,12 +10,12 @@ services:
|
||||
image: alpine:latest
|
||||
container_name: sdp-control-plane
|
||||
restart: unless-stopped
|
||||
command: ["/SDP/bin/control-plane", "-addr", ":8080", "-data", "/SDP/data"]
|
||||
command: ["/SDP/bin/control-plane", "-addr", ":3452", "-data", "/SDP/data"]
|
||||
volumes:
|
||||
- ./bin/control-plane:/SDP/bin/control-plane:ro
|
||||
- sdp-data:/SDP/data
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "3452:3452"
|
||||
|
||||
agent-micro:
|
||||
image: alpine:latest
|
||||
@@ -28,7 +28,7 @@ services:
|
||||
- -node
|
||||
- micro
|
||||
- -cp
|
||||
- ws://control-plane:8080/ws/agent
|
||||
- ws://control-plane:3452/ws/agent
|
||||
volumes:
|
||||
- ./bin/agent-micro:/SDP/bin/agent-micro:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
@@ -48,7 +48,7 @@ services:
|
||||
- -node
|
||||
- gateway
|
||||
- -cp
|
||||
- ws://control-plane:8080/ws/agent
|
||||
- ws://control-plane:3452/ws/agent
|
||||
volumes:
|
||||
- ./bin/agent-gateway:/SDP/bin/agent-gateway:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
+4
-3
@@ -1,11 +1,12 @@
|
||||
# SDP nginx — serves the static NextJS export and proxies API + WS
|
||||
# to the Go control plane.
|
||||
# SDP nginx — reference config for serving the dashboard at the host
|
||||
# root. The actual deployment on 186 mounts the dashboard under
|
||||
# /sandbox/credit-card; see nginx/sandbox.conf for that.
|
||||
#
|
||||
# try_files: any unknown path falls back to /index.html so client-side
|
||||
# routing works. /api and /ws are matched first and proxied upstream.
|
||||
|
||||
upstream control_plane {
|
||||
server 127.0.0.1:8080;
|
||||
server 127.0.0.1:3452;
|
||||
keepalive 16;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# SDP nginx for 186 — mount the dashboard under /sandbox/credit-card
|
||||
# and proxy /api/ + /ws/ to the control plane on 127.0.0.1:3452.
|
||||
#
|
||||
# Splice the four location blocks into the existing
|
||||
# /etc/nginx/sites-available/default server { } on 186. Order doesn't
|
||||
# matter for these specific prefixes (they're disjoint), but
|
||||
# longest-prefix-first is the convention.
|
||||
#
|
||||
# Verify with `sudo nginx -t` then `sudo systemctl reload nginx`.
|
||||
|
||||
# Static asset chunks (hashed filenames, safe to cache forever).
|
||||
location /sandbox/credit-card/_next/static/ {
|
||||
alias /home/administrator/SDP/dashboard/_next/static/;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# API: control plane on 127.0.0.1:3452. The proxy_pass has no path
|
||||
# component, so the original /api/... URI is forwarded unchanged.
|
||||
location /sandbox/credit-card/api/ {
|
||||
proxy_pass http://127.0.0.1:3452;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_read_timeout 3600s;
|
||||
}
|
||||
|
||||
# WebSocket: deployment log subscriptions + agent WS.
|
||||
location /sandbox/credit-card/ws/ {
|
||||
proxy_pass http://127.0.0.1:3452;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_read_timeout 3600s;
|
||||
}
|
||||
|
||||
# Everything else under /sandbox/credit-card: serve the static
|
||||
# dashboard. Client-side routes (e.g. /sandbox/credit-card/dashboard/sandboxes/abc)
|
||||
# fall through to /index.html, which Next.js hydrates and the React
|
||||
# Router takes over.
|
||||
location /sandbox/credit-card {
|
||||
alias /home/administrator/SDP/dashboard/;
|
||||
index index.html;
|
||||
try_files $uri $uri/ $uri.html /index.html;
|
||||
}
|
||||
+3
-8
@@ -4,8 +4,8 @@
|
||||
# 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.
|
||||
# Nginx is configured by hand on 186 (out of scope for this script).
|
||||
# Run scripts/build.sh first.
|
||||
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
@@ -47,10 +47,5 @@ $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."
|
||||
echo "done. (configure nginx by hand on 186; see AGENTS.md for the location block.)"
|
||||
|
||||
Reference in New Issue
Block a user