@ossy/deployment-tools 3.0.9 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +156 -118
  3. package/jest.config.js +5 -0
  4. package/package.json +6 -4
  5. package/src/config/platform-config.js +16 -16
  6. package/src/ecs/platform-ecs-services.js +209 -0
  7. package/src/ecs/platform-ecs-services.spec.js +163 -0
  8. package/src/edge/platform-edge-domains.js +101 -0
  9. package/src/edge/platform-edge-domains.spec.js +87 -0
  10. package/src/index.js +45 -1
  11. package/src/infrastructure/dns-stack.js +47 -3
  12. package/src/infrastructure/dns-stack.spec.js +15 -0
  13. package/src/infrastructure/platform-ci-stack.js +151 -0
  14. package/src/infrastructure/platform-ecs-stack.js +349 -0
  15. package/src/infrastructure/platform-edge-stack.js +213 -0
  16. package/src/infrastructure/platform-secrets-stack.js +103 -0
  17. package/src/infrastructure/platform-stage.js +32 -5
  18. package/src/infrastructure/storage-static-stack.js +4 -5
  19. package/src/secrets/platform-secret-services.js +32 -0
  20. package/src/secrets/platform-secret-services.spec.js +40 -0
  21. package/src/secrets/sync-platform-secrets.js +136 -0
  22. package/src/services/platform-services.js +94 -0
  23. package/src/services/platform-services.spec.js +80 -0
  24. package/src/template/platform-template.js +5 -5
  25. package/src/infrastructure/container-deployment-target/aws-profile.js +0 -25
  26. package/src/infrastructure/container-deployment-target/caddy.service.js +0 -117
  27. package/src/infrastructure/container-deployment-target/container-deployment-target.js +0 -222
  28. package/src/infrastructure/container-deployment-target/container-service.js +0 -162
  29. package/src/infrastructure/container-deployment-target/index.js +0 -3
  30. package/src/infrastructure/container-deployment-target/ossy-api.service.js +0 -54
  31. package/src/infrastructure/container-deployment-target/ossy-runtime.service.js +0 -49
  32. package/src/infrastructure/container-deployment-target/user-data-commands.js +0 -31
  33. package/src/infrastructure/deployment-target-stack.js +0 -53
package/CHANGELOG.md CHANGED
@@ -3,6 +3,23 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [3.4.0](https://github.com/ossy-se/ossy/compare/v3.3.0...v3.4.0) (2026-08-14)
7
+
8
+ ### Features
9
+
10
+ * **deployment-tools:** add ECS container /health probes ([#589](https://github.com/ossy-se/ossy/issues/589)) ([0761f19](https://github.com/ossy-se/ossy/commit/0761f19a695f98470fccdcd9fc90ae0af288d7f3)), closes [#553](https://github.com/ossy-se/ossy/issues/553) [#553](https://github.com/ossy-se/ossy/issues/553)
11
+
12
+
13
+ # [3.1.0](https://github.com/ossy-se/ossy/compare/v3.0.9...v3.1.0) (2026-08-12)
14
+
15
+ ### Features
16
+
17
+ * **deployment-tools:** add CloudFront WAF and ACM edge in front of ALB ([#574](https://github.com/ossy-se/ossy/issues/574)) ([2dd1a69](https://github.com/ossy-se/ossy/commit/2dd1a69f4facfcbcc0e30c931915e6d58c916ad7))
18
+ * **deployment-tools:** add ECS Fargate and ALB for platform services ([#573](https://github.com/ossy-se/ossy/issues/573)) ([42ef998](https://github.com/ossy-se/ossy/commit/42ef9986a1f96b422f0351b311e906dadd4db26b)), closes [#557](https://github.com/ossy-se/ossy/issues/557) [#572](https://github.com/ossy-se/ossy/issues/572) [#560](https://github.com/ossy-se/ossy/issues/560)
19
+ * **deployment-tools:** add per-service Secrets Manager secrets ([#572](https://github.com/ossy-se/ossy/issues/572)) ([021ad55](https://github.com/ossy-se/ossy/commit/021ad5535d20722c819c154f41239bf8e583b9c3)), closes [#555](https://github.com/ossy-se/ossy/issues/555)
20
+ * **deployment-tools:** cut over DNS to CloudFront and retire EC2/Caddy ([#570](https://github.com/ossy-se/ossy/issues/570)) ([99c4323](https://github.com/ossy-se/ossy/commit/99c43236adfee4ea68534f3245d2d9d420c51ce4)), closes [#560](https://github.com/ossy-se/ossy/issues/560) [#560](https://github.com/ossy-se/ossy/issues/560)
21
+
22
+
6
23
  ## 3.0.9 (2026-08-11)
7
24
 
8
25
  **Note:** Version bump only for package @ossy/deployment-tools
package/README.md CHANGED
@@ -1,45 +1,41 @@
1
1
  # @ossy/deployment-tools
2
2
 
3
- Infrastructure and CDK tooling for the Ossy platform EC2 host.
3
+ Infrastructure and CDK tooling for the Ossy platform.
4
4
 
5
- The host runs Caddy as a reverse proxy plus several Docker containers managed by systemd. Apps are deployed by running `ossy app publish`.
5
+ **Architecture** (epic #553): **CloudFront WAF ALB ECS Fargate**, with per-service Secrets Manager and GitHub Actions rollouts via OIDC. EC2 + Caddy + EIP are **removed from CDK** (#560).
6
6
 
7
7
  ## Architecture
8
8
 
9
9
  ```
10
- Internet → Caddy (:443)
11
- ├── api.ossy.se → ossy-api (:3001)
12
- ├── ossy.se ossy-website-ossy (:3002) ← temporary
13
- ├── www.plexus-sanitas.comossy-website-plexus-sanitas (:3003) ← temporary
14
- └── *.* (on-demand TLS) ossy-platform (:3000)
15
- └── loads build from CMS on first request
10
+ Internet → CloudFront (ACM TLS, us-east-1) → WAF → ALB (:80)
11
+ ├── host rules from platforms.json `services[].hosts|domain`
12
+ │ e.g. ossy.se, api.ossy.se → website-ossy Fargate service
13
+ ├── other known hosts / *.ossy.seplatform-runtime Fargate service
14
+ └── default actionplatform-runtime
16
15
  ```
17
16
 
18
- Caddy uses **on-demand TLS**: before issuing a certificate for any domain, it calls `GET http://localhost:3001/api/v0/apps/ask?domain=<domain>`. If the domain is registered (i.e. `ossy app publish` has been run for it), Caddy issues the cert and proxies to the platform runtime.
17
+ - Website images include the API **no** separate `ossy-api` service.
18
+ - Task definitions inject env from Secrets Manager (`{platform}/{serviceKey}`) and pull images from GHCR using `{platform}/ghcr-pull`.
19
+ - Health checks use `GET /health` on each ALB target group **and** each ECS container (Node `fetch` probe; no curl in `node:*-slim`).
20
+ - App CloudFront is **separate** from the `/media` CloudFront in `storage-static`.
21
+ - Viewer `Host` is forwarded to the ALB (`OriginRequestPolicy.ALL_VIEWER`) so host rules match.
22
+ - ACM terminates TLS at CloudFront; Caddy is not part of this path.
23
+ - Route53 for known domains aliases to CloudFront (A + AAAA), not an Elastic IP.
24
+
25
+ Customer nameserver / custom-domain handoff at a registrar is **out of scope** (follow-up).
19
26
 
20
27
  ## Health checks (ALB / ECS)
21
28
 
22
- Every deployable Ossy HTTP image exposes an unauthenticated liveness probe at **`GET /health`** (also accepts `HEAD`). It returns `200` when the Node process can accept traffic.
29
+ Every deployable Ossy HTTP image exposes an unauthenticated liveness probe at **`GET /health`** (also accepts `HEAD`). It returns `200` when the Node process can accept traffic. The JSON `service` field prefers **`OSSY_SERVICE_NAME`** (set on the task definition to the service key).
23
30
 
24
31
  | Image | How `/health` is provided |
25
32
  |---|---|
26
33
  | `ghcr.io/ossy-se/platform` (runtime) | Built into `@ossy/platform` `startRuntime` — does not load a CMS site |
27
34
  | Website / app images (`website-ossy`, other `services[]`) | Built into `@ossy/platform` `startServer` (and/or a local `health.api.js` until the platform package is bumped) |
28
35
 
29
- Use path `/health` for future ALB target-group and ECS container health checks. Do not point probes at `/`, `/status`, or authenticated routes.
30
-
31
- ## Services
32
-
33
- All services run as Docker containers managed by systemd. Service files are written to the EC2 host by CDK user data at instance creation.
36
+ **ALB target groups** probe `GET /health`. **ECS container health checks** run an exec-form Node one-liner (`ecsContainerHealthCheckCommand`) that `fetch`es the same path on `PORT` and aborts after `HEALTHCHECK_FETCH_TIMEOUT_MS` (4s). Do not point probes at `/`, `/status`, or authenticated routes.
34
37
 
35
- | Service | Unit file | Port | Image |
36
- |---|---|---|---|
37
- | Caddy | `caddy-route53.service` | 80, 443 | — |
38
- | Ossy API | `ossy-api.service` | 3001 | `ghcr.io/ossy-se/api:latest` |
39
- | Ossy Platform | `ossy-runtime.service` | 3000 | `ghcr.io/ossy-se/platform:latest` |
40
- | _configured via `services`_ | `<name>.service` | _per config_ | _per config_ |
41
-
42
- Additional services are generated automatically from the `services` array in `platforms.json` — see the Configuration section below.
38
+ Task definitions also set plain env `PORT`, `OSSY_SERVICE_NAME`, `NODE_ENV`, and media bucket vars via `containerEnvironment`. Those keys are **filtered out** of Secrets Manager injection (`secretEnvKeysForEcsContainer`) so ECS cannot reject duplicate environment/secret keys.
43
39
 
44
40
  ## Configuration — `platforms.json`
45
41
 
@@ -49,101 +45,179 @@ Additional services are generated automatically from the `services` array in `pl
49
45
  |---|---|---|
50
46
  | `platformName` | Yes | Unique name, used as the CDK stage name and CloudFormation stack prefix |
51
47
  | `awsAccountId` | Yes | AWS account ID |
52
- | `awsKeyPairName` | Yes | EC2 key pair name for SSH access |
53
- | `awsInstanceClass` | No | EC2 instance class (e.g. `t3`, `t4g`, `m5`). Defaults to `t3` |
54
- | `awsInstanceSize` | No | EC2 instance size (e.g. `small`, `medium`, `large`). Defaults to `small` |
48
+ | `awsRegion` | No | Defaults to `eu-north-1` |
55
49
  | `sesDomains` | No | Domains to configure SES email identity for |
56
- | `domains` | No | Domains to create Route53 A records for, pointing to the EC2 Elastic IP. Supports wildcards (e.g. `*.ossy.se`). |
57
- | `services` | No | Per-platform container services (see below) |
50
+ | `domains` | No | Domains for Route53 aliases CloudFront and ACM coverage. Supports wildcards (e.g. `*.ossy.se`). |
51
+ | `services` | No | Per-platform HTTP container services (see below) |
58
52
  | `dnsRecords` | No | Additional DNS records by root domain (currently supports MX) |
59
- | `env` | No | Environment variables written to `/etc/environment` on the EC2 host at boot. **Never uploaded to S3.** |
60
-
61
- ### `services` container/domain mapping
53
+ | `env` | No | Env vars upserted into per-service Secrets Manager secrets (`npm run sync-secrets`). **Never uploaded to S3.** Values remain in git for now. |
54
+ | `githubDeployRepos` | Yes (for `platform-ci`) | `owner/repo` list trusted by the GitHub OIDC deploy role. Set in `platforms.json` (e.g. `ossy-se/ossy`, `ossy-se/website-ossy`). |
55
+ | `awsKeyPairName` / `awsInstanceClass` / `awsInstanceSize` | No | **Unused** after EC2 decommission (#560). Harmless if left in JSON. |
62
56
 
63
- Services come in two flavours: **HTTP** (routed through Caddy) and **TCP/UDP** (raw socket, bypasses Caddy). Both generate a systemd unit that pulls the image on every restart and forwards `/etc/environment` to the container.
57
+ ### `services` HTTP containers on ECS
64
58
 
65
- #### HTTP service (default)
66
-
67
- Generates a Caddy reverse-proxy block with Route53 TLS. The container must listen on port 3000.
59
+ HTTP services (default) become Fargate services with ALB host rules. The container must listen on port 3000.
68
60
 
69
61
  ```json
70
62
  "services": [
71
63
  {
72
64
  "name": "ossy-website-ossy",
73
65
  "domain": "ossy.se",
74
- "image": "ghcr.io/ossy-se/website-ossy:latest"
66
+ "image": "ghcr.io/ossy-se/website-ossy:latest",
67
+ "hosts": ["ossy.se", "api.ossy.se"]
75
68
  }
76
69
  ]
77
70
  ```
78
71
 
79
72
  | Field | Description |
80
73
  |---|---|
81
- | `name` | Unique service name — Docker container name and systemd unit (`<name>.service`). |
82
- | `domain` | Hostname routed by Caddy to this container (Route53 TLS via `dns route53`). |
83
- | `image` | Docker image to pull and run. |
74
+ | `name` | Unique service name — Secrets Manager / ECS key strips a leading `ossy-` (`website-ossy`). |
75
+ | `domain` | Primary hostname (default ALB host when `hosts` is omitted). |
76
+ | `hosts` | Optional ALB host-header list. Example: `["ossy.se","api.ossy.se"]` so the website image serves apex and API host. Defaults to `[domain]`. |
77
+ | `image` | Docker image to pull and run (GHCR). |
84
78
 
85
- Host ports are auto-assigned starting at `3002`, counting only HTTP services (TCP entries are skipped). Appending new HTTP entries is safe; avoid reordering existing ones.
79
+ **Built-in (always present):**
80
+ - `runtime` — platform runtime (`ghcr.io/ossy-se/platform:latest`) serving CMS-published apps; ALB default + unclaimed `domains`.
86
81
 
87
- #### TCP/UDP service
82
+ **Not deployed:** separate `ossy-api` container; TCP/UDP `services[]` entries (ignored by the ECS path).
88
83
 
89
- No Caddy block. Ports are mapped directly (`HOST:CONTAINER`) and opened in the EC2 security group. Use this for game servers or any other raw socket protocol.
84
+ **Wildcard subdomains:** Add `*.ossy.se` to `domains` for a Route53 wildcard alias to CloudFront.
90
85
 
91
- ```json
92
- "services": [
93
- {
94
- "name": "minecraft",
95
- "type": "tcp",
96
- "image": "itzg/minecraft-server",
97
- "ports": [25565],
98
- "protocol": "tcp"
99
- }
100
- ]
86
+ `MEDIA_REPOSITORY` and `MEDIA_CDN_DOMAIN_NAME` are derived from the provisioned S3 bucket and media CloudFront — do not set them in `env`.
87
+
88
+ To add a new platform, add an entry to `platforms.json`. No bucket name or CDN domain is needed; these are auto-generated by CloudFormation.
89
+
90
+ ## Platform secrets (Secrets Manager)
91
+
92
+ Each platform service gets **one** Secrets Manager secret (JSON object of env keys). Values still live in `platforms.json` for now; CDK owns the secret resources and IAM, and deploy upserts values.
93
+
94
+ | Secret name | Source |
95
+ |---|---|
96
+ | `{platform}/runtime` | Built-in platform-runtime |
97
+ | `{platform}/website-ossy` | HTTP entry in `services[]` named `ossy-website-ossy` (leading `ossy-` stripped) |
98
+ | `{platform}/…` | Other HTTP `services[]` entries |
99
+
100
+ **Not created:** `ossy-api`. TCP/UDP `services[]` entries are skipped.
101
+
102
+ Each secret has a matching IAM role `{platform}-{key}-task` assumed by `ecs-tasks.amazonaws.com` with read access to that secret only.
103
+
104
+ Also created: `{platform}/ghcr-pull` — Docker Hub-style credentials JSON (`username` + `password` PAT with `read:packages`) for private GHCR pulls. **Not** filled by `sync-secrets`; set manually once after the secrets stack deploys.
105
+
106
+ ### Sync values after CDK deploy
107
+
108
+ ```bash
109
+ cd packages/deployment-tools
110
+
111
+ # Create / update secret resources + IAM (part of the stage)
112
+ npx cdk deploy 'ossybot/platform-secrets' --profile ossybot
113
+
114
+ # Upsert secret *values* from platforms.json (never prints secret contents)
115
+ npm run sync-secrets -- --profile ossybot
116
+
117
+ # Optional: one platform, or dry-run
118
+ npm run sync-secrets -- --profile ossybot --platform ossybot
119
+ npm run sync-secrets -- --dry-run
101
120
  ```
102
121
 
103
- | Field | Description |
122
+ Do **not** remove secrets from `platforms.json` yet.
123
+
124
+ ## ECS Fargate + ALB
125
+
126
+ ```bash
127
+ cd packages/deployment-tools
128
+
129
+ # Secrets + IAM first, then sync values + set ghcr-pull
130
+ npx cdk deploy 'ossybot/platform-secrets' --profile ossybot
131
+ npm run sync-secrets -- --profile ossybot
132
+ # then put GHCR credentials into ossybot/ghcr-pull
133
+
134
+ # VPC, cluster, Fargate services, ALB host rules
135
+ npx cdk deploy 'ossybot/platform-ecs' --profile ossybot
136
+ ```
137
+
138
+ | Resource | Notes |
139
+ |---|---|
140
+ | VPC | Public + private subnets, 1 NAT gateway |
141
+ | ECS cluster | `{platform}-platform` |
142
+ | Fargate services | `runtime` (`ghcr.io/ossy-se/platform:latest`) + each HTTP `services[]` entry |
143
+ | ALB | Internet-facing HTTP :80; host rules; default → runtime; health check `/health` |
144
+ | Task env | Secrets Manager JSON keys from `platforms.json` `env`; `MEDIA_*` from storage stack |
145
+
146
+ ## CloudFront + WAF + ACM
147
+
148
+ App traffic uses its own CloudFront distribution (do **not** overload the `/media` CDN). The edge stack is deployed in **`us-east-1`** because CloudFront viewer certificates and `CLOUDFRONT`-scope WAFv2 web ACLs must live there.
149
+
150
+ ```bash
151
+ # After platform-ecs exists (ALB DNS used as origin)
152
+ npx cdk deploy 'ossybot/platform-edge' --profile ossybot
153
+
154
+ # DNS aliases for known domains → CloudFront
155
+ npx cdk deploy 'ossybot/dns' --profile ossybot
156
+ ```
157
+
158
+ | Piece | Detail |
104
159
  |---|---|
105
- | `name` | Unique service name Docker container name and systemd unit. |
106
- | `type` | Must be `"tcp"` to enable this mode. |
107
- | `image` | Docker image to pull and run. |
108
- | `ports` | Array of port numbers to expose on the host and open in the security group. |
109
- | `protocol` | `"tcp"` (default), `"udp"`, or `"both"`. |
160
+ | Origin | ALB DNS name from `platform-ecs` (`LoadBalancerDnsName`), HTTP :80 |
161
+ | Certificate | ACM in `us-east-1`, DNS-validated; covers `domains` + HTTP service hosts (apex + `*.ossy.se` etc.) |
162
+ | WAF | WAFv2 web ACL (`AWSManagedRulesCommonRuleSet` + `KnownBadInputs`) associated via `webAclId` |
163
+ | Cache | `CACHING_DISABLED` + `ALL_VIEWER` origin request policy (forwards `Host`) |
164
+ | DNS | Route53 alias A/AAAA for known domains → CloudFront |
110
165
 
111
- **Fixed built-in services** (always present, not configurable via `services`):
112
- - `ossy-api` — the Ossy API (`ghcr.io/ossy-se/api:latest`, port 3001)
113
- - `ossy-runtime` — the platform runtime serving all CMS-published apps (`ghcr.io/ossy-se/platform:latest`, port 3000, catch-all)
166
+ ## Release to ECS from GitHub Actions
114
167
 
115
- **Custom domains**: For customer-owned domains (e.g. `woodhill.com`), the customer adds an A record pointing to the Elastic IP at their registrar. No infrastructure change needed Caddy's on-demand TLS issues the cert automatically once the domain is registered via `ossy app publish`.
168
+ Images stay on **GHCR**. After a push to `main`, Publish workflows tag the image with the git SHA (and `:latest`), assume the platform deploy role via **GitHub OIDC** (no long-lived AWS keys), register a new task definition revision with the SHA tag, and update the ECS service.
116
169
 
117
- **Wildcard subdomains**: Add `*.ossy.se` to `domains` for a Route53 wildcard A record. This covers any `*.ossy.se` subdomain without individual records.
170
+ ```bash
171
+ # After platform-ecs exists — creates OIDC provider + deploy role
172
+ npx cdk deploy 'ossybot/platform-ci' --profile ossybot
173
+ ```
118
174
 
119
- `MEDIA_REPOSITORY` and `MEDIA_CDN_DOMAIN_NAME` are derived automatically from the provisioned S3 bucket and CloudFront distribution — do not set them in `env`.
175
+ | Piece | Detail |
176
+ |---|---|
177
+ | Role | `{platform}-github-deploy` (e.g. `ossybot-github-deploy`) |
178
+ | Trust | OIDC `token.actions.githubusercontent.com`, `sub` = `repo:<owner>/<repo>:ref:refs/heads/main` for each entry in `platforms.json` → `githubDeployRepos` |
179
+ | Cluster / services | `{platform}-platform` / `{platform}-{serviceKey}` (e.g. `ossybot-runtime`, `ossybot-website-ossy`) |
180
+ | Image tags | `ghcr.io/ossy-se/<image>:latest` and `:<git-sha>` |
181
+ | Workflows | each trusted repo’s Publish workflow rolls its ECS service |
120
182
 
121
- To add a new platform, add an entry to `platforms.json`. No bucket name or CDN domain is needed; these are auto-generated by CloudFormation.
183
+ Add another deploy source by appending `owner/repo` to `githubDeployRepos` and redeploying `platform-ci`. Deploy that stack once before the first Actions rollout. Workflows wait for ECS service stability after update.
122
184
 
123
185
  ## CDK Stacks
124
186
 
125
- Each platform is a CDK `Stage` containing five stacks:
187
+ Each platform is a CDK `Stage` containing these stacks:
126
188
 
127
189
  | Stack | Description |
128
190
  |---|---|
129
- | `storage-static` | S3 bucket (auto-named), daily backup plan, CloudFront CDN for `/media`. Kept separate from the EC2 stack so storage resources are never touched during instance updates. |
130
- | `deployment-target` | EC2 instance with Elastic IP. Imports the S3 bucket from `storage-static` via cross-stack reference. |
131
- | `dns` | Route53 A records for all domains in `platforms.json`, pointing to the Elastic IP. |
191
+ | `storage-static` | S3 bucket (auto-named), daily backup plan, CloudFront CDN for `/media`. |
192
+ | `platform-secrets` | Secrets Manager secrets per service + ECS task roles (read IAM) + `ghcr-pull`. Values synced from `platforms.json` via `npm run sync-secrets`. |
193
+ | `platform-ecs` | VPC, ECS Fargate services, ALB with host-based routing. |
194
+ | `platform-ci` | GitHub OIDC provider + `{platform}-github-deploy` IAM role for ECS rollouts from Actions. |
195
+ | `platform-edge` | CloudFront + WAF + ACM in `us-east-1` in front of the ALB. |
196
+ | `dns` | Route53 alias records for known domains → CloudFront app distribution. |
132
197
  | `ses` | SES email sending identity and DKIM records. |
133
198
 
134
- The Elastic IP ensures the EC2 instance can be replaced (instance type changes, user data updates) without breaking DNS or CloudFormation cross-stack exports.
199
+ ### Cutover / decommission EC2 (#560)
200
+
201
+ CDK no longer synthesizes `deployment-target` (EC2 + EIP + Caddy + systemd units). After `platform-edge` and `dns` are live and traffic is healthy:
202
+
203
+ ```bash
204
+ # Destroy the leftover CloudFormation stack (name may be stage-prefixed)
205
+ npx cdk destroy 'ossybot/deployment-target' --profile ossybot
206
+ # or, if the stack still exists outside the app:
207
+ # aws cloudformation delete-stack --stack-name ossybot-deployment-target --profile ossybot
208
+ ```
209
+
210
+ Downtime during cutover is acceptable. Confirm known domains resolve to CloudFront before destroying EC2.
135
211
 
136
212
  ## Bootstrapping a new platform
137
213
 
138
214
  ```bash
139
215
  cd packages/deployment-tools
140
216
  npx cdk deploy 'ossybot/**' --profile ossybot --require-approval never
217
+ npm run sync-secrets -- --profile ossybot
218
+ # set {platform}/ghcr-pull credentials once
141
219
  ```
142
220
 
143
- CDK provisions the instance, writes all service files, writes `/etc/environment` from `platforms.json`, and starts all services automatically. No manual SSH steps are required.
144
-
145
- > Note: EC2 user data only runs on **first boot**. To apply infrastructure changes to an existing instance you must terminate and recreate it, or SSH in and apply changes manually.
146
-
147
221
  ## Deploying changes
148
222
 
149
223
  Target a single platform:
@@ -168,32 +242,18 @@ npx cdk list
168
242
 
169
243
  ## Redeploying after a code change
170
244
 
171
- All services pull the latest image from GHCR on every restart no manual build step needed.
172
-
173
- ```bash
174
- # API — push to main triggers CI → ghcr.io/ossy-se/api:latest
175
- sudo systemctl restart ossy-api
176
-
177
- # Platform runtime — merge to main in ossy monorepo → ghcr.io/ossy-se/platform:latest
178
- sudo systemctl restart ossy-runtime
179
-
180
- # website-ossy — push to main in website-ossy repo → ghcr.io/ossy-se/website-ossy:latest
181
- sudo systemctl restart ossy-website-ossy
182
-
183
- # website-plexus-sanitas — push to main → ghcr.io/ossy-se/website-plexus-sanitas:latest
184
- sudo systemctl restart ossy-website-plexus-sanitas
185
- ```
245
+ Merge to `main` Publish workflows push GHCR tags (`:latest` + `:<sha>`) and roll the matching ECS service. No SSH / `systemctl`.
186
246
 
187
247
  ## Environment variables
188
248
 
189
- Defined in the `env` block of `platforms.json`, written to `/etc/environment` at boot. The following variables are injected automatically by CDK and should **not** be set manually:
249
+ Defined in the `env` block of `platforms.json`, synced into Secrets Manager. The following are injected automatically by the ECS stack and should **not** be set manually:
190
250
 
191
251
  | Variable | Source |
192
252
  |---|---|
193
253
  | `MEDIA_REPOSITORY` | S3 bucket name from `storage-static` stack |
194
254
  | `MEDIA_CDN_DOMAIN_NAME` | CloudFront domain from `storage-static` stack |
195
255
 
196
- All other env vars:
256
+ Other common env vars:
197
257
 
198
258
  | Variable | Used by | Description |
199
259
  |---|---|---|
@@ -209,32 +269,10 @@ All other env vars:
209
269
 
210
270
  ## Useful commands
211
271
 
212
- **SSH into instance**
213
- ```bash
214
- ssh -i path/to/keys ubuntu@<elastic-ip>
215
- ```
216
-
217
- **View service logs**
218
- ```bash
219
- journalctl -u ossy-runtime.service -n 200 -f
220
- journalctl -u ossy-api.service -n 200 -f
221
- journalctl -u ossy-website-ossy.service -n 200 -f
222
- journalctl -u caddy-route53.service -n 200 -f
223
- ```
224
-
225
- **View running containers**
226
272
  ```bash
227
- docker ps
228
- ```
229
-
230
- **View container logs**
231
- ```bash
232
- docker logs ossy-runtime
233
- docker logs ossy-api
234
- docker logs ossy-website-ossy
235
- ```
236
-
237
- **Reload Caddy config**
238
- ```bash
239
- sudo systemctl reload caddy-route53.service
273
+ cd packages/deployment-tools
274
+ npm test
275
+ npx cdk list
276
+ npx cdk diff 'ossybot/**' --profile ossybot
277
+ npm run sync-secrets -- --profile ossybot --dry-run
240
278
  ```
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ /** @type {import('jest').Config} */
2
+ module.exports = {
3
+ testEnvironment: 'node',
4
+ testMatch: ['**/src/**/*.spec.js'],
5
+ }
package/package.json CHANGED
@@ -1,18 +1,20 @@
1
1
  {
2
2
  "name": "@ossy/deployment-tools",
3
- "version": "3.0.9",
3
+ "version": "3.4.0",
4
4
  "description": "Collection of scripts and tools to aid deployment of containers and static files to Amazon Web Services through GitHub Actions",
5
5
  "source": "./src/index.js",
6
6
  "main": "./src/index.js",
7
7
  "scripts": {
8
- "test": "echo \"Error: no test specified\" && exit 0",
8
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest",
9
9
  "build": "echo \"The build step is not required when using JavaScript!\" && exit 0",
10
10
  "build:docs": "jsdoc -c jsdoc.config.js",
11
- "cdk": "cdk"
11
+ "cdk": "cdk",
12
+ "sync-secrets": "node ./src/secrets/sync-platform-secrets.js"
12
13
  },
13
14
  "author": "Ossy",
14
15
  "license": "ISC",
15
16
  "dependencies": {
17
+ "@aws-sdk/client-secrets-manager": "^3.0.0",
16
18
  "aws-cdk": "^2.73.0",
17
19
  "aws-cdk-lib": "^2.73.0",
18
20
  "constructs": "^10.1.304",
@@ -22,5 +24,5 @@
22
24
  "devDependencies": {
23
25
  "jest": "^30.4.2"
24
26
  },
25
- "gitHead": "908fae28675f6c3c179d4c2eecb451e8a1af886e"
27
+ "gitHead": "d36b69444268d172bc3e8e1dc77e67afc63f8650"
26
28
  }
@@ -3,31 +3,31 @@
3
3
  * @typedef {Object} PlatformConfig
4
4
  * @property {string} platformName - Name of platform
5
5
  * @property {string[]=} sesDomains - list of domains to configure AWS SES email identity and DKIM records for
6
- * @property {string[]=} domains - list of domains to create Route53 A records for, pointing to the EC2 host
6
+ * @property {string[]=} domains - list of domains for Route53 aliases (CloudFront app edge) and ACM coverage
7
7
  * @property {object} dnsRecords - map of dns records by root domain, only supports MX records so that we can add dns records for our email service
8
- * @property {Array<HttpServiceConfig|TcpServiceConfig>=} services - per-platform container services; each entry generates a systemd unit. HTTP services also get a Caddy reverse-proxy block. TCP/UDP services get direct port exposure and security group rules instead.
8
+ * @property {Array<HttpServiceConfig|TcpServiceConfig>=} services - per-platform container services. HTTP entries become ECS Fargate services + ALB host rules. TCP/UDP entries are ignored by the ECS path (not deployed).
9
+ * @property {Record<string,string>=} env - environment variables upserted into per-service Secrets Manager secrets via `npm run sync-secrets`. Never uploaded to S3. Values stay in git for now (#555).
10
+ * @property {string} awsAccountId - Aws account id
11
+ * @property {string=} awsRegion - AWS region, defaults to eu-north-1
12
+ * @property {string=} awsKeyPairName - unused after EC2 decommission (#560); ignored by CDK
13
+ * @property {string=} awsInstanceClass - unused after EC2 decommission (#560); ignored by CDK
14
+ * @property {string=} awsInstanceSize - unused after EC2 decommission (#560); ignored by CDK
15
+ * @property {string[]=} githubDeployRepos - `owner/repo` list trusted by the GitHub OIDC deploy role (#559); required for platform-ci, set in platforms.json
16
+ * @property {string=} ciDockerNetworkName - ?
9
17
  *
10
18
  * @typedef {Object} HttpServiceConfig
11
19
  * @property {'http'} [type] - defaults to 'http' when omitted
12
- * @property {string} name - unique service name (container + systemd unit)
13
- * @property {string} domain - hostname routed by Caddy to this container
20
+ * @property {string} name - unique service name (ECS service key derived by stripping leading `ossy-`)
21
+ * @property {string} domain - primary hostname (default ALB host when `hosts` omitted)
22
+ * @property {string[]=} hosts - ALB host-header values (#557); defaults to `[domain]`
14
23
  * @property {string} image - Docker image (e.g. ghcr.io/ossy-se/website-ossy:latest)
15
24
  *
16
25
  * @typedef {Object} TcpServiceConfig
17
- * @property {'tcp'} type - marks a raw socket service; bypasses Caddy entirely
18
- * @property {string} name - unique service name (container + systemd unit)
26
+ * @property {'tcp'} type - raw socket service; not deployed on the ECS path (#560)
27
+ * @property {string} name - unique service name
19
28
  * @property {string} image - Docker image (e.g. itzg/minecraft-server)
20
- * @property {number[]} ports - ports to open on the host and in the EC2 security group
29
+ * @property {number[]} ports - ports (unused by current CDK stacks)
21
30
  * @property {'tcp'|'udp'|'both'} [protocol] - defaults to 'tcp'
22
- * @property {Record<string,string>=} env - environment variables written to /etc/environment on the EC2 host at boot time; never uploaded to S3
23
- *
24
- * @property {string} awsAccountId - Aws account id
25
- * @property {string=} awsRegion - AWS region, defaults to eu-north-1
26
- * @property {string=} awsKeyPairName - EC2 key pair name for SSH access
27
- * @property {string=} awsInstanceClass - EC2 instance class (e.g. t3, t4g, m5), defaults to t3
28
- * @property {string=} awsInstanceSize - EC2 instance size (e.g. small, medium, large), defaults to small
29
- *
30
- * @property {string=} ciDockerNetworkName - ?
31
31
  */
32
32
 
33
33
  const SupportedRegions = {