@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. Ports are mapped directly (`HOST:CONTAINER`) and opened in the EC2 security group. Use this for game servers or any raw socket protocol.
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` | Array of port numbers to expose on the host and open in the security group. |
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.1",
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": "5920a30df63c1d67315d83eca0bce77b8e85b7b9"
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 - ports to open on the host and in the EC2 security group
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 - ports to expose directly (same inside and outside container)
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, ports exposed directly on the host.
84
- // Used for game servers and any other raw socket services.
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.ports = ports
92
- this.protocol = protocol // 'tcp' | 'udp' | 'both'
116
+ this.portEntries = normalizePortEntries(ports, protocol)
93
117
  }
94
118
 
95
119
  get systemdUnitFile() {
96
- const portMappings = this.ports.map(p => `-p ${p}:${p}`).join(' \\\n ')
120
+ const portList = this.portEntries.map(({ port, protocol }) => `${port}/${protocol}`).join(', ')
97
121
  return `
98
122
  [Unit]
99
- Description=Container service: ${this.name} (${this.protocol.toUpperCase()} ${this.ports.join(', ')})
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 ossy-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.ports.flatMap(port => {
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
  }
@@ -39,6 +39,7 @@ class StorageStaticStack extends Stack {
39
39
  })
40
40
 
41
41
  const backupVault = new BackupVault(this, 'StaticMediaBackupVault', {
42
+ backupVaultName: `${props.config.platformName}-static-media-backup`,
42
43
  removalPolicy: RemovalPolicy.RETAIN
43
44
  })
44
45