diff --git a/deploy-v2/.env.example b/deploy-v2/.env.example new file mode 100644 index 0000000..2996d70 --- /dev/null +++ b/deploy-v2/.env.example @@ -0,0 +1,12 @@ +# Rustok prod stack (deploy-v2) — copy to `.env` (chmod 600) on the host and fill in. +# Mirrors /opt/rustok/deploy-v2/.env. Placeholders ONLY — never commit real values. + +# Unlocks the agent keystore (set at `create-wallet`). REQUIRED. +RUSTOK_KEYRING_PASSWORD= + +# Bearer key the MCP / clients present to the Gateway (Authorization: Bearer). REQUIRED. +RUSTOK_MCP_API_KEY= + +# Inbound bearer key clients present to the MCP SSE transport. REQUIRED in prod. +# Generate: openssl rand -hex 32 +RUSTOK_MCP_INBOUND_API_KEY= diff --git a/deploy-v2/Caddyfile-v2 b/deploy-v2/Caddyfile-v2 new file mode 100644 index 0000000..5496d12 --- /dev/null +++ b/deploy-v2/Caddyfile-v2 @@ -0,0 +1,28 @@ +# Cutover Caddyfile — api.rustokwallet.com now points at the new Gateway +# (Core+Gateway+MCP stack), replacing the old rustok-api monolith. +# aiwell.dev static site is preserved unchanged. + +api.rustokwallet.com { + # Access logs to stdout (docker logs rustok-caddy) so we can observe any + # consumer hitting the new backend post-cutover and catch broken integrations. + log { + format json + } + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains" + X-Content-Type-Options "nosniff" + X-Frame-Options "DENY" + } + + reverse_proxy rustok-gateway:3000 +} + +aiwell.dev, www.aiwell.dev { + root * /var/www/aiwell.dev + file_server + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains" + X-Content-Type-Options "nosniff" + X-Frame-Options "DENY" + } +} diff --git a/deploy-v2/README.md b/deploy-v2/README.md new file mode 100644 index 0000000..ec52c17 --- /dev/null +++ b/deploy-v2/README.md @@ -0,0 +1,13 @@ +# deploy-v2 — production deployment artifacts + +Mirror of `/opt/rustok/deploy-v2/` on the prod host (`ssh rustok-prod`), committed +for reproducibility. The new stack (compose project `rustokv2`): Core + Gateway + +MCP + Redis, fronted by Caddy on `api.rustokwallet.com`. + +- `docker-compose.yml` — the deployed compose (env-interpolated; **no secrets**). +- `Caddyfile-v2` — TLS reverse-proxy config for `api.rustokwallet.com`. +- `.env.example` — required env keys; copy to `.env` (chmod 600) on the host and fill. + +Images are shipped privately via `docker save | ssh | docker load` (Core source +stays private). Public images live on GHCR (`rustok-wallet` public, `rustok-core` +private). See `../docs/PROJECT-OVERVIEW.md` §6. diff --git a/deploy-v2/docker-compose.yml b/deploy-v2/docker-compose.yml new file mode 100644 index 0000000..75701b7 --- /dev/null +++ b/deploy-v2/docker-compose.yml @@ -0,0 +1,85 @@ +name: rustokv2 + +services: + redis: + image: redis:7-alpine + container_name: rustok-redis + restart: unless-stopped + networks: [rustokv2] + + core: + image: rustok-core:v0.1.0 + container_name: rustok-core + restart: unless-stopped + environment: + RUSTOK_GRPC_ADDR: "0.0.0.0:50051" + RUSTOK_REDIS_URL: "redis://rustok-redis:6379" + RUSTOK_KEYRING_PASSWORD: ${RUSTOK_KEYRING_PASSWORD:?} + RUSTOK_ALLOWED_CHAINS: "1,8453" + RUSTOK_RPC_URLS_1: "https://ethereum-rpc.publicnode.com" + RUSTOK_RPC_URLS_8453: "https://base-rpc.publicnode.com" + volumes: + - rustok-data:/home/rustok/.rustok/agent + networks: [rustokv2] + read_only: true + tmpfs: + - /tmp:noexec,nosuid,size=64m + user: "1000:1000" + + gateway: + image: rustok-core:v0.1.0 + container_name: rustok-gateway + entrypoint: ["/usr/local/bin/gateway"] + restart: unless-stopped + ports: + - "127.0.0.1:3001:3000" # loopback test port (live monolith stays on 3000) + environment: + RUSTOK_GATEWAY_ADDR: "0.0.0.0:3000" + RUSTOK_CORE_ADDR: "http://rustok-core:50051" + RUSTOK_MCP_API_KEY: ${RUSTOK_MCP_API_KEY:?} + networks: [rustokv2, shared] # shared = existing caddy network (for cutover) + read_only: true + tmpfs: + - /tmp:noexec,nosuid,size=64m + user: "1000:1000" + # Override the image's core healthcheck (probes :50051) — the gateway + # listens on :3000. No curl/wget in the image, so probe /health over /dev/tcp. + healthcheck: + test: + - CMD + - bash + - -c + - >- + exec 3<>/dev/tcp/127.0.0.1/3000 && + printf 'GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3 && + grep -q '"status":"ok"' <&3 + interval: 15s + timeout: 5s + retries: 3 + start_period: 10s + depends_on: [core] + + mcp: + image: rustok-mcp:v0.1.0 + container_name: rustok-mcp + restart: unless-stopped + ports: + - "127.0.0.1:3002:3001" # loopback test port + environment: + RUSTOK_MCP_HOST: "0.0.0.0" + RUSTOK_MCP_PORT: "3001" + RUSTOK_MCP_GATEWAY_URL: "http://rustok-gateway:3000" + RUSTOK_MCP_API_KEY: ${RUSTOK_MCP_API_KEY:?} + RUSTOK_MCP_INBOUND_API_KEY: ${RUSTOK_MCP_INBOUND_API_KEY:?} + networks: [rustokv2] + depends_on: [gateway] + +volumes: + rustok-data: + +networks: + rustokv2: + driver: bridge + shared: + name: deploy_default + external: true