@ossy/deployment-tools 1.19.0 → 1.20.1
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 +22 -0
- package/README.md +129 -34
- package/package.json +2 -6
- package/src/config/platform-config.js +18 -4
- package/src/infrastructure/cli.js +2 -18
- package/src/infrastructure/container-deployment-target/caddy.service.js +22 -29
- package/src/infrastructure/container-deployment-target/container-deployment-target.js +56 -17
- package/src/infrastructure/container-deployment-target/container-service.js +163 -0
- package/src/infrastructure/deployment-target-stack.js +29 -76
- package/src/infrastructure/platform-stage.js +40 -0
- package/src/infrastructure/storage-static-stack.js +87 -0
- package/src/template/platform-template.js +0 -3
- package/src/aws-credentials/aws-credentials.js +0 -87
- package/src/aws-credentials/cli.js +0 -55
- package/src/aws-credentials/index.js +0 -1
- package/src/index.cli.js +0 -11
- package/src/infrastructure/container-deployment-target/ossy-website-ossy.service.js +0 -52
- package/src/infrastructure/container-deployment-target/ossy-website-plexus-sanitas.service.js +0 -52
- package/src/infrastructure/trust-ci-stack.js +0 -76
- package/src/template/cli.js +0 -24
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
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
|
+
## 1.20.1 (2026-05-26)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **deployment-tools:** Update Caddy service setup and Ubuntu AMI version ([6b3f4f7](https://github.com/ossy-se/ossy/commit/6b3f4f755901450be744d4402ec1f314418d101e))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# 1.20.0 (2026-05-25)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **deployment-tools:** Refactor container service management and update documentation ([8e2bc9c](https://github.com/ossy-se/ossy/commit/8e2bc9c186dbc9a16ae6127fad755baffc07d0be))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
# 1.19.0 (2026-05-25)
|
|
7
29
|
|
|
8
30
|
|
package/README.md
CHANGED
|
@@ -21,38 +21,139 @@ Caddy uses **on-demand TLS**: before issuing a certificate for any domain, it ca
|
|
|
21
21
|
|
|
22
22
|
All services run as Docker containers managed by systemd. Service files are written to the EC2 host by CDK user data at instance creation.
|
|
23
23
|
|
|
24
|
-
| Service | Unit file | Port |
|
|
25
|
-
|
|
26
|
-
| Caddy | `caddy-route53.service` | 80, 443 |
|
|
27
|
-
| Ossy API | `ossy-api.service` | 3001 |
|
|
28
|
-
| Ossy Platform | `ossy-runtime.service` | 3000 |
|
|
29
|
-
|
|
|
30
|
-
| website-plexus-sanitas | `ossy-website-plexus-sanitas.service` | 3003 | Yes | `ghcr.io/ossy-se/website-plexus-sanitas:latest` |
|
|
24
|
+
| Service | Unit file | Port | Image |
|
|
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_ |
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
> **TODO:** Pin `@ossy/platform` to a specific version at CDK deploy time (thread through `PlatformConfig`) rather than always pulling `@latest`.
|
|
31
|
+
Additional services are generated automatically from the `services` array in `platforms.json` — see the Configuration section below.
|
|
35
32
|
|
|
36
33
|
## Configuration — `platforms.json`
|
|
37
34
|
|
|
38
|
-
`packages/infrastructure/platforms.json` is the single source of truth for a platform.
|
|
35
|
+
`packages/infrastructure/platforms.json` is the single source of truth for a platform. Each entry becomes a CDK `Stage` with its own set of stacks.
|
|
36
|
+
|
|
37
|
+
| Field | Required | Description |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `platformName` | Yes | Unique name, used as the CDK stage name and CloudFormation stack prefix |
|
|
40
|
+
| `awsAccountId` | Yes | AWS account ID |
|
|
41
|
+
| `awsKeyPairName` | Yes | EC2 key pair name for SSH access |
|
|
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` |
|
|
44
|
+
| `sesDomains` | No | Domains to configure SES email identity for |
|
|
45
|
+
| `domains` | No | Domains to create Route53 A records for, pointing to the EC2 Elastic IP. Supports wildcards (e.g. `*.ossy.se`). |
|
|
46
|
+
| `services` | No | Per-platform container services (see below) |
|
|
47
|
+
| `dnsRecords` | No | Additional DNS records by root domain (currently supports MX) |
|
|
48
|
+
| `env` | No | Environment variables written to `/etc/environment` on the EC2 host at boot. **Never uploaded to S3.** |
|
|
49
|
+
|
|
50
|
+
### `services` — container/domain mapping
|
|
51
|
+
|
|
52
|
+
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.
|
|
53
|
+
|
|
54
|
+
#### HTTP service (default)
|
|
55
|
+
|
|
56
|
+
Generates a Caddy reverse-proxy block with Route53 TLS. The container must listen on port 3000.
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
"services": [
|
|
60
|
+
{
|
|
61
|
+
"name": "ossy-website-ossy",
|
|
62
|
+
"domain": "ossy.se",
|
|
63
|
+
"image": "ghcr.io/ossy-se/website-ossy:latest"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Field | Description |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `name` | Unique service name — Docker container name and systemd unit (`<name>.service`). |
|
|
71
|
+
| `domain` | Hostname routed by Caddy to this container (Route53 TLS via `dns route53`). |
|
|
72
|
+
| `image` | Docker image to pull and run. |
|
|
73
|
+
|
|
74
|
+
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.
|
|
75
|
+
|
|
76
|
+
#### TCP/UDP service
|
|
77
|
+
|
|
78
|
+
No Caddy block. Ports are mapped directly (`HOST:CONTAINER`) and opened in the EC2 security group. Use this for game servers or any raw socket protocol.
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
"services": [
|
|
82
|
+
{
|
|
83
|
+
"name": "minecraft",
|
|
84
|
+
"type": "tcp",
|
|
85
|
+
"image": "itzg/minecraft-server",
|
|
86
|
+
"ports": [25565],
|
|
87
|
+
"protocol": "tcp"
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
| Field | Description |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `name` | Unique service name — Docker container name and systemd unit. |
|
|
95
|
+
| `type` | Must be `"tcp"` to enable this mode. |
|
|
96
|
+
| `image` | Docker image to pull and run. |
|
|
97
|
+
| `ports` | Array of port numbers to expose on the host and open in the security group. |
|
|
98
|
+
| `protocol` | `"tcp"` (default), `"udp"`, or `"both"`. |
|
|
99
|
+
|
|
100
|
+
**Fixed built-in services** (always present, not configurable via `services`):
|
|
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)
|
|
39
103
|
|
|
40
|
-
-
|
|
41
|
-
- **`domains`** — list of domains to create Route53 A records for, all pointing to the EC2 host IP
|
|
42
|
-
- **`env`** — environment variables written to `/etc/environment` on the EC2 host at boot time. **These are never uploaded to S3.**
|
|
104
|
+
**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`.
|
|
43
105
|
|
|
44
|
-
|
|
106
|
+
**Wildcard subdomains**: Add `*.ossy.se` to `domains` for a Route53 wildcard A record. This covers any `*.ossy.se` subdomain without individual records.
|
|
45
107
|
|
|
46
|
-
|
|
108
|
+
`MEDIA_REPOSITORY` and `MEDIA_CDN_DOMAIN_NAME` are derived automatically from the provisioned S3 bucket and CloudFront distribution — do not set them in `env`.
|
|
47
109
|
|
|
48
|
-
|
|
110
|
+
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.
|
|
111
|
+
|
|
112
|
+
## CDK Stacks
|
|
113
|
+
|
|
114
|
+
Each platform is a CDK `Stage` containing five stacks:
|
|
115
|
+
|
|
116
|
+
| Stack | Description |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `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. |
|
|
119
|
+
| `deployment-target` | EC2 instance with Elastic IP. Imports the S3 bucket from `storage-static` via cross-stack reference. |
|
|
120
|
+
| `dns` | Route53 A records for all domains in `platforms.json`, pointing to the Elastic IP. |
|
|
121
|
+
| `ses` | SES email sending identity and DKIM records. |
|
|
122
|
+
|
|
123
|
+
The Elastic IP ensures the EC2 instance can be replaced (instance type changes, user data updates) without breaking DNS or CloudFormation cross-stack exports.
|
|
124
|
+
|
|
125
|
+
## Bootstrapping a new platform
|
|
49
126
|
|
|
50
127
|
```bash
|
|
51
128
|
cd packages/deployment-tools
|
|
52
|
-
npx cdk deploy ossybot
|
|
129
|
+
npx cdk deploy 'ossybot/**' --profile ossybot --require-approval never
|
|
53
130
|
```
|
|
54
131
|
|
|
55
|
-
|
|
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
|
+
## Deploying changes
|
|
137
|
+
|
|
138
|
+
Target a single platform:
|
|
139
|
+
```bash
|
|
140
|
+
npx cdk deploy 'ossybot/**' --profile ossybot
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Target all platforms:
|
|
144
|
+
```bash
|
|
145
|
+
npx cdk deploy '**' --profile ossybot
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Preview changes without deploying:
|
|
149
|
+
```bash
|
|
150
|
+
npx cdk diff 'ossybot/**' --profile ossybot
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
List all stacks:
|
|
154
|
+
```bash
|
|
155
|
+
npx cdk list
|
|
156
|
+
```
|
|
56
157
|
|
|
57
158
|
## Redeploying after a code change
|
|
58
159
|
|
|
@@ -74,15 +175,20 @@ sudo systemctl restart ossy-website-plexus-sanitas
|
|
|
74
175
|
|
|
75
176
|
## Environment variables
|
|
76
177
|
|
|
77
|
-
|
|
178
|
+
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:
|
|
179
|
+
|
|
180
|
+
| Variable | Source |
|
|
181
|
+
|---|---|
|
|
182
|
+
| `MEDIA_REPOSITORY` | S3 bucket name from `storage-static` stack |
|
|
183
|
+
| `MEDIA_CDN_DOMAIN_NAME` | CloudFront domain from `storage-static` stack |
|
|
184
|
+
|
|
185
|
+
All other env vars:
|
|
78
186
|
|
|
79
187
|
| Variable | Used by | Description |
|
|
80
188
|
|---|---|---|
|
|
81
189
|
| `DB_URL` | API | Atlas MongoDB connection string |
|
|
82
190
|
| `DB_NAME` | API | MongoDB database name |
|
|
83
191
|
| `TOKEN_SECRET` | API | JWT signing secret |
|
|
84
|
-
| `MEDIA_REPOSITORY` | API | S3 bucket name for media uploads |
|
|
85
|
-
| `MEDIA_CDN_DOMAIN_NAME` | API | CloudFront domain for public media |
|
|
86
192
|
| `AWS_ACCESS_KEY_ID` | API | AWS credentials for S3 |
|
|
87
193
|
| `AWS_SECRET_ACCESS_KEY` | API | AWS credentials for S3 |
|
|
88
194
|
| `OSSY_API_KEY` | Platform, websites | API JWT for CMS reads |
|
|
@@ -90,22 +196,11 @@ All env vars are defined in the `env` block of `platforms.json` and written to `
|
|
|
90
196
|
| `OPENAI_API_KEY` | Worker | OpenAI API key |
|
|
91
197
|
| `OPENAI_ORGANIZATION` | Worker | OpenAI organisation ID |
|
|
92
198
|
|
|
93
|
-
## Infrastructure (CDK)
|
|
94
|
-
|
|
95
|
-
CDK stacks are defined in `src/infrastructure/`. Run via `cdk deploy` from the `deployment-tools` package.
|
|
96
|
-
|
|
97
|
-
| Stack | Description |
|
|
98
|
-
|---|---|
|
|
99
|
-
| `trust-ci` | IAM role for CI to deploy infrastructure |
|
|
100
|
-
| `deployment-target` | EC2 host, S3 bucket, CloudFront CDN for media |
|
|
101
|
-
| `dns` | Route53 A records for all domains in `platforms.json` |
|
|
102
|
-
| `ses` | SES email sending |
|
|
103
|
-
|
|
104
199
|
## Useful commands
|
|
105
200
|
|
|
106
201
|
**SSH into instance**
|
|
107
202
|
```bash
|
|
108
|
-
ssh -i path/to/keys ubuntu@<ip>
|
|
203
|
+
ssh -i path/to/keys ubuntu@<elastic-ip>
|
|
109
204
|
```
|
|
110
205
|
|
|
111
206
|
**View service logs**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/deployment-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.1",
|
|
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",
|
|
@@ -12,11 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"author": "Ossy",
|
|
14
14
|
"license": "ISC",
|
|
15
|
-
"bin": "./src/index.cli.js",
|
|
16
15
|
"dependencies": {
|
|
17
|
-
"@actions/core": "^1.10.0",
|
|
18
|
-
"@aws-sdk/client-sts": "^3.245.0",
|
|
19
|
-
"arg": "^5.0.2",
|
|
20
16
|
"aws-cdk": "^2.73.0",
|
|
21
17
|
"aws-cdk-lib": "^2.73.0",
|
|
22
18
|
"constructs": "^10.1.304",
|
|
@@ -26,5 +22,5 @@
|
|
|
26
22
|
"devDependencies": {
|
|
27
23
|
"jest": "^27.5.1"
|
|
28
24
|
},
|
|
29
|
-
"gitHead": "
|
|
25
|
+
"gitHead": "b063e40ba5b374e04a5911a4fec92ec6b9014409"
|
|
30
26
|
}
|
|
@@ -5,15 +5,29 @@
|
|
|
5
5
|
* @property {string[]=} sesDomains - list of domains to configure AWS SES email identity and DKIM records for
|
|
6
6
|
* @property {string[]=} domains - list of domains to create Route53 A records for, pointing to the EC2 host
|
|
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.
|
|
9
|
+
*
|
|
10
|
+
* @typedef {Object} HttpServiceConfig
|
|
11
|
+
* @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
|
|
14
|
+
* @property {string} image - Docker image (e.g. ghcr.io/ossy-se/website-ossy:latest)
|
|
15
|
+
*
|
|
16
|
+
* @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)
|
|
19
|
+
* @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
|
|
21
|
+
* @property {'tcp'|'udp'|'both'} [protocol] - defaults to 'tcp'
|
|
8
22
|
* @property {Record<string,string>=} env - environment variables written to /etc/environment on the EC2 host at boot time; never uploaded to S3
|
|
9
23
|
*
|
|
10
24
|
* @property {string} awsAccountId - Aws account id
|
|
11
|
-
* @property {string=} awsRegion -
|
|
12
|
-
* @property {string=} awsKeyPairName -
|
|
13
|
-
* @property {string}
|
|
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
|
|
14
29
|
*
|
|
15
30
|
* @property {string=} ciDockerNetworkName - ?
|
|
16
|
-
* @property {string} ciGithubActionsRepo - organisation/repoName
|
|
17
31
|
*/
|
|
18
32
|
|
|
19
33
|
const SupportedRegions = {
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/* eslint-disable no-new */
|
|
3
3
|
const { App } = require('aws-cdk-lib')
|
|
4
|
-
const {
|
|
5
|
-
const { DeploymentTargetStack } = require('./deployment-target-stack')
|
|
6
|
-
const { DnsStack } = require('./dns-stack')
|
|
7
|
-
const { SesStack } = require('./ses-stack')
|
|
4
|
+
const { PlatformStage } = require('./platform-stage')
|
|
8
5
|
const { PlatformTemplateService } = require('../template')
|
|
9
6
|
const { PlatformConfigService } = require('../config')
|
|
10
7
|
|
|
@@ -15,25 +12,12 @@ PlatformTemplateService
|
|
|
15
12
|
const app = new App()
|
|
16
13
|
|
|
17
14
|
configs.forEach(config => {
|
|
18
|
-
|
|
19
|
-
const stackProps = {
|
|
15
|
+
new PlatformStage(app, config.platformName, {
|
|
20
16
|
config,
|
|
21
17
|
env: {
|
|
22
18
|
account: config.awsAccountId,
|
|
23
19
|
region: config.awsRegion
|
|
24
20
|
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
new TrustCiStack(app, `${config.platformName}-trust-ci`, stackProps)
|
|
28
|
-
|
|
29
|
-
const deploymentTarget = new DeploymentTargetStack(app, `${config.platformName}-deployment-target`, stackProps)
|
|
30
|
-
|
|
31
|
-
new DnsStack(app, `${config.platformName}-dns`, {
|
|
32
|
-
...stackProps,
|
|
33
|
-
containerDeploymentTargetPublicIp: deploymentTarget.containerDeploymentTargetPublicIp
|
|
34
21
|
})
|
|
35
|
-
|
|
36
|
-
new SesStack(app, `${config.platformName}-ses`, stackProps)
|
|
37
|
-
|
|
38
22
|
})
|
|
39
23
|
})
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Builds the full Caddyfile content.
|
|
3
|
+
* - Global block: on-demand TLS pointing at the API ask endpoint
|
|
4
|
+
* - API block: explicit Route53 TLS for api.ossy.se
|
|
5
|
+
* - One block per ContainerService (from platforms.json `services`)
|
|
6
|
+
* - Catch-all :443 block: on-demand TLS, routes to the platform runtime
|
|
7
|
+
*
|
|
8
|
+
* @param {import('./container-service').ContainerService[]} services
|
|
9
|
+
*/
|
|
10
|
+
const buildCaddyfile = (services) => {
|
|
11
|
+
const serviceBlocks = services.map(s => s.caddyBlock).join('\n')
|
|
12
|
+
|
|
13
|
+
return `
|
|
2
14
|
{
|
|
3
15
|
on_demand_tls {
|
|
4
16
|
ask http://localhost:3001/api/v0/apps/ask
|
|
@@ -14,32 +26,7 @@ api.ossy.se {
|
|
|
14
26
|
}
|
|
15
27
|
reverse_proxy localhost:3001
|
|
16
28
|
}
|
|
17
|
-
|
|
18
|
-
# TODO: remove this block once ossy.se traffic flows through the platform runtime.
|
|
19
|
-
# Once ossy app publish is validated for ossy.se, delete this block and the
|
|
20
|
-
# ossy-website-ossy.service so the catch-all below serves it via the platform.
|
|
21
|
-
ossy.se {
|
|
22
|
-
tls {
|
|
23
|
-
dns route53 {
|
|
24
|
-
max_retries 10
|
|
25
|
-
profile ci-client
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
reverse_proxy localhost:3002
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
# TODO: remove this block once www.plexus-sanitas.com traffic flows through the platform runtime.
|
|
32
|
-
# Once ossy app publish is validated for plexus-sanitas, delete this block and the
|
|
33
|
-
# ossy-website-plexus-sanitas.service so the catch-all below serves it via the platform.
|
|
34
|
-
www.plexus-sanitas.com {
|
|
35
|
-
tls {
|
|
36
|
-
dns route53 {
|
|
37
|
-
max_retries 10
|
|
38
|
-
profile ci-client
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
reverse_proxy localhost:3003
|
|
42
|
-
}
|
|
29
|
+
${serviceBlocks}
|
|
43
30
|
|
|
44
31
|
:443 {
|
|
45
32
|
tls {
|
|
@@ -52,6 +39,7 @@ www.plexus-sanitas.com {
|
|
|
52
39
|
reverse_proxy localhost:3000
|
|
53
40
|
}
|
|
54
41
|
`
|
|
42
|
+
}
|
|
55
43
|
|
|
56
44
|
const systemdServiceFile = `
|
|
57
45
|
# caddy-route53.service — Caddy with Route53 DNS plugin for on-demand TLS.
|
|
@@ -84,14 +72,19 @@ WantedBy=multi-user.target cloud-init.target
|
|
|
84
72
|
*/
|
|
85
73
|
class CaddyService {
|
|
86
74
|
|
|
87
|
-
|
|
75
|
+
/**
|
|
76
|
+
* @param {import('./container-service').ContainerService[]} services
|
|
77
|
+
*/
|
|
78
|
+
static install(services = []) {
|
|
79
|
+
const caddyfile = buildCaddyfile(services)
|
|
88
80
|
return [
|
|
89
81
|
// Write Caddyfile
|
|
90
82
|
'sudo mkdir -p /etc/caddy',
|
|
91
83
|
`sudo tee /etc/caddy/Caddyfile > /dev/null << 'CADDYEOF'\n${caddyfile}\nCADDYEOF`,
|
|
92
84
|
// Write systemd unit
|
|
93
85
|
`sudo tee /etc/systemd/system/caddy-route53.service > /dev/null << 'UNITEOF'\n${systemdServiceFile}\nUNITEOF`,
|
|
94
|
-
//
|
|
86
|
+
// Create caddy system user (apt install caddy may not run on all Ubuntu versions)
|
|
87
|
+
'sudo useradd --system --home /var/lib/caddy --shell /usr/sbin/nologin --create-home caddy || true',
|
|
95
88
|
'sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https',
|
|
96
89
|
'sudo curl -1sLf \'https://dl.cloudsmith.io/public/caddy/stable/gpg.key\' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg',
|
|
97
90
|
'sudo curl -1sLf \'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt\' | sudo tee /etc/apt/sources.list.d/caddy-stable.list',
|
|
@@ -11,7 +11,9 @@ const {
|
|
|
11
11
|
Port,
|
|
12
12
|
UserData,
|
|
13
13
|
KeyPair,
|
|
14
|
-
BlockDeviceVolume
|
|
14
|
+
BlockDeviceVolume,
|
|
15
|
+
CfnEIP,
|
|
16
|
+
CfnEIPAssociation
|
|
15
17
|
} = require('aws-cdk-lib/aws-ec2')
|
|
16
18
|
const { Role, ServicePrincipal, Policy, PolicyStatement, Effect } = require('aws-cdk-lib/aws-iam')
|
|
17
19
|
const { Source, BucketDeployment } = require('aws-cdk-lib/aws-s3-deployment')
|
|
@@ -19,8 +21,7 @@ const { getInstallNodeJs, getInstallNpm, getInstallDocker } = require('./user-da
|
|
|
19
21
|
const { CaddyService } = require('./caddy.service')
|
|
20
22
|
const { OssyRuntimeService } = require('./ossy-runtime.service')
|
|
21
23
|
const { OssyApiService } = require('./ossy-api.service')
|
|
22
|
-
const {
|
|
23
|
-
const { OssyWebsitePlexusSanitasService } = require('./ossy-website-plexus-sanitas.service')
|
|
24
|
+
const { fromConfig: buildContainerServices } = require('./container-service')
|
|
24
25
|
const { AwsProfile } = require('./aws-profile')
|
|
25
26
|
const { SupportedRegions } = require('../../config')
|
|
26
27
|
|
|
@@ -33,7 +34,7 @@ const { SupportedRegions } = require('../../config')
|
|
|
33
34
|
*/
|
|
34
35
|
|
|
35
36
|
const InstanceImages = {
|
|
36
|
-
UBUNTU: 'ami-
|
|
37
|
+
UBUNTU: 'ami-067bcf851477ebb78' // Ubuntu Server 24.04 LTS (HVM), eu-north-1
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
@@ -70,8 +71,18 @@ class ContainerDeploymentTarget extends Construct {
|
|
|
70
71
|
'allow HTTPS traffic from anywhere'
|
|
71
72
|
)
|
|
72
73
|
|
|
74
|
+
// Build container services from config — opens additional security group ports for TCP/UDP services
|
|
75
|
+
const containerServices = buildContainerServices(props.config.services)
|
|
76
|
+
|
|
77
|
+
containerServices.forEach(service => {
|
|
78
|
+
service.securityGroupPorts.forEach(({ port, protocol }) => {
|
|
79
|
+
const portRule = protocol === 'udp' ? Port.udp(port) : Port.tcp(port)
|
|
80
|
+
securityGroup.addIngressRule(Peer.anyIpv4(), portRule, `${service.name} ${protocol.toUpperCase()} ${port}`)
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
73
84
|
const platformConfigDeployment = new BucketDeployment(this, 'PlatformConfigDeployment', {
|
|
74
|
-
sources: [Source.jsonData('platform-config.json', { ...props.config,
|
|
85
|
+
sources: [Source.jsonData('platform-config.json', { ...props.config, env: undefined })],
|
|
75
86
|
destinationBucket: props.bucket,
|
|
76
87
|
// Default prune:true would delete every other object (e.g. media/*) on each infra deploy.
|
|
77
88
|
prune: false
|
|
@@ -125,11 +136,10 @@ class ContainerDeploymentTarget extends Construct {
|
|
|
125
136
|
`sudo tee /etc/environment << 'ENVEOF'\n${envLines.join('\n')}\nENVEOF`,
|
|
126
137
|
// Create shared Docker network for inter-container communication
|
|
127
138
|
'docker network create ossy-network || true',
|
|
128
|
-
...CaddyService.install(),
|
|
139
|
+
...CaddyService.install(containerServices.filter(s => s.caddyBlock !== null)),
|
|
129
140
|
...OssyRuntimeService.install(),
|
|
130
141
|
...OssyApiService.install(),
|
|
131
|
-
...
|
|
132
|
-
...OssyWebsitePlexusSanitasService.install()
|
|
142
|
+
...containerServices.flatMap(s => s.install())
|
|
133
143
|
)
|
|
134
144
|
|
|
135
145
|
userData.addS3DownloadCommand({
|
|
@@ -146,21 +156,42 @@ class ContainerDeploymentTarget extends Construct {
|
|
|
146
156
|
...OssyRuntimeService.start(),
|
|
147
157
|
...OssyApiService.enable(),
|
|
148
158
|
...OssyApiService.start(),
|
|
149
|
-
...
|
|
150
|
-
...OssyWebsiteOssyService.start(),
|
|
151
|
-
...OssyWebsitePlexusSanitasService.enable(),
|
|
152
|
-
...OssyWebsitePlexusSanitasService.start()
|
|
159
|
+
...containerServices.flatMap(s => [...s.enable(), ...s.start()])
|
|
153
160
|
)
|
|
154
161
|
|
|
162
|
+
const instanceClassMap = {
|
|
163
|
+
t2: InstanceClass.T2,
|
|
164
|
+
t3: InstanceClass.T3,
|
|
165
|
+
t3a: InstanceClass.T3A,
|
|
166
|
+
t4g: InstanceClass.T4G,
|
|
167
|
+
m5: InstanceClass.M5,
|
|
168
|
+
m6i: InstanceClass.M6I,
|
|
169
|
+
c5: InstanceClass.C5,
|
|
170
|
+
c6i: InstanceClass.C6I,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const instanceSizeMap = {
|
|
174
|
+
nano: InstanceSize.NANO,
|
|
175
|
+
micro: InstanceSize.MICRO,
|
|
176
|
+
small: InstanceSize.SMALL,
|
|
177
|
+
medium: InstanceSize.MEDIUM,
|
|
178
|
+
large: InstanceSize.LARGE,
|
|
179
|
+
xlarge: InstanceSize.XLARGE,
|
|
180
|
+
'2xlarge': InstanceSize.XLARGE2,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const instanceClass = instanceClassMap[props.config.awsInstanceClass ?? 't3']
|
|
184
|
+
const instanceSize = instanceSizeMap[props.config.awsInstanceSize ?? 'small']
|
|
185
|
+
|
|
186
|
+
if (!instanceClass) throw new Error(`[ContainerDeploymentTarget] Unknown awsInstanceClass: ${props.config.awsInstanceClass}`)
|
|
187
|
+
if (!instanceSize) throw new Error(`[ContainerDeploymentTarget] Unknown awsInstanceSize: ${props.config.awsInstanceSize}`)
|
|
188
|
+
|
|
155
189
|
const ec2Instance = new Instance(this, 'Ec2Instance', {
|
|
156
190
|
vpc,
|
|
157
191
|
securityGroup,
|
|
158
192
|
userData,
|
|
159
193
|
role,
|
|
160
|
-
instanceType: InstanceType.of(
|
|
161
|
-
InstanceClass.T3,
|
|
162
|
-
InstanceSize.SMALL
|
|
163
|
-
),
|
|
194
|
+
instanceType: InstanceType.of(instanceClass, instanceSize),
|
|
164
195
|
machineImage: new GenericLinuxImage({
|
|
165
196
|
[SupportedRegions.North]: InstanceImages.UBUNTU
|
|
166
197
|
}),
|
|
@@ -175,7 +206,15 @@ class ContainerDeploymentTarget extends Construct {
|
|
|
175
206
|
|
|
176
207
|
props.bucket.grantRead(ec2Instance, '*')
|
|
177
208
|
|
|
178
|
-
|
|
209
|
+
// Elastic IP gives the instance a stable public IP that survives instance replacements,
|
|
210
|
+
// preventing CloudFormation cross-stack export conflicts when the EC2 instance is updated.
|
|
211
|
+
const eip = new CfnEIP(this, 'ElasticIp', { domain: 'vpc' })
|
|
212
|
+
new CfnEIPAssociation(this, 'EipAssociation', {
|
|
213
|
+
instanceId: ec2Instance.instanceId,
|
|
214
|
+
allocationId: eip.attrAllocationId
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
this.instancePublicIp = eip.attrPublicIp
|
|
179
218
|
|
|
180
219
|
}
|
|
181
220
|
}
|