@ossy/deployment-tools 3.0.8 → 3.1.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.
- package/CHANGELOG.md +18 -0
- package/README.md +160 -113
- package/jest.config.js +5 -0
- package/package.json +6 -4
- package/src/config/platform-config.js +16 -16
- package/src/ecs/platform-ecs-services.js +111 -0
- package/src/ecs/platform-ecs-services.spec.js +77 -0
- package/src/edge/platform-edge-domains.js +101 -0
- package/src/edge/platform-edge-domains.spec.js +87 -0
- package/src/index.js +33 -1
- package/src/infrastructure/dns-stack.js +47 -3
- package/src/infrastructure/dns-stack.spec.js +15 -0
- package/src/infrastructure/platform-ci-stack.js +151 -0
- package/src/infrastructure/platform-ecs-stack.js +326 -0
- package/src/infrastructure/platform-edge-stack.js +213 -0
- package/src/infrastructure/platform-secrets-stack.js +103 -0
- package/src/infrastructure/platform-stage.js +32 -5
- package/src/infrastructure/storage-static-stack.js +4 -5
- package/src/secrets/platform-secret-services.js +32 -0
- package/src/secrets/platform-secret-services.spec.js +40 -0
- package/src/secrets/sync-platform-secrets.js +136 -0
- package/src/services/platform-services.js +94 -0
- package/src/services/platform-services.spec.js +80 -0
- package/src/template/platform-template.js +5 -5
- package/src/infrastructure/container-deployment-target/aws-profile.js +0 -25
- package/src/infrastructure/container-deployment-target/caddy.service.js +0 -117
- package/src/infrastructure/container-deployment-target/container-deployment-target.js +0 -222
- package/src/infrastructure/container-deployment-target/container-service.js +0 -162
- package/src/infrastructure/container-deployment-target/index.js +0 -3
- package/src/infrastructure/container-deployment-target/ossy-api.service.js +0 -54
- package/src/infrastructure/container-deployment-target/ossy-runtime.service.js +0 -49
- package/src/infrastructure/container-deployment-target/user-data-commands.js +0 -31
- package/src/infrastructure/deployment-target-stack.js +0 -53
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
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.1.0](https://github.com/ossy-se/ossy/compare/v3.0.9...v3.1.0) (2026-08-12)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **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))
|
|
11
|
+
* **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)
|
|
12
|
+
* **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)
|
|
13
|
+
* **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)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## 3.0.9 (2026-08-11)
|
|
17
|
+
|
|
18
|
+
**Note:** Version bump only for package @ossy/deployment-tools
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
## 3.0.8 (2026-08-07)
|
|
7
25
|
|
|
8
26
|
**Note:** Version bump only for package @ossy/deployment-tools
|
package/README.md
CHANGED
|
@@ -1,34 +1,39 @@
|
|
|
1
1
|
# @ossy/deployment-tools
|
|
2
2
|
|
|
3
|
-
Infrastructure and CDK tooling for the Ossy platform
|
|
3
|
+
Infrastructure and CDK tooling for the Ossy platform.
|
|
4
4
|
|
|
5
|
-
|
|
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 →
|
|
11
|
-
├──
|
|
12
|
-
|
|
13
|
-
├──
|
|
14
|
-
└──
|
|
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.se → platform-runtime Fargate service
|
|
14
|
+
└── default action → platform-runtime
|
|
16
15
|
```
|
|
17
16
|
|
|
18
|
-
|
|
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 target group.
|
|
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.
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
Customer nameserver / custom-domain handoff at a registrar is **out of scope** (follow-up).
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
## Health checks (ALB / ECS)
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|---|---|---|---|
|
|
26
|
-
| Caddy | `caddy-route53.service` | 80, 443 | — |
|
|
27
|
-
| Ossy API | `ossy-api.service` | 3001 | `ghcr.io/ossy-se/api:latest` |
|
|
28
|
-
| Ossy Platform | `ossy-runtime.service` | 3000 | `ghcr.io/ossy-se/platform:latest` |
|
|
29
|
-
| _configured via `services`_ | `<name>.service` | _per config_ | _per config_ |
|
|
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.
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
| Image | How `/health` is provided |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `ghcr.io/ossy-se/platform` (runtime) | Built into `@ossy/platform` `startRuntime` — does not load a CMS site |
|
|
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) |
|
|
35
|
+
|
|
36
|
+
Use path `/health` for ALB target-group and ECS container health checks. Do not point probes at `/`, `/status`, or authenticated routes.
|
|
32
37
|
|
|
33
38
|
## Configuration — `platforms.json`
|
|
34
39
|
|
|
@@ -38,101 +43,179 @@ Additional services are generated automatically from the `services` array in `pl
|
|
|
38
43
|
|---|---|---|
|
|
39
44
|
| `platformName` | Yes | Unique name, used as the CDK stage name and CloudFormation stack prefix |
|
|
40
45
|
| `awsAccountId` | Yes | AWS account ID |
|
|
41
|
-
| `
|
|
42
|
-
| `awsInstanceClass` | No | EC2 instance class (e.g. `t3`, `t4g`, `m5`). Defaults to `t3` |
|
|
43
|
-
| `awsInstanceSize` | No | EC2 instance size (e.g. `small`, `medium`, `large`). Defaults to `small` |
|
|
46
|
+
| `awsRegion` | No | Defaults to `eu-north-1` |
|
|
44
47
|
| `sesDomains` | No | Domains to configure SES email identity for |
|
|
45
|
-
| `domains` | No | Domains
|
|
46
|
-
| `services` | No | Per-platform container services (see below) |
|
|
48
|
+
| `domains` | No | Domains for Route53 aliases → CloudFront and ACM coverage. Supports wildcards (e.g. `*.ossy.se`). |
|
|
49
|
+
| `services` | No | Per-platform HTTP container services (see below) |
|
|
47
50
|
| `dnsRecords` | No | Additional DNS records by root domain (currently supports MX) |
|
|
48
|
-
| `env` | No |
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
| `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. |
|
|
52
|
+
| `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`). |
|
|
53
|
+
| `awsKeyPairName` / `awsInstanceClass` / `awsInstanceSize` | No | **Unused** after EC2 decommission (#560). Harmless if left in JSON. |
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
### `services` — HTTP containers on ECS
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Generates a Caddy reverse-proxy block with Route53 TLS. The container must listen on port 3000.
|
|
57
|
+
HTTP services (default) become Fargate services with ALB host rules. The container must listen on port 3000.
|
|
57
58
|
|
|
58
59
|
```json
|
|
59
60
|
"services": [
|
|
60
61
|
{
|
|
61
62
|
"name": "ossy-website-ossy",
|
|
62
63
|
"domain": "ossy.se",
|
|
63
|
-
"image": "ghcr.io/ossy-se/website-ossy:latest"
|
|
64
|
+
"image": "ghcr.io/ossy-se/website-ossy:latest",
|
|
65
|
+
"hosts": ["ossy.se", "api.ossy.se"]
|
|
64
66
|
}
|
|
65
67
|
]
|
|
66
68
|
```
|
|
67
69
|
|
|
68
70
|
| Field | Description |
|
|
69
71
|
|---|---|
|
|
70
|
-
| `name` | Unique service name —
|
|
71
|
-
| `domain` |
|
|
72
|
-
| `
|
|
72
|
+
| `name` | Unique service name — Secrets Manager / ECS key strips a leading `ossy-` (`website-ossy`). |
|
|
73
|
+
| `domain` | Primary hostname (default ALB host when `hosts` is omitted). |
|
|
74
|
+
| `hosts` | Optional ALB host-header list. Example: `["ossy.se","api.ossy.se"]` so the website image serves apex and API host. Defaults to `[domain]`. |
|
|
75
|
+
| `image` | Docker image to pull and run (GHCR). |
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
**Built-in (always present):**
|
|
78
|
+
- `runtime` — platform runtime (`ghcr.io/ossy-se/platform:latest`) serving CMS-published apps; ALB default + unclaimed `domains`.
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
**Not deployed:** separate `ossy-api` container; TCP/UDP `services[]` entries (ignored by the ECS path).
|
|
77
81
|
|
|
78
|
-
|
|
82
|
+
**Wildcard subdomains:** Add `*.ossy.se` to `domains` for a Route53 wildcard alias to CloudFront.
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
`MEDIA_REPOSITORY` and `MEDIA_CDN_DOMAIN_NAME` are derived from the provisioned S3 bucket and media CloudFront — do not set them in `env`.
|
|
85
|
+
|
|
86
|
+
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.
|
|
87
|
+
|
|
88
|
+
## Platform secrets (Secrets Manager)
|
|
89
|
+
|
|
90
|
+
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.
|
|
91
|
+
|
|
92
|
+
| Secret name | Source |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `{platform}/runtime` | Built-in platform-runtime |
|
|
95
|
+
| `{platform}/website-ossy` | HTTP entry in `services[]` named `ossy-website-ossy` (leading `ossy-` stripped) |
|
|
96
|
+
| `{platform}/…` | Other HTTP `services[]` entries |
|
|
97
|
+
|
|
98
|
+
**Not created:** `ossy-api`. TCP/UDP `services[]` entries are skipped.
|
|
99
|
+
|
|
100
|
+
Each secret has a matching IAM role `{platform}-{key}-task` assumed by `ecs-tasks.amazonaws.com` with read access to that secret only.
|
|
101
|
+
|
|
102
|
+
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.
|
|
103
|
+
|
|
104
|
+
### Sync values after CDK deploy
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
cd packages/deployment-tools
|
|
108
|
+
|
|
109
|
+
# Create / update secret resources + IAM (part of the stage)
|
|
110
|
+
npx cdk deploy 'ossybot/platform-secrets' --profile ossybot
|
|
111
|
+
|
|
112
|
+
# Upsert secret *values* from platforms.json (never prints secret contents)
|
|
113
|
+
npm run sync-secrets -- --profile ossybot
|
|
114
|
+
|
|
115
|
+
# Optional: one platform, or dry-run
|
|
116
|
+
npm run sync-secrets -- --profile ossybot --platform ossybot
|
|
117
|
+
npm run sync-secrets -- --dry-run
|
|
90
118
|
```
|
|
91
119
|
|
|
92
|
-
|
|
120
|
+
Do **not** remove secrets from `platforms.json` yet.
|
|
121
|
+
|
|
122
|
+
## ECS Fargate + ALB
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
cd packages/deployment-tools
|
|
126
|
+
|
|
127
|
+
# Secrets + IAM first, then sync values + set ghcr-pull
|
|
128
|
+
npx cdk deploy 'ossybot/platform-secrets' --profile ossybot
|
|
129
|
+
npm run sync-secrets -- --profile ossybot
|
|
130
|
+
# then put GHCR credentials into ossybot/ghcr-pull
|
|
131
|
+
|
|
132
|
+
# VPC, cluster, Fargate services, ALB host rules
|
|
133
|
+
npx cdk deploy 'ossybot/platform-ecs' --profile ossybot
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
| Resource | Notes |
|
|
93
137
|
|---|---|
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
| `
|
|
97
|
-
|
|
|
98
|
-
|
|
|
138
|
+
| VPC | Public + private subnets, 1 NAT gateway |
|
|
139
|
+
| ECS cluster | `{platform}-platform` |
|
|
140
|
+
| Fargate services | `runtime` (`ghcr.io/ossy-se/platform:latest`) + each HTTP `services[]` entry |
|
|
141
|
+
| ALB | Internet-facing HTTP :80; host rules; default → runtime; health check `/health` |
|
|
142
|
+
| Task env | Secrets Manager JSON keys from `platforms.json` `env`; `MEDIA_*` from storage stack |
|
|
99
143
|
|
|
100
|
-
|
|
101
|
-
- `ossy-api` — the Ossy API (`ghcr.io/ossy-se/api:latest`, port 3001)
|
|
102
|
-
- `ossy-runtime` — the platform runtime serving all CMS-published apps (`ghcr.io/ossy-se/platform:latest`, port 3000, catch-all)
|
|
144
|
+
## CloudFront + WAF + ACM
|
|
103
145
|
|
|
104
|
-
|
|
146
|
+
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.
|
|
105
147
|
|
|
106
|
-
|
|
148
|
+
```bash
|
|
149
|
+
# After platform-ecs exists (ALB DNS used as origin)
|
|
150
|
+
npx cdk deploy 'ossybot/platform-edge' --profile ossybot
|
|
107
151
|
|
|
108
|
-
|
|
152
|
+
# DNS aliases for known domains → CloudFront
|
|
153
|
+
npx cdk deploy 'ossybot/dns' --profile ossybot
|
|
154
|
+
```
|
|
109
155
|
|
|
110
|
-
|
|
156
|
+
| Piece | Detail |
|
|
157
|
+
|---|---|
|
|
158
|
+
| Origin | ALB DNS name from `platform-ecs` (`LoadBalancerDnsName`), HTTP :80 |
|
|
159
|
+
| Certificate | ACM in `us-east-1`, DNS-validated; covers `domains` + HTTP service hosts (apex + `*.ossy.se` etc.) |
|
|
160
|
+
| WAF | WAFv2 web ACL (`AWSManagedRulesCommonRuleSet` + `KnownBadInputs`) associated via `webAclId` |
|
|
161
|
+
| Cache | `CACHING_DISABLED` + `ALL_VIEWER` origin request policy (forwards `Host`) |
|
|
162
|
+
| DNS | Route53 alias A/AAAA for known domains → CloudFront |
|
|
163
|
+
|
|
164
|
+
## Release to ECS from GitHub Actions
|
|
165
|
+
|
|
166
|
+
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.
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# After platform-ecs exists — creates OIDC provider + deploy role
|
|
170
|
+
npx cdk deploy 'ossybot/platform-ci' --profile ossybot
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
| Piece | Detail |
|
|
174
|
+
|---|---|
|
|
175
|
+
| Role | `{platform}-github-deploy` (e.g. `ossybot-github-deploy`) |
|
|
176
|
+
| Trust | OIDC `token.actions.githubusercontent.com`, `sub` = `repo:<owner>/<repo>:ref:refs/heads/main` for each entry in `platforms.json` → `githubDeployRepos` |
|
|
177
|
+
| Cluster / services | `{platform}-platform` / `{platform}-{serviceKey}` (e.g. `ossybot-runtime`, `ossybot-website-ossy`) |
|
|
178
|
+
| Image tags | `ghcr.io/ossy-se/<image>:latest` and `:<git-sha>` |
|
|
179
|
+
| Workflows | each trusted repo’s Publish workflow rolls its ECS service |
|
|
180
|
+
|
|
181
|
+
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.
|
|
111
182
|
|
|
112
183
|
## CDK Stacks
|
|
113
184
|
|
|
114
|
-
Each platform is a CDK `Stage` containing
|
|
185
|
+
Each platform is a CDK `Stage` containing these stacks:
|
|
115
186
|
|
|
116
187
|
| Stack | Description |
|
|
117
188
|
|---|---|
|
|
118
|
-
| `storage-static` | S3 bucket (auto-named), daily backup plan, CloudFront CDN for `/media`.
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
189
|
+
| `storage-static` | S3 bucket (auto-named), daily backup plan, CloudFront CDN for `/media`. |
|
|
190
|
+
| `platform-secrets` | Secrets Manager secrets per service + ECS task roles (read IAM) + `ghcr-pull`. Values synced from `platforms.json` via `npm run sync-secrets`. |
|
|
191
|
+
| `platform-ecs` | VPC, ECS Fargate services, ALB with host-based routing. |
|
|
192
|
+
| `platform-ci` | GitHub OIDC provider + `{platform}-github-deploy` IAM role for ECS rollouts from Actions. |
|
|
193
|
+
| `platform-edge` | CloudFront + WAF + ACM in `us-east-1` in front of the ALB. |
|
|
194
|
+
| `dns` | Route53 alias records for known domains → CloudFront app distribution. |
|
|
121
195
|
| `ses` | SES email sending identity and DKIM records. |
|
|
122
196
|
|
|
123
|
-
|
|
197
|
+
### Cutover / decommission EC2 (#560)
|
|
198
|
+
|
|
199
|
+
CDK no longer synthesizes `deployment-target` (EC2 + EIP + Caddy + systemd units). After `platform-edge` and `dns` are live and traffic is healthy:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Destroy the leftover CloudFormation stack (name may be stage-prefixed)
|
|
203
|
+
npx cdk destroy 'ossybot/deployment-target' --profile ossybot
|
|
204
|
+
# or, if the stack still exists outside the app:
|
|
205
|
+
# aws cloudformation delete-stack --stack-name ossybot-deployment-target --profile ossybot
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Downtime during cutover is acceptable. Confirm known domains resolve to CloudFront before destroying EC2.
|
|
124
209
|
|
|
125
210
|
## Bootstrapping a new platform
|
|
126
211
|
|
|
127
212
|
```bash
|
|
128
213
|
cd packages/deployment-tools
|
|
129
214
|
npx cdk deploy 'ossybot/**' --profile ossybot --require-approval never
|
|
215
|
+
npm run sync-secrets -- --profile ossybot
|
|
216
|
+
# set {platform}/ghcr-pull credentials once
|
|
130
217
|
```
|
|
131
218
|
|
|
132
|
-
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.
|
|
133
|
-
|
|
134
|
-
> 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.
|
|
135
|
-
|
|
136
219
|
## Deploying changes
|
|
137
220
|
|
|
138
221
|
Target a single platform:
|
|
@@ -157,32 +240,18 @@ npx cdk list
|
|
|
157
240
|
|
|
158
241
|
## Redeploying after a code change
|
|
159
242
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
```bash
|
|
163
|
-
# API — push to main triggers CI → ghcr.io/ossy-se/api:latest
|
|
164
|
-
sudo systemctl restart ossy-api
|
|
165
|
-
|
|
166
|
-
# Platform runtime — merge to main in ossy monorepo → ghcr.io/ossy-se/platform:latest
|
|
167
|
-
sudo systemctl restart ossy-runtime
|
|
168
|
-
|
|
169
|
-
# website-ossy — push to main in website-ossy repo → ghcr.io/ossy-se/website-ossy:latest
|
|
170
|
-
sudo systemctl restart ossy-website-ossy
|
|
171
|
-
|
|
172
|
-
# website-plexus-sanitas — push to main → ghcr.io/ossy-se/website-plexus-sanitas:latest
|
|
173
|
-
sudo systemctl restart ossy-website-plexus-sanitas
|
|
174
|
-
```
|
|
243
|
+
Merge to `main` — Publish workflows push GHCR tags (`:latest` + `:<sha>`) and roll the matching ECS service. No SSH / `systemctl`.
|
|
175
244
|
|
|
176
245
|
## Environment variables
|
|
177
246
|
|
|
178
|
-
Defined in the `env` block of `platforms.json`,
|
|
247
|
+
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:
|
|
179
248
|
|
|
180
249
|
| Variable | Source |
|
|
181
250
|
|---|---|
|
|
182
251
|
| `MEDIA_REPOSITORY` | S3 bucket name from `storage-static` stack |
|
|
183
252
|
| `MEDIA_CDN_DOMAIN_NAME` | CloudFront domain from `storage-static` stack |
|
|
184
253
|
|
|
185
|
-
|
|
254
|
+
Other common env vars:
|
|
186
255
|
|
|
187
256
|
| Variable | Used by | Description |
|
|
188
257
|
|---|---|---|
|
|
@@ -198,32 +267,10 @@ All other env vars:
|
|
|
198
267
|
|
|
199
268
|
## Useful commands
|
|
200
269
|
|
|
201
|
-
**SSH into instance**
|
|
202
|
-
```bash
|
|
203
|
-
ssh -i path/to/keys ubuntu@<elastic-ip>
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
**View service logs**
|
|
207
|
-
```bash
|
|
208
|
-
journalctl -u ossy-runtime.service -n 200 -f
|
|
209
|
-
journalctl -u ossy-api.service -n 200 -f
|
|
210
|
-
journalctl -u ossy-website-ossy.service -n 200 -f
|
|
211
|
-
journalctl -u caddy-route53.service -n 200 -f
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
**View running containers**
|
|
215
270
|
```bash
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
docker logs ossy-runtime
|
|
222
|
-
docker logs ossy-api
|
|
223
|
-
docker logs ossy-website-ossy
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
**Reload Caddy config**
|
|
227
|
-
```bash
|
|
228
|
-
sudo systemctl reload caddy-route53.service
|
|
271
|
+
cd packages/deployment-tools
|
|
272
|
+
npm test
|
|
273
|
+
npx cdk list
|
|
274
|
+
npx cdk diff 'ossybot/**' --profile ossybot
|
|
275
|
+
npm run sync-secrets -- --profile ossybot --dry-run
|
|
229
276
|
```
|
package/jest.config.js
ADDED
package/package.json
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/deployment-tools",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.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": "
|
|
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": "
|
|
27
|
+
"gitHead": "9b146fb6fd9769f800863806c5bd16016508b218"
|
|
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
|
|
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
|
|
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 (
|
|
13
|
-
* @property {string} domain - hostname
|
|
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 -
|
|
18
|
-
* @property {string} name - unique service name
|
|
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
|
|
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 = {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ECS/Fargate service targets derived from platforms.json.
|
|
3
|
+
*
|
|
4
|
+
* Service set and naming come from shared `platform-services`. No separate
|
|
5
|
+
* `ossy-api` — API lives in website images. TCP/UDP entries are skipped (not
|
|
6
|
+
* part of the ECS path after #560).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
listHttpServiceEntries,
|
|
11
|
+
listPlatformServices,
|
|
12
|
+
} = require('../services/platform-services')
|
|
13
|
+
|
|
14
|
+
const RUNTIME_IMAGE = 'ghcr.io/ossy-se/platform:latest'
|
|
15
|
+
const CONTAINER_PORT = 3000
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} PlatformEcsService
|
|
19
|
+
* @property {string} key - short key (`runtime`, `website-ossy`, …) — matches Secrets Manager
|
|
20
|
+
* @property {string} secretName - `{platform}/{key}`
|
|
21
|
+
* @property {string} source - origin label (`platform-runtime` or `services[].name`)
|
|
22
|
+
* @property {string} image - container image (GHCR)
|
|
23
|
+
* @property {string[]} hosts - ALB host-header values for this service (empty = default/catch-all)
|
|
24
|
+
* @property {boolean} isDefault - when true, ALB default action forwards here (runtime)
|
|
25
|
+
* @property {number} containerPort - container listen port
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Host headers for an HTTP `services[]` entry.
|
|
30
|
+
* Uses optional `hosts` when set; otherwise `[domain]`.
|
|
31
|
+
*
|
|
32
|
+
* @param {{ domain?: string, hosts?: string[] }} entry
|
|
33
|
+
* @returns {string[]}
|
|
34
|
+
*/
|
|
35
|
+
function hostsFromServiceEntry(entry) {
|
|
36
|
+
if (Array.isArray(entry.hosts) && entry.hosts.length > 0) {
|
|
37
|
+
return entry.hosts.filter(Boolean)
|
|
38
|
+
}
|
|
39
|
+
if (entry.domain) return [entry.domain]
|
|
40
|
+
return []
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Hosts routed explicitly to runtime (platform domains not claimed by an HTTP service).
|
|
45
|
+
* Wildcards like `*.ossy.se` are included so CMS apps keep working behind the ALB.
|
|
46
|
+
*
|
|
47
|
+
* @param {{ domains?: string[] }} config
|
|
48
|
+
* @param {Array<{ domain?: string, hosts?: string[] }>} httpEntries
|
|
49
|
+
* @returns {string[]}
|
|
50
|
+
*/
|
|
51
|
+
function runtimeHosts(config, httpEntries) {
|
|
52
|
+
const claimed = new Set(httpEntries.flatMap(hostsFromServiceEntry))
|
|
53
|
+
return (config.domains || []).filter(domain => !claimed.has(domain))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* List Fargate services for a platform (runtime + HTTP services[]).
|
|
58
|
+
*
|
|
59
|
+
* @param {{
|
|
60
|
+
* platformName: string,
|
|
61
|
+
* domains?: string[],
|
|
62
|
+
* services?: Array<{ name: string, type?: string, domain?: string, hosts?: string[], image?: string }>
|
|
63
|
+
* }} config
|
|
64
|
+
* @returns {PlatformEcsService[]}
|
|
65
|
+
*/
|
|
66
|
+
function listPlatformEcsServices(config) {
|
|
67
|
+
if (!config?.platformName) {
|
|
68
|
+
throw new Error('[platform-ecs-services] platformName is required')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const httpEntries = listHttpServiceEntries(config)
|
|
72
|
+
const runtimeHostList = runtimeHosts(config, httpEntries)
|
|
73
|
+
|
|
74
|
+
return listPlatformServices(config).map(service => {
|
|
75
|
+
if (service.key === 'runtime') {
|
|
76
|
+
return {
|
|
77
|
+
key: service.key,
|
|
78
|
+
secretName: service.secretName,
|
|
79
|
+
source: service.source,
|
|
80
|
+
image: RUNTIME_IMAGE,
|
|
81
|
+
hosts: runtimeHostList,
|
|
82
|
+
isDefault: true,
|
|
83
|
+
containerPort: CONTAINER_PORT,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!service.entry?.image) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`[platform-ecs-services] HTTP service "${service.key}" is missing image`
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
key: service.key,
|
|
95
|
+
secretName: service.secretName,
|
|
96
|
+
source: service.source,
|
|
97
|
+
image: service.entry.image,
|
|
98
|
+
hosts: hostsFromServiceEntry(service.entry),
|
|
99
|
+
isDefault: false,
|
|
100
|
+
containerPort: CONTAINER_PORT,
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
RUNTIME_IMAGE,
|
|
107
|
+
CONTAINER_PORT,
|
|
108
|
+
hostsFromServiceEntry,
|
|
109
|
+
listPlatformEcsServices,
|
|
110
|
+
runtimeHosts,
|
|
111
|
+
}
|