@ossy/deployment-tools 3.6.0 → 3.12.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
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.12.0](https://github.com/ossy-se/ossy/compare/v3.11.1...v3.12.0) (2026-09-06)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* default the API to same-origin and drop api.ossy.se ([#794](https://github.com/ossy-se/ossy/issues/794)) ([397a06f](https://github.com/ossy-se/ossy/commit/397a06f88b01e297fb76f35c429f3e687bdd6af9))
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* **analytics:** Host visitor locations from connection geo ([#772](https://github.com/ossy-se/ossy/issues/772)) ([a565c1b](https://github.com/ossy-se/ossy/commit/a565c1b5078219ef412148d77dc33697f2079612)), closes [#769](https://github.com/ossy-se/ossy/issues/769) [#797](https://github.com/ossy-se/ossy/issues/797) [#797](https://github.com/ossy-se/ossy/issues/797) [#797](https://github.com/ossy-se/ossy/issues/797) [#797](https://github.com/ossy-se/ossy/issues/797) [#769](https://github.com/ossy-se/ossy/issues/769) [#797](https://github.com/ossy-se/ossy/issues/797) [#769](https://github.com/ossy-se/ossy/issues/769)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# [3.7.0](https://github.com/ossy-se/ossy/compare/v3.6.2...v3.7.0) (2026-08-22)
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* **deployment-tools:** give website-ossy 1 vCPU, 4 GB, two tasks, and a larger Node heap ([#667](https://github.com/ossy-se/ossy/issues/667)) ([3e81436](https://github.com/ossy-se/ossy/commit/3e814363a7f80f37a1b90f64677371cfa2f2fe44))
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
# [3.6.0](https://github.com/ossy-se/ossy/compare/v3.5.1...v3.6.0) (2026-08-16)
|
|
7
25
|
|
|
8
26
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Infrastructure and CDK tooling for the Ossy platform.
|
|
|
9
9
|
```
|
|
10
10
|
Internet → CloudFront (ACM TLS, us-east-1) → WAF → ALB (:80)
|
|
11
11
|
├── host rules from platforms.json `services[].hosts|domain`
|
|
12
|
-
│ e.g. ossy.se
|
|
12
|
+
│ e.g. ossy.se → website-ossy Fargate service
|
|
13
13
|
├── other known hosts / *.ossy.se → platform-runtime Fargate service
|
|
14
14
|
└── default action → platform-runtime
|
|
15
15
|
```
|
|
@@ -64,7 +64,7 @@ HTTP services (default) become Fargate services with ALB host rules. The contain
|
|
|
64
64
|
"name": "ossy-website-ossy",
|
|
65
65
|
"domain": "ossy.se",
|
|
66
66
|
"image": "ghcr.io/ossy-se/website-ossy:latest",
|
|
67
|
-
"hosts": ["ossy.se"
|
|
67
|
+
"hosts": ["ossy.se"]
|
|
68
68
|
}
|
|
69
69
|
]
|
|
70
70
|
```
|
|
@@ -73,7 +73,7 @@ HTTP services (default) become Fargate services with ALB host rules. The contain
|
|
|
73
73
|
|---|---|
|
|
74
74
|
| `name` | Unique service name — Secrets Manager / ECS key strips a leading `ossy-` (`website-ossy`). |
|
|
75
75
|
| `domain` | Primary hostname (default ALB host when `hosts` is omitted). |
|
|
76
|
-
| `hosts` | Optional ALB host-header list. Example: `["ossy.se"
|
|
76
|
+
| `hosts` | Optional ALB host-header list. Example: `["ossy.se"]` so the website image serves the apex. Defaults to `[domain]`. |
|
|
77
77
|
| `image` | Docker image to pull and run (GHCR). |
|
|
78
78
|
|
|
79
79
|
**Built-in (always present):**
|
|
@@ -264,6 +264,8 @@ Other common env vars:
|
|
|
264
264
|
| `AWS_SECRET_ACCESS_KEY` | API | AWS credentials for S3 |
|
|
265
265
|
| `OSSY_API_KEY` | Platform, websites | API JWT for CMS reads |
|
|
266
266
|
| `OSSY_COOKIE_SECRET` | Websites | Cookie signing secret |
|
|
267
|
+
| `GEOLITE2_COUNTRY_MMDB` | App (`@ossy/analytics` location projection) | Absolute path to MaxMind GeoLite2-Country `.mmdb` (#769). Used by `PageViewLocationProjection` / `ProjectionRebuild` — not at ingest. Optional; without it `countryCode` is omitted (IP still stored on the event). |
|
|
268
|
+
| `OSSY_TRUST_PROXY_HOPS` | Platform / websites | Express `trust proxy` hop count behind CloudFront→ALB (default: `2`). |
|
|
267
269
|
| `OPENAI_API_KEY` | Worker | OpenAI API key |
|
|
268
270
|
| `OPENAI_ORGANIZATION` | Worker | OpenAI organisation ID |
|
|
269
271
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/deployment-tools",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.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",
|
|
@@ -24,5 +24,5 @@
|
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"jest": "^30.4.2"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "c2650803219ad1831559b512848358d9550ffad2"
|
|
28
28
|
}
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
* @property {string} domain - primary hostname (default ALB host when `hosts` omitted)
|
|
22
22
|
* @property {string[]=} hosts - ALB host-header values (#557); defaults to `[domain]`
|
|
23
23
|
* @property {string} image - Docker image (e.g. ghcr.io/ossy-se/website-ossy:latest)
|
|
24
|
+
* @property {number=} cpu - Fargate CPU units; default 512
|
|
25
|
+
* @property {number=} memoryLimitMiB - Fargate memory MiB; default 1024
|
|
26
|
+
* @property {number=} desiredCount - ECS desired tasks; default 1
|
|
24
27
|
*
|
|
25
28
|
* @typedef {Object} TcpServiceConfig
|
|
26
29
|
* @property {'tcp'} type - raw socket service; not deployed on the ECS path (#560)
|
|
@@ -42,6 +42,10 @@ const CONTAINER_ENVIRONMENT_KEYS = Object.freeze([
|
|
|
42
42
|
* @property {string[]} hosts - ALB host-header values for this service (empty = default/catch-all)
|
|
43
43
|
* @property {boolean} isDefault - when true, ALB default action forwards here (runtime)
|
|
44
44
|
* @property {number} containerPort - container listen port
|
|
45
|
+
* @property {number} cpu - Fargate task CPU units (256, 512, 1024, …)
|
|
46
|
+
* @property {number} memoryLimitMiB - Fargate task memory (must be valid for `cpu`)
|
|
47
|
+
* @property {number} desiredCount - ECS service desired task count
|
|
48
|
+
* @property {string} [nodeOptions] - optional `NODE_OPTIONS` for the container
|
|
45
49
|
*/
|
|
46
50
|
|
|
47
51
|
/**
|
|
@@ -55,7 +59,7 @@ function secretEnvKeysForEcsContainer(envKeys = []) {
|
|
|
55
59
|
if (!Array.isArray(envKeys)) {
|
|
56
60
|
throw new Error('[platform-ecs-services] envKeys must be an array')
|
|
57
61
|
}
|
|
58
|
-
const reserved = new Set(CONTAINER_ENVIRONMENT_KEYS)
|
|
62
|
+
const reserved = new Set([...CONTAINER_ENVIRONMENT_KEYS, 'NODE_OPTIONS'])
|
|
59
63
|
return envKeys.filter((key) => typeof key === 'string' && key && !reserved.has(key))
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -78,6 +82,7 @@ function containerEnvironment({
|
|
|
78
82
|
containerPort = CONTAINER_PORT,
|
|
79
83
|
mediaRepository,
|
|
80
84
|
mediaCdnDomainName,
|
|
85
|
+
nodeOptions,
|
|
81
86
|
}) {
|
|
82
87
|
if (!serviceKey) {
|
|
83
88
|
throw new Error('[platform-ecs-services] serviceKey is required')
|
|
@@ -91,6 +96,7 @@ function containerEnvironment({
|
|
|
91
96
|
OSSY_SERVICE_NAME: serviceKey,
|
|
92
97
|
MEDIA_REPOSITORY: mediaRepository,
|
|
93
98
|
MEDIA_CDN_DOMAIN_NAME: mediaCdnDomainName,
|
|
99
|
+
...(nodeOptions ? { NODE_OPTIONS: nodeOptions } : {}),
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
|
|
@@ -151,7 +157,7 @@ function runtimeHosts(config, httpEntries) {
|
|
|
151
157
|
* @param {{
|
|
152
158
|
* platformName: string,
|
|
153
159
|
* domains?: string[],
|
|
154
|
-
* services?: Array<{ name: string, type?: string, domain?: string, hosts?: string[], image?: string }>
|
|
160
|
+
* services?: Array<{ name: string, type?: string, domain?: string, hosts?: string[], image?: string, cpu?: number, memoryLimitMiB?: number, desiredCount?: number }>
|
|
155
161
|
* }} config
|
|
156
162
|
* @returns {PlatformEcsService[]}
|
|
157
163
|
*/
|
|
@@ -173,6 +179,9 @@ function listPlatformEcsServices(config) {
|
|
|
173
179
|
hosts: runtimeHostList,
|
|
174
180
|
isDefault: true,
|
|
175
181
|
containerPort: CONTAINER_PORT,
|
|
182
|
+
cpu: 512,
|
|
183
|
+
memoryLimitMiB: 1024,
|
|
184
|
+
desiredCount: 1,
|
|
176
185
|
}
|
|
177
186
|
}
|
|
178
187
|
|
|
@@ -190,6 +199,10 @@ function listPlatformEcsServices(config) {
|
|
|
190
199
|
hosts: hostsFromServiceEntry(service.entry),
|
|
191
200
|
isDefault: false,
|
|
192
201
|
containerPort: CONTAINER_PORT,
|
|
202
|
+
cpu: Number.isFinite(service.entry.cpu) ? service.entry.cpu : 512,
|
|
203
|
+
memoryLimitMiB: Number.isFinite(service.entry.memoryLimitMiB) ? service.entry.memoryLimitMiB : 1024,
|
|
204
|
+
desiredCount: Number.isFinite(service.entry.desiredCount) ? service.entry.desiredCount : 1,
|
|
205
|
+
nodeOptions: typeof service.entry.nodeOptions === 'string' ? service.entry.nodeOptions : undefined,
|
|
193
206
|
}
|
|
194
207
|
})
|
|
195
208
|
}
|
|
@@ -63,6 +63,16 @@ describe('containerEnvironment', () => {
|
|
|
63
63
|
})
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
+
it('adds NODE_OPTIONS when provided', () => {
|
|
67
|
+
const env = containerEnvironment({
|
|
68
|
+
serviceKey: 'website-ossy',
|
|
69
|
+
mediaRepository: 'media-bucket',
|
|
70
|
+
mediaCdnDomainName: 'https://cdn.example',
|
|
71
|
+
nodeOptions: '--max-old-space-size=3072',
|
|
72
|
+
})
|
|
73
|
+
expect(env.NODE_OPTIONS).toBe('--max-old-space-size=3072')
|
|
74
|
+
})
|
|
75
|
+
|
|
66
76
|
it('requires serviceKey and media fields', () => {
|
|
67
77
|
expect(() => containerEnvironment({
|
|
68
78
|
mediaRepository: 'b',
|
|
@@ -82,6 +92,7 @@ describe('secretEnvKeysForEcsContainer', () => {
|
|
|
82
92
|
'TOKEN_SECRET',
|
|
83
93
|
'NODE_ENV',
|
|
84
94
|
'MEDIA_REPOSITORY',
|
|
95
|
+
'NODE_OPTIONS',
|
|
85
96
|
'DB_URL',
|
|
86
97
|
])).toEqual(['TOKEN_SECRET', 'DB_URL'])
|
|
87
98
|
})
|
|
@@ -95,8 +106,8 @@ describe('hostsFromServiceEntry', () => {
|
|
|
95
106
|
it('uses hosts when provided', () => {
|
|
96
107
|
expect(hostsFromServiceEntry({
|
|
97
108
|
domain: 'ossy.se',
|
|
98
|
-
hosts: ['ossy.se', '
|
|
99
|
-
})).toEqual(['ossy.se', '
|
|
109
|
+
hosts: ['ossy.se', 'www.ossy.se'],
|
|
110
|
+
})).toEqual(['ossy.se', 'www.ossy.se'])
|
|
100
111
|
})
|
|
101
112
|
|
|
102
113
|
it('falls back to domain', () => {
|
|
@@ -109,7 +120,6 @@ describe('listPlatformEcsServices', () => {
|
|
|
109
120
|
const services = listPlatformEcsServices({
|
|
110
121
|
platformName: 'ossybot',
|
|
111
122
|
domains: [
|
|
112
|
-
'api.ossy.se',
|
|
113
123
|
'ossy.se',
|
|
114
124
|
'*.ossy.se',
|
|
115
125
|
'www.plexus-sanitas.com',
|
|
@@ -119,8 +129,12 @@ describe('listPlatformEcsServices', () => {
|
|
|
119
129
|
{
|
|
120
130
|
name: 'ossy-website-ossy',
|
|
121
131
|
domain: 'ossy.se',
|
|
122
|
-
hosts: ['ossy.se'
|
|
132
|
+
hosts: ['ossy.se'],
|
|
123
133
|
image: 'ghcr.io/ossy-se/website-ossy:latest',
|
|
134
|
+
cpu: 1024,
|
|
135
|
+
memoryLimitMiB: 4096,
|
|
136
|
+
desiredCount: 2,
|
|
137
|
+
nodeOptions: '--max-old-space-size=3072',
|
|
124
138
|
},
|
|
125
139
|
{
|
|
126
140
|
name: 'ossy-website-plexus-sanitas',
|
|
@@ -150,9 +164,22 @@ describe('listPlatformEcsServices', () => {
|
|
|
150
164
|
|
|
151
165
|
const website = services.find(s => s.key === 'website-ossy')
|
|
152
166
|
expect(website.isDefault).toBe(false)
|
|
153
|
-
expect(website.hosts).toEqual(['ossy.se'
|
|
167
|
+
expect(website.hosts).toEqual(['ossy.se'])
|
|
154
168
|
expect(website.image).toBe('ghcr.io/ossy-se/website-ossy:latest')
|
|
155
169
|
expect(website.secretName).toBe('ossybot/website-ossy')
|
|
170
|
+
expect(website.cpu).toBe(1024)
|
|
171
|
+
expect(website.memoryLimitMiB).toBe(4096)
|
|
172
|
+
expect(website.desiredCount).toBe(2)
|
|
173
|
+
expect(website.nodeOptions).toBe('--max-old-space-size=3072')
|
|
174
|
+
|
|
175
|
+
const plexus = services.find(s => s.key === 'website-plexus-sanitas')
|
|
176
|
+
expect(plexus.cpu).toBe(512)
|
|
177
|
+
expect(plexus.memoryLimitMiB).toBe(1024)
|
|
178
|
+
expect(plexus.desiredCount).toBe(1)
|
|
179
|
+
expect(plexus.nodeOptions).toBeUndefined()
|
|
180
|
+
|
|
181
|
+
expect(runtime.cpu).toBe(512)
|
|
182
|
+
expect(runtime.desiredCount).toBe(1)
|
|
156
183
|
|
|
157
184
|
expect(services.some(s => s.key.includes('api'))).toBe(false)
|
|
158
185
|
})
|
|
@@ -166,8 +166,8 @@ class PlatformEcsStack extends Stack {
|
|
|
166
166
|
|
|
167
167
|
const taskDefinition = new FargateTaskDefinition(this, `TaskDef${idSuffix}`, {
|
|
168
168
|
family: `${platformName}-${service.key}`,
|
|
169
|
-
cpu:
|
|
170
|
-
memoryLimitMiB:
|
|
169
|
+
cpu: service.cpu,
|
|
170
|
+
memoryLimitMiB: service.memoryLimitMiB,
|
|
171
171
|
runtimePlatform: {
|
|
172
172
|
cpuArchitecture: CpuArchitecture.X86_64,
|
|
173
173
|
operatingSystemFamily: OperatingSystemFamily.LINUX,
|
|
@@ -199,6 +199,7 @@ class PlatformEcsStack extends Stack {
|
|
|
199
199
|
containerPort: service.containerPort,
|
|
200
200
|
mediaRepository: props.bucket.bucketName,
|
|
201
201
|
mediaCdnDomainName: `https://${props.cdnDomainName}`,
|
|
202
|
+
nodeOptions: service.nodeOptions,
|
|
202
203
|
}),
|
|
203
204
|
secrets: containerSecrets,
|
|
204
205
|
// Node fetch → GET /health (no curl in node:*-slim; no image-local script required).
|
|
@@ -222,7 +223,7 @@ class PlatformEcsStack extends Stack {
|
|
|
222
223
|
serviceName: truncateAwsName(`${platformName}-${service.key}`, 255),
|
|
223
224
|
cluster,
|
|
224
225
|
taskDefinition,
|
|
225
|
-
desiredCount:
|
|
226
|
+
desiredCount: service.desiredCount,
|
|
226
227
|
assignPublicIp: false,
|
|
227
228
|
securityGroups: [serviceSecurityGroup],
|
|
228
229
|
vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS },
|
|
@@ -280,7 +281,7 @@ class PlatformEcsStack extends Stack {
|
|
|
280
281
|
})
|
|
281
282
|
|
|
282
283
|
// Lower priority number = evaluated first. Specific hosts before wildcards
|
|
283
|
-
// so `
|
|
284
|
+
// so `ossy.se` is not swallowed by `*.ossy.se`.
|
|
284
285
|
// Priorities are hash-stable per host — sequential 10,11,12… renumbers
|
|
285
286
|
// every insert and CloudFormation cannot swap ALB priorities in one update.
|
|
286
287
|
const usedPriorities = new Set()
|