Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ echo -n 'us-east-1' > deploy/secrets/s3-region
# Optional — leave empty for standard AWS S3; set for R2 or other S3-compatible stores.
touch deploy/secrets/s3-endpoint
# echo -n 'https://your-endpoint.r2.dev' > deploy/secrets/s3-endpoint
# Optional — AWS credentials for explicit S3 authentication. Leave empty
# to fall back to the SDK default credential chain (env vars, ~/.aws,
# or EC2/EKS instance role).
```

> **Pair contract:** `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` must both be provided or both left empty. `AWS_SESSION_TOKEN` is only used when the pair is present — it is ignored otherwise. With neither pair value set, the AWS SDK default credential chain takes over (env vars, `~/.aws`, EC2/EKS instance role).

> **Rotation:** Static credentials read from `AWS_*_FILE` are loaded at gateway startup. Rotate by writing the new value into the secret file and restarting the gateway container. For refreshable credentials (STS temporary tokens, EC2/EKS instance roles), leave the pair empty and rely on the SDK default credential chain — it refreshes without restart.

```sh
touch deploy/secrets/aws-access-key-id
touch deploy/secrets/aws-secret-access-key
touch deploy/secrets/aws-session-token
# echo -n 'AKIAI...' > deploy/secrets/aws-access-key-id
# echo -n 'wJal...' > deploy/secrets/aws-secret-access-key
# echo -n 'FwoG...' > deploy/secrets/aws-session-token # only for STS temporary credentials
# Optional — guild-scoped slash command registration (propagates in ~5s vs up to 1h globally).
# Leave the file empty (or omit the echo) to register slash commands globally instead:
touch deploy/secrets/discord-guild-id
Expand Down Expand Up @@ -107,6 +123,15 @@ This checks:
- Recent log output
- Gateway exit code (fails if gateway crashed in the last cycle)

## Gateway Readiness

The gateway container is considered healthy only after two conditions are both true:

1. The Discord `clientReady` event has fired — the bot is fully connected and ready to receive events. At that point the process writes `/var/run/fro-bot/gateway-ready`.
2. The daemon process (PID 1) is still alive (`kill -0 1`).

The flag is cleared at process startup, so a stale `/var/run/fro-bot/gateway-ready` from a prior container run cannot mask a current-run failure. `docker compose up --wait` blocks until the gateway is genuinely connected to Discord before returning.

## Stopping the Stack

```bash
Expand Down
91 changes: 79 additions & 12 deletions deploy/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ services:
S3_BUCKET_FILE: /run/secrets/s3_bucket
S3_REGION_FILE: /run/secrets/s3_region
S3_ENDPOINT_FILE: /run/secrets/s3_endpoint
AWS_ACCESS_KEY_ID_FILE: /run/secrets/aws_access_key_id
AWS_SECRET_ACCESS_KEY_FILE: /run/secrets/aws_secret_access_key
AWS_SESSION_TOKEN_FILE: /run/secrets/aws_session_token
# Optional — guild-scoped slash command registration (fast dev propagation)
DISCORD_GUILD_ID_FILE: /run/secrets/discord_guild_id
# Egress proxy (regular proxy mode — NOT transparent)
Expand All @@ -28,27 +31,81 @@ services:
NO_PROXY: workspace,localhost,127.0.0.1
NODE_EXTRA_CA_CERTS: /etc/ssl/certs/mitmproxy-ca-cert.pem
volumes:
# Credentials — bind-mounted as accepted-risk (no Docker Swarm secrets in v1)
- ./secrets/discord-token:/run/secrets/discord_token:ro
- ./secrets/discord-application-id:/run/secrets/discord_application_id:ro
- ./secrets/s3-bucket:/run/secrets/s3_bucket:ro
- ./secrets/s3-region:/run/secrets/s3_region:ro
- ./secrets/s3-endpoint:/run/secrets/s3_endpoint:ro
# Credentials — bind-mounted as accepted-risk (no Docker Swarm secrets in v1).
# create_host_path: false ensures a missing source file causes a clear Compose
# error at `docker compose up` time instead of Docker silently creating a directory.
- type: bind
source: ./secrets/discord-token
target: /run/secrets/discord_token
read_only: true
bind:
create_host_path: false
- type: bind
source: ./secrets/discord-application-id
target: /run/secrets/discord_application_id
read_only: true
bind:
create_host_path: false
- type: bind
source: ./secrets/s3-bucket
target: /run/secrets/s3_bucket
read_only: true
bind:
create_host_path: false
- type: bind
source: ./secrets/s3-region
target: /run/secrets/s3_region
read_only: true
bind:
create_host_path: false
- type: bind
source: ./secrets/s3-endpoint
target: /run/secrets/s3_endpoint
read_only: true
bind:
create_host_path: false
# Optional — AWS credentials for explicit S3 authentication. Leave the
# access-key-id and secret-access-key files empty (`touch deploy/secrets/aws-access-key-id`)
# to fall back to the AWS SDK default credential chain (env, ~/.aws, IMDS).
# AWS_SESSION_TOKEN is only needed for STS temporary credentials.
- type: bind
source: ./secrets/aws-access-key-id
target: /run/secrets/aws_access_key_id
read_only: true
bind:
create_host_path: false
- type: bind
source: ./secrets/aws-secret-access-key
target: /run/secrets/aws_secret_access_key
read_only: true
bind:
create_host_path: false
- type: bind
source: ./secrets/aws-session-token
target: /run/secrets/aws_session_token
read_only: true
bind:
create_host_path: false
# Optional — guild-scoped slash command registration (fast dev propagation).
# Create the file empty (`touch deploy/secrets/discord-guild-id`) to register
# slash commands globally; the gateway treats empty files as unset.
- ./secrets/discord-guild-id:/run/secrets/discord_guild_id:ro
- type: bind
source: ./secrets/discord-guild-id
target: /run/secrets/discord_guild_id
read_only: true
bind:
create_host_path: false
# mitmproxy CA cert (written by mitmproxy on first start, read here for trust)
- mitmproxy-certs:/etc/ssl/certs:ro
networks:
- gateway-net
- sandbox-net
restart: unless-stopped
healthcheck:
test: [CMD-SHELL, test -s /etc/ssl/certs/mitmproxy-ca-cert.pem]
interval: 30s
timeout: 5s
retries: 3
logging:
driver: json-file
options:
max-size: 10m
max-file: '3'

workspace:
build:
Expand All @@ -65,6 +122,11 @@ services:
networks:
- sandbox-net
restart: unless-stopped
logging:
driver: json-file
options:
max-size: 10m
max-file: '3'

mitmproxy:
image: mitmproxy/mitmproxy:11.0.2@sha256:92ab9e0626ec73a3abae9c460e5475323f414745d7d0001205c8fa0c77ad31a2
Expand All @@ -89,6 +151,11 @@ services:
networks:
- sandbox-net
restart: unless-stopped
logging:
driver: json-file
options:
max-size: 10m
max-file: '3'
healthcheck:
# Gate dependent services on the CA actually existing in the shared
# volume. mitmproxy generates ~/.mitmproxy/mitmproxy-ca-cert.pem on
Expand Down
15 changes: 12 additions & 3 deletions deploy/gateway.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,17 @@ COPY --from=build /workspace/packages/gateway/dist/ ./packages/gateway/dist/

WORKDIR /app/packages/gateway

# Placeholder healthcheck — real HTTP health endpoint arrives in Unit 7
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD node -e 'process.exit(0)'
# Readiness flag directory. Kept outside /tmp so static-analysis tools
# don't flag the predictable-path pattern, and so the flag survives any
# future tmpfs-mount changes to /tmp.
RUN mkdir -p /var/run/fro-bot && chmod 0700 /var/run/fro-bot

# Readiness healthcheck — passes when the Discord `clientReady` event has
# fired (writes /var/run/fro-bot/gateway-ready) AND PID 1 is alive. Cleared
# at process startup so a stale flag from a prior process cannot mask a
# current-run failure. A real liveness probe (HTTP /healthz) lands alongside
# the workspace agent.
HEALTHCHECK --interval=10s --timeout=3s --retries=12 --start-period=45s \
CMD test -f /var/run/fro-bot/gateway-ready && kill -0 1

CMD ["node", "dist/main.mjs"]
2 changes: 1 addition & 1 deletion dist/artifact-D-cL1zfn.js → dist/artifact-BFFXP2kT.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/post.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading