@ossy/deployment-tools 1.21.1 → 1.21.3
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
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
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.21.3 (2026-05-26)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @ossy/deployment-tools
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## 1.21.2 (2026-05-26)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @ossy/deployment-tools
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
## 1.21.1 (2026-05-26)
|
|
7
23
|
|
|
8
24
|
**Note:** Version bump only for package @ossy/deployment-tools
|
package/README.md
CHANGED
|
@@ -75,7 +75,9 @@ Host ports are auto-assigned starting at `3002`, counting only HTTP services (TC
|
|
|
75
75
|
|
|
76
76
|
#### TCP/UDP service
|
|
77
77
|
|
|
78
|
-
No Caddy block.
|
|
78
|
+
No Caddy block. Containers run with **`--network host`** so game servers bind ports on the EC2 instance directly (avoids Docker bridge DNAT issues for public traffic). The `ports` and `protocol` fields still drive **security group** ingress rules — they are not passed as `-p` mappings.
|
|
79
|
+
|
|
80
|
+
Use this for game servers or any other raw socket protocol.
|
|
79
81
|
|
|
80
82
|
```json
|
|
81
83
|
"services": [
|
|
@@ -85,6 +87,15 @@ No Caddy block. Ports are mapped directly (`HOST:CONTAINER`) and opened in the E
|
|
|
85
87
|
"image": "itzg/minecraft-server",
|
|
86
88
|
"ports": [25565],
|
|
87
89
|
"protocol": "tcp"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "palworld",
|
|
93
|
+
"type": "tcp",
|
|
94
|
+
"image": "thijsvanloef/palworld-server-docker",
|
|
95
|
+
"ports": [
|
|
96
|
+
{ "port": 8211, "protocol": "udp" },
|
|
97
|
+
{ "port": 27021, "protocol": "udp" }
|
|
98
|
+
]
|
|
88
99
|
}
|
|
89
100
|
]
|
|
90
101
|
```
|
|
@@ -94,8 +105,8 @@ No Caddy block. Ports are mapped directly (`HOST:CONTAINER`) and opened in the E
|
|
|
94
105
|
| `name` | Unique service name — Docker container name and systemd unit. |
|
|
95
106
|
| `type` | Must be `"tcp"` to enable this mode. |
|
|
96
107
|
| `image` | Docker image to pull and run. |
|
|
97
|
-
| `ports` |
|
|
98
|
-
| `protocol` | `"tcp"` (default), `"udp"`, or `"both"`. |
|
|
108
|
+
| `ports` | Port numbers (host network) and security group rules. Use a **number** with service-level `protocol`, or `{ "port": 8211, "protocol": "udp" }` per port when protocols differ. |
|
|
109
|
+
| `protocol` | Default for numeric `ports` entries: `"tcp"` (default), `"udp"`, or `"both"`. Ignored when every port is an object with its own `protocol`. Must match what the game image listens on. |
|
|
99
110
|
|
|
100
111
|
**Fixed built-in services** (always present, not configurable via `services`):
|
|
101
112
|
- `ossy-api` — the Ossy API (`ghcr.io/ossy-se/api:latest`, port 3001)
|
|
@@ -122,6 +133,38 @@ Each platform is a CDK `Stage` containing five stacks:
|
|
|
122
133
|
|
|
123
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.
|
|
124
135
|
|
|
136
|
+
## Destroy + redeploy
|
|
137
|
+
|
|
138
|
+
`<platformName>` is the `platformName` field in `packages/infrastructure/platforms.json` (CDK stage id). `<aws-profile>` is your AWS CLI profile for that account. List stacks: `npx cdk list --profile <aws-profile>`.
|
|
139
|
+
|
|
140
|
+
Full platform teardown:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
cd packages/deployment-tools
|
|
144
|
+
npx cdk destroy '<platformName>/**' --profile <aws-profile>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Recreate (CDK applies stacks in dependency order):
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npx cdk deploy '<platformName>/**' --profile <aws-profile>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Recycle **only the EC2 host** (keep S3, CDN, and backups):
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npx cdk destroy '<platformName>/dns' --profile <aws-profile>
|
|
157
|
+
npx cdk destroy '<platformName>/deployment-target' --profile <aws-profile>
|
|
158
|
+
npx cdk deploy '<platformName>/deployment-target' --profile <aws-profile>
|
|
159
|
+
npx cdk deploy '<platformName>/dns' --profile <aws-profile>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Do not destroy `storage-static` unless you intend to drop media storage (bucket is `RETAIN` and may remain in the account anyway).
|
|
163
|
+
|
|
164
|
+
The S3 bucket and **AWS Backup vault** use `RETAIN` so media and recovery points survive a stack delete. CloudFront and the backup *plan* are removed with the stack.
|
|
165
|
+
|
|
166
|
+
If deploy fails with **BackupVault already exists** after a full destroy, a retained vault is still in the account (`<platformName>-static-media-backup`). Delete it in AWS Backup (empty recovery points first), or import it into the new stack.
|
|
167
|
+
|
|
125
168
|
## Bootstrapping a new platform
|
|
126
169
|
|
|
127
170
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/deployment-tools",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.3",
|
|
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": "909e3d78752d07afceb60af78784987f6fcea8b7"
|
|
26
26
|
}
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* @property {'tcp'} type - marks a raw socket service; bypasses Caddy entirely
|
|
18
18
|
* @property {string} name - unique service name (container + systemd unit)
|
|
19
19
|
* @property {string} image - Docker image (e.g. itzg/minecraft-server)
|
|
20
|
-
* @property {number[]} ports
|
|
21
|
-
* @property {'tcp'|'udp'|'both'} [protocol] - defaults to 'tcp'
|
|
20
|
+
* @property {(number|{port: number, protocol: 'tcp'|'udp'|'both'})[]} ports - host ports + security group rules
|
|
21
|
+
* @property {'tcp'|'udp'|'both'} [protocol] - default for numeric ports; defaults to 'tcp'
|
|
22
22
|
* @property {Record<string,string>=} env - environment variables written to /etc/environment on the EC2 host at boot time; never uploaded to S3
|
|
23
23
|
*
|
|
24
24
|
* @property {string} awsAccountId - Aws account id
|
|
@@ -11,14 +11,38 @@
|
|
|
11
11
|
* @property {'tcp'} type - marks this as a raw TCP/UDP service (no HTTP/Caddy)
|
|
12
12
|
* @property {string} name - unique name, used for container name and systemd unit
|
|
13
13
|
* @property {string} image - Docker image to run
|
|
14
|
-
* @property {number[]} ports
|
|
15
|
-
* @property {'tcp'|'udp'|'both'} [protocol] - defaults to 'tcp'
|
|
14
|
+
* @property {(number|TcpPortEntry)[]} ports - host ports; each entry uses `protocol` or the service default
|
|
15
|
+
* @property {'tcp'|'udp'|'both'} [protocol] - default for numeric ports; defaults to 'tcp'
|
|
16
|
+
*
|
|
17
|
+
* @typedef {Object} TcpPortEntry
|
|
18
|
+
* @property {number} port
|
|
19
|
+
* @property {'tcp'|'udp'|'both'} protocol
|
|
16
20
|
*
|
|
17
21
|
* @typedef {HttpServiceConfig|TcpServiceConfig} ContainerServiceConfig
|
|
18
22
|
*/
|
|
19
23
|
|
|
20
24
|
const BASE_HTTP_PORT = 3002 // 3000 = platform runtime, 3001 = API
|
|
21
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @param {(number|TcpPortEntry)[]} ports
|
|
28
|
+
* @param {'tcp'|'udp'|'both'} defaultProtocol
|
|
29
|
+
* @returns {TcpPortEntry[]}
|
|
30
|
+
*/
|
|
31
|
+
const normalizePortEntries = (ports, defaultProtocol = 'tcp') =>
|
|
32
|
+
ports.map(entry =>
|
|
33
|
+
typeof entry === 'number'
|
|
34
|
+
? { port: entry, protocol: defaultProtocol }
|
|
35
|
+
: { port: entry.port, protocol: entry.protocol ?? defaultProtocol }
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
/** @param {TcpPortEntry} entry */
|
|
39
|
+
const expandSecurityGroupRules = ({ port, protocol }) => {
|
|
40
|
+
if (protocol === 'both') {
|
|
41
|
+
return [{ port, protocol: 'tcp' }, { port, protocol: 'udp' }]
|
|
42
|
+
}
|
|
43
|
+
return [{ port, protocol }]
|
|
44
|
+
}
|
|
45
|
+
|
|
22
46
|
// ---------------------------------------------------------------------------
|
|
23
47
|
// HTTP service — reverse-proxied through Caddy, container listens on port 3000
|
|
24
48
|
// ---------------------------------------------------------------------------
|
|
@@ -80,23 +104,23 @@ ${this.domain} {
|
|
|
80
104
|
}
|
|
81
105
|
|
|
82
106
|
// ---------------------------------------------------------------------------
|
|
83
|
-
// TCP/UDP service — bypasses Caddy,
|
|
84
|
-
//
|
|
107
|
+
// TCP/UDP service — bypasses Caddy, listens on the EC2 host network namespace.
|
|
108
|
+
// Uses --network host so game traffic hits the process directly (no bridge DNAT).
|
|
109
|
+
// Security group ingress still comes from securityGroupPorts below.
|
|
85
110
|
// ---------------------------------------------------------------------------
|
|
86
111
|
|
|
87
112
|
class TcpContainerService {
|
|
88
113
|
constructor({ name, image, ports, protocol = 'tcp' }) {
|
|
89
114
|
this.name = name
|
|
90
115
|
this.image = image
|
|
91
|
-
this.
|
|
92
|
-
this.protocol = protocol // 'tcp' | 'udp' | 'both'
|
|
116
|
+
this.portEntries = normalizePortEntries(ports, protocol)
|
|
93
117
|
}
|
|
94
118
|
|
|
95
119
|
get systemdUnitFile() {
|
|
96
|
-
const
|
|
120
|
+
const portList = this.portEntries.map(({ port, protocol }) => `${port}/${protocol}`).join(', ')
|
|
97
121
|
return `
|
|
98
122
|
[Unit]
|
|
99
|
-
Description=Container service: ${this.name} (
|
|
123
|
+
Description=Container service: ${this.name} (host network, ${portList})
|
|
100
124
|
After=network.target docker.service
|
|
101
125
|
Requires=docker.service
|
|
102
126
|
|
|
@@ -105,8 +129,7 @@ EnvironmentFile=/etc/environment
|
|
|
105
129
|
ExecStartPre=-/usr/bin/docker rm -f ${this.name}
|
|
106
130
|
ExecStartPre=/usr/bin/docker pull ${this.image}
|
|
107
131
|
ExecStart=/usr/bin/docker run --name ${this.name} \\
|
|
108
|
-
--network
|
|
109
|
-
${portMappings} \\
|
|
132
|
+
--network host \\
|
|
110
133
|
--env-file /etc/environment \\
|
|
111
134
|
${this.image}
|
|
112
135
|
ExecStop=/usr/bin/docker stop ${this.name}
|
|
@@ -121,12 +144,9 @@ WantedBy=multi-user.target
|
|
|
121
144
|
/** TCP services bypass Caddy — no Caddy block. */
|
|
122
145
|
get caddyBlock() { return null }
|
|
123
146
|
|
|
124
|
-
/** Each port needs an explicit security group ingress rule. */
|
|
147
|
+
/** Each port needs an explicit security group ingress rule matching its protocol. */
|
|
125
148
|
get securityGroupPorts() {
|
|
126
|
-
return this.
|
|
127
|
-
if (this.protocol === 'both') return [{ port, protocol: 'tcp' }, { port, protocol: 'udp' }]
|
|
128
|
-
return [{ port, protocol: this.protocol }]
|
|
129
|
-
})
|
|
149
|
+
return this.portEntries.flatMap(expandSecurityGroupRules)
|
|
130
150
|
}
|
|
131
151
|
|
|
132
152
|
get unitName() { return `${this.name}.service` }
|
|
@@ -158,5 +178,6 @@ const fromConfig = (services = []) => {
|
|
|
158
178
|
module.exports = {
|
|
159
179
|
HttpContainerService,
|
|
160
180
|
TcpContainerService,
|
|
181
|
+
normalizePortEntries,
|
|
161
182
|
fromConfig
|
|
162
183
|
}
|