@ossy/deployment-tools 1.20.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 +11 -0
- package/README.md +32 -4
- package/package.json +2 -2
- package/src/config/platform-config.js +14 -1
- package/src/infrastructure/container-deployment-target/caddy.service.js +2 -1
- package/src/infrastructure/container-deployment-target/container-deployment-target.js +13 -6
- package/src/infrastructure/container-deployment-target/container-service.js +104 -42
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
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
|
+
|
|
6
17
|
# 1.20.0 (2026-05-25)
|
|
7
18
|
|
|
8
19
|
|
package/README.md
CHANGED
|
@@ -49,7 +49,11 @@ Additional services are generated automatically from the `services` array in `pl
|
|
|
49
49
|
|
|
50
50
|
### `services` — container/domain mapping
|
|
51
51
|
|
|
52
|
-
|
|
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.
|
|
53
57
|
|
|
54
58
|
```json
|
|
55
59
|
"services": [
|
|
@@ -63,11 +67,35 @@ Each entry in `services` generates a systemd unit that pulls and runs a Docker i
|
|
|
63
67
|
|
|
64
68
|
| Field | Description |
|
|
65
69
|
|---|---|
|
|
66
|
-
| `name` | Unique service name
|
|
67
|
-
| `domain` |
|
|
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`). |
|
|
68
72
|
| `image` | Docker image to pull and run. |
|
|
69
73
|
|
|
70
|
-
Host ports are auto-assigned starting at `3002
|
|
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"`. |
|
|
71
99
|
|
|
72
100
|
**Fixed built-in services** (always present, not configurable via `services`):
|
|
73
101
|
- `ossy-api` — the Ossy API (`ghcr.io/ossy-se/api:latest`, port 3001)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/deployment-tools",
|
|
3
|
-
"version": "1.20.
|
|
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",
|
|
@@ -22,5 +22,5 @@
|
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"jest": "^27.5.1"
|
|
24
24
|
},
|
|
25
|
-
"gitHead": "
|
|
25
|
+
"gitHead": "b063e40ba5b374e04a5911a4fec92ec6b9014409"
|
|
26
26
|
}
|
|
@@ -5,7 +5,20 @@
|
|
|
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<
|
|
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'
|
|
9
22
|
* @property {Record<string,string>=} env - environment variables written to /etc/environment on the EC2 host at boot time; never uploaded to S3
|
|
10
23
|
*
|
|
11
24
|
* @property {string} awsAccountId - Aws account id
|
|
@@ -83,7 +83,8 @@ class CaddyService {
|
|
|
83
83
|
`sudo tee /etc/caddy/Caddyfile > /dev/null << 'CADDYEOF'\n${caddyfile}\nCADDYEOF`,
|
|
84
84
|
// Write systemd unit
|
|
85
85
|
`sudo tee /etc/systemd/system/caddy-route53.service > /dev/null << 'UNITEOF'\n${systemdServiceFile}\nUNITEOF`,
|
|
86
|
-
//
|
|
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',
|
|
87
88
|
'sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https',
|
|
88
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',
|
|
89
90
|
'sudo curl -1sLf \'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt\' | sudo tee /etc/apt/sources.list.d/caddy-stable.list',
|
|
@@ -21,7 +21,7 @@ const { getInstallNodeJs, getInstallNpm, getInstallDocker } = require('./user-da
|
|
|
21
21
|
const { CaddyService } = require('./caddy.service')
|
|
22
22
|
const { OssyRuntimeService } = require('./ossy-runtime.service')
|
|
23
23
|
const { OssyApiService } = require('./ossy-api.service')
|
|
24
|
-
const {
|
|
24
|
+
const { fromConfig: buildContainerServices } = require('./container-service')
|
|
25
25
|
const { AwsProfile } = require('./aws-profile')
|
|
26
26
|
const { SupportedRegions } = require('../../config')
|
|
27
27
|
|
|
@@ -34,7 +34,7 @@ const { SupportedRegions } = require('../../config')
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
const InstanceImages = {
|
|
37
|
-
UBUNTU: 'ami-
|
|
37
|
+
UBUNTU: 'ami-067bcf851477ebb78' // Ubuntu Server 24.04 LTS (HVM), eu-north-1
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -71,6 +71,16 @@ class ContainerDeploymentTarget extends Construct {
|
|
|
71
71
|
'allow HTTPS traffic from anywhere'
|
|
72
72
|
)
|
|
73
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
|
+
|
|
74
84
|
const platformConfigDeployment = new BucketDeployment(this, 'PlatformConfigDeployment', {
|
|
75
85
|
sources: [Source.jsonData('platform-config.json', { ...props.config, env: undefined })],
|
|
76
86
|
destinationBucket: props.bucket,
|
|
@@ -112,9 +122,6 @@ class ContainerDeploymentTarget extends Construct {
|
|
|
112
122
|
// Write platform env vars to /etc/environment so all systemd services pick them up.
|
|
113
123
|
const envLines = Object.entries(props.config.env ?? {}).map(([k, v]) => `${k}=${v}`)
|
|
114
124
|
|
|
115
|
-
// Build generic container services from config.services
|
|
116
|
-
const containerServices = ContainerService.fromConfig(props.config.services)
|
|
117
|
-
|
|
118
125
|
userData.addCommands(
|
|
119
126
|
'sudo groupadd docker',
|
|
120
127
|
'sudo usermod -aG docker ubuntu',
|
|
@@ -129,7 +136,7 @@ class ContainerDeploymentTarget extends Construct {
|
|
|
129
136
|
`sudo tee /etc/environment << 'ENVEOF'\n${envLines.join('\n')}\nENVEOF`,
|
|
130
137
|
// Create shared Docker network for inter-container communication
|
|
131
138
|
'docker network create ossy-network || true',
|
|
132
|
-
...CaddyService.install(containerServices),
|
|
139
|
+
...CaddyService.install(containerServices.filter(s => s.caddyBlock !== null)),
|
|
133
140
|
...OssyRuntimeService.install(),
|
|
134
141
|
...OssyApiService.install(),
|
|
135
142
|
...containerServices.flatMap(s => s.install())
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* @
|
|
5
|
-
* @property {
|
|
6
|
-
* @property {string}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const BASE_PORT = 3002 // 3000 = platform runtime, 3001 = API
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* ContainerService generates a systemd unit file and a Caddy reverse-proxy block
|
|
13
|
-
* for a domain → Docker container mapping defined in platforms.json `services`.
|
|
2
|
+
* @typedef {'http'|'tcp'} ServiceType
|
|
3
|
+
*
|
|
4
|
+
* @typedef {Object} HttpServiceConfig
|
|
5
|
+
* @property {'http'} [type] - defaults to 'http'
|
|
6
|
+
* @property {string} name - unique name, used for container name and systemd unit
|
|
7
|
+
* @property {string} domain - domain this service handles (Caddy block + Route53)
|
|
8
|
+
* @property {string} image - Docker image to run
|
|
14
9
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* @typedef {Object} TcpServiceConfig
|
|
11
|
+
* @property {'tcp'} type - marks this as a raw TCP/UDP service (no HTTP/Caddy)
|
|
12
|
+
* @property {string} name - unique name, used for container name and systemd unit
|
|
13
|
+
* @property {string} image - Docker image to run
|
|
14
|
+
* @property {number[]} ports - ports to expose directly (same inside and outside container)
|
|
15
|
+
* @property {'tcp'|'udp'|'both'} [protocol] - defaults to 'tcp'
|
|
17
16
|
*
|
|
18
|
-
*
|
|
19
|
-
* via --env-file, so no per-service env wiring is needed.
|
|
17
|
+
* @typedef {HttpServiceConfig|TcpServiceConfig} ContainerServiceConfig
|
|
20
18
|
*/
|
|
21
|
-
class ContainerService {
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
const BASE_HTTP_PORT = 3002 // 3000 = platform runtime, 3001 = API
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// HTTP service — reverse-proxied through Caddy, container listens on port 3000
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
class HttpContainerService {
|
|
27
|
+
constructor({ name, domain, image }, httpIndex) {
|
|
28
28
|
this.name = name
|
|
29
29
|
this.domain = domain
|
|
30
30
|
this.image = image
|
|
31
|
-
this.
|
|
31
|
+
this.hostPort = BASE_HTTP_PORT + httpIndex
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
get systemdUnitFile() {
|
|
@@ -44,7 +44,7 @@ ExecStartPre=-/usr/bin/docker rm -f ${this.name}
|
|
|
44
44
|
ExecStartPre=/usr/bin/docker pull ${this.image}
|
|
45
45
|
ExecStart=/usr/bin/docker run --name ${this.name} \\
|
|
46
46
|
--network ossy-network \\
|
|
47
|
-
-p ${this.
|
|
47
|
+
-p ${this.hostPort}:3000 \\
|
|
48
48
|
--env-file /etc/environment \\
|
|
49
49
|
${this.image}
|
|
50
50
|
ExecStop=/usr/bin/docker stop ${this.name}
|
|
@@ -65,37 +65,99 @@ ${this.domain} {
|
|
|
65
65
|
profile ci-client
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
reverse_proxy localhost:${this.
|
|
68
|
+
reverse_proxy localhost:${this.hostPort}
|
|
69
69
|
}`
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
/** Ports to open in the EC2 security group. HTTP services use Caddy on 80/443 — no extra rules needed. */
|
|
73
|
+
get securityGroupPorts() {
|
|
74
|
+
return []
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
77
|
+
get unitName() { return `${this.name}.service` }
|
|
78
|
+
install() { return [`sudo tee /etc/systemd/system/${this.unitName} > /dev/null << 'EOF'\n${this.systemdUnitFile}\nEOF`] }
|
|
79
|
+
enable() { return [`sudo systemctl enable ${this.name}`] }
|
|
80
|
+
start() { return [`sudo systemctl start ${this.name}`] }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// TCP/UDP service — bypasses Caddy, ports exposed directly on the host.
|
|
85
|
+
// Used for game servers and any other raw socket services.
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
79
87
|
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
class TcpContainerService {
|
|
89
|
+
constructor({ name, image, ports, protocol = 'tcp' }) {
|
|
90
|
+
this.name = name
|
|
91
|
+
this.image = image
|
|
92
|
+
this.ports = ports
|
|
93
|
+
this.protocol = protocol // 'tcp' | 'udp' | 'both'
|
|
82
94
|
}
|
|
83
95
|
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
get systemdUnitFile() {
|
|
97
|
+
const portMappings = this.ports.map(p => `-p ${p}:${p}`).join(' \\\n ')
|
|
98
|
+
return `
|
|
99
|
+
[Unit]
|
|
100
|
+
Description=Container service: ${this.name} (${this.protocol.toUpperCase()} ${this.ports.join(', ')})
|
|
101
|
+
After=network.target docker.service
|
|
102
|
+
Requires=docker.service
|
|
103
|
+
|
|
104
|
+
[Service]
|
|
105
|
+
EnvironmentFile=/etc/environment
|
|
106
|
+
ExecStartPre=-/usr/bin/docker rm -f ${this.name}
|
|
107
|
+
ExecStartPre=/usr/bin/docker pull ${this.image}
|
|
108
|
+
ExecStart=/usr/bin/docker run --name ${this.name} \\
|
|
109
|
+
--network ossy-network \\
|
|
110
|
+
${portMappings} \\
|
|
111
|
+
--env-file /etc/environment \\
|
|
112
|
+
${this.image}
|
|
113
|
+
ExecStop=/usr/bin/docker stop ${this.name}
|
|
114
|
+
Restart=on-failure
|
|
115
|
+
RestartSec=5
|
|
116
|
+
|
|
117
|
+
[Install]
|
|
118
|
+
WantedBy=multi-user.target
|
|
119
|
+
`
|
|
86
120
|
}
|
|
87
121
|
|
|
88
|
-
/**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
122
|
+
/** TCP services bypass Caddy — no Caddy block. */
|
|
123
|
+
get caddyBlock() { return null }
|
|
124
|
+
|
|
125
|
+
/** Each port needs an explicit security group ingress rule. */
|
|
126
|
+
get securityGroupPorts() {
|
|
127
|
+
return this.ports.flatMap(port => {
|
|
128
|
+
if (this.protocol === 'both') return [{ port, protocol: 'tcp' }, { port, protocol: 'udp' }]
|
|
129
|
+
return [{ port, protocol: this.protocol }]
|
|
130
|
+
})
|
|
95
131
|
}
|
|
96
132
|
|
|
133
|
+
get unitName() { return `${this.name}.service` }
|
|
134
|
+
install() { return [`sudo tee /etc/systemd/system/${this.unitName} > /dev/null << 'EOF'\n${this.systemdUnitFile}\nEOF`] }
|
|
135
|
+
enable() { return [`sudo systemctl enable ${this.name}`] }
|
|
136
|
+
start() { return [`sudo systemctl start ${this.name}`] }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Factory
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Build service instances from the `services` array in platforms.json.
|
|
145
|
+
* HTTP services auto-assign host ports starting at 3002 (counted separately
|
|
146
|
+
* from TCP services so reordering TCP entries doesn't shift HTTP ports).
|
|
147
|
+
*
|
|
148
|
+
* @param {ContainerServiceConfig[]} services
|
|
149
|
+
* @returns {(HttpContainerService|TcpContainerService)[]}
|
|
150
|
+
*/
|
|
151
|
+
const fromConfig = (services = []) => {
|
|
152
|
+
let httpIndex = 0
|
|
153
|
+
return services.map(s => {
|
|
154
|
+
if (s.type === 'tcp') return new TcpContainerService(s)
|
|
155
|
+
return new HttpContainerService(s, httpIndex++)
|
|
156
|
+
})
|
|
97
157
|
}
|
|
98
158
|
|
|
99
159
|
module.exports = {
|
|
100
|
-
|
|
160
|
+
HttpContainerService,
|
|
161
|
+
TcpContainerService,
|
|
162
|
+
fromConfig
|
|
101
163
|
}
|