@ossy/deployment-tools 3.0.9 → 3.4.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 +17 -0
- package/README.md +156 -118
- package/jest.config.js +5 -0
- package/package.json +6 -4
- package/src/config/platform-config.js +16 -16
- package/src/ecs/platform-ecs-services.js +209 -0
- package/src/ecs/platform-ecs-services.spec.js +163 -0
- package/src/edge/platform-edge-domains.js +101 -0
- package/src/edge/platform-edge-domains.spec.js +87 -0
- package/src/index.js +45 -1
- package/src/infrastructure/dns-stack.js +47 -3
- package/src/infrastructure/dns-stack.spec.js +15 -0
- package/src/infrastructure/platform-ci-stack.js +151 -0
- package/src/infrastructure/platform-ecs-stack.js +349 -0
- package/src/infrastructure/platform-edge-stack.js +213 -0
- package/src/infrastructure/platform-secrets-stack.js +103 -0
- package/src/infrastructure/platform-stage.js +32 -5
- package/src/infrastructure/storage-static-stack.js +4 -5
- package/src/secrets/platform-secret-services.js +32 -0
- package/src/secrets/platform-secret-services.spec.js +40 -0
- package/src/secrets/sync-platform-secrets.js +136 -0
- package/src/services/platform-services.js +94 -0
- package/src/services/platform-services.spec.js +80 -0
- package/src/template/platform-template.js +5 -5
- package/src/infrastructure/container-deployment-target/aws-profile.js +0 -25
- package/src/infrastructure/container-deployment-target/caddy.service.js +0 -117
- package/src/infrastructure/container-deployment-target/container-deployment-target.js +0 -222
- package/src/infrastructure/container-deployment-target/container-service.js +0 -162
- package/src/infrastructure/container-deployment-target/index.js +0 -3
- package/src/infrastructure/container-deployment-target/ossy-api.service.js +0 -54
- package/src/infrastructure/container-deployment-target/ossy-runtime.service.js +0 -49
- package/src/infrastructure/container-deployment-target/user-data-commands.js +0 -31
- package/src/infrastructure/deployment-target-stack.js +0 -53
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared platform service naming and listing from platforms.json.
|
|
3
|
+
*
|
|
4
|
+
* Used by Secrets Manager and ECS so neither domain depends on the other.
|
|
5
|
+
* Built-in `ossy-api` is omitted — API lives in website images; only
|
|
6
|
+
* platform-runtime + configured HTTP `services[]` are included.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} PlatformService
|
|
11
|
+
* @property {string} key - short key (`runtime`, `website-ossy`, …)
|
|
12
|
+
* @property {string} secretName - `{platform}/{key}`
|
|
13
|
+
* @property {string} source - origin label (`platform-runtime` or systemd/service name)
|
|
14
|
+
* @property {object=} entry - original HTTP `services[]` entry when applicable
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Derive the canonical service key from a platforms.json HTTP service name.
|
|
19
|
+
* Strips a leading `ossy-` prefix so `ossy-website-ossy` → `website-ossy`.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} serviceName
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
function serviceKeyFromName(serviceName) {
|
|
25
|
+
if (!serviceName || typeof serviceName !== 'string') {
|
|
26
|
+
throw new Error('[platform-services] service name is required')
|
|
27
|
+
}
|
|
28
|
+
return serviceName.replace(/^ossy-/, '')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Secrets Manager / shared resource name for a service key.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} platformName
|
|
35
|
+
* @param {string} key
|
|
36
|
+
* @returns {string}
|
|
37
|
+
*/
|
|
38
|
+
function secretNameFor(platformName, key) {
|
|
39
|
+
return `${platformName}/${key}`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* HTTP `services[]` entries (skips tcp/udp and nameless rows).
|
|
44
|
+
*
|
|
45
|
+
* @param {{ services?: Array<{ name?: string, type?: string }> }} config
|
|
46
|
+
* @returns {Array<{ name: string, type?: string, domain?: string, hosts?: string[], image?: string }>}
|
|
47
|
+
*/
|
|
48
|
+
function listHttpServiceEntries(config) {
|
|
49
|
+
return (config.services || []).filter(
|
|
50
|
+
entry => (entry.type || 'http') === 'http' && entry.name
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Platform services that get secrets / ECS targets: runtime + HTTP services[].
|
|
56
|
+
* Skips legacy `ossy-api` (key `api`).
|
|
57
|
+
*
|
|
58
|
+
* @param {{ platformName: string, services?: Array<{ name: string, type?: string }> }} config
|
|
59
|
+
* @returns {PlatformService[]}
|
|
60
|
+
*/
|
|
61
|
+
function listPlatformServices(config) {
|
|
62
|
+
if (!config?.platformName) {
|
|
63
|
+
throw new Error('[platform-services] platformName is required')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { platformName } = config
|
|
67
|
+
const runtime = {
|
|
68
|
+
key: 'runtime',
|
|
69
|
+
secretName: secretNameFor(platformName, 'runtime'),
|
|
70
|
+
source: 'platform-runtime',
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const httpServices = listHttpServiceEntries(config)
|
|
74
|
+
.map(entry => {
|
|
75
|
+
const key = serviceKeyFromName(entry.name)
|
|
76
|
+
return { key, entry }
|
|
77
|
+
})
|
|
78
|
+
.filter(({ key }) => key !== 'api')
|
|
79
|
+
.map(({ key, entry }) => ({
|
|
80
|
+
key,
|
|
81
|
+
secretName: secretNameFor(platformName, key),
|
|
82
|
+
source: entry.name,
|
|
83
|
+
entry,
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
return [runtime, ...httpServices]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
serviceKeyFromName,
|
|
91
|
+
secretNameFor,
|
|
92
|
+
listHttpServiceEntries,
|
|
93
|
+
listPlatformServices,
|
|
94
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const { describe, expect, it } = require('@jest/globals')
|
|
2
|
+
const {
|
|
3
|
+
serviceKeyFromName,
|
|
4
|
+
secretNameFor,
|
|
5
|
+
listHttpServiceEntries,
|
|
6
|
+
listPlatformServices,
|
|
7
|
+
} = require('./platform-services')
|
|
8
|
+
|
|
9
|
+
describe('serviceKeyFromName', () => {
|
|
10
|
+
it('strips leading ossy- prefix', () => {
|
|
11
|
+
expect(serviceKeyFromName('ossy-website-ossy')).toBe('website-ossy')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('leaves names without ossy- prefix unchanged', () => {
|
|
15
|
+
expect(serviceKeyFromName('website-ossy')).toBe('website-ossy')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('throws when name is missing', () => {
|
|
19
|
+
expect(() => serviceKeyFromName('')).toThrow(/service name is required/)
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('secretNameFor', () => {
|
|
24
|
+
it('joins platform and key', () => {
|
|
25
|
+
expect(secretNameFor('ossybot', 'website-ossy')).toBe('ossybot/website-ossy')
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('listHttpServiceEntries', () => {
|
|
30
|
+
it('keeps http entries and skips tcp', () => {
|
|
31
|
+
const entries = listHttpServiceEntries({
|
|
32
|
+
services: [
|
|
33
|
+
{ name: 'ossy-website-ossy', domain: 'ossy.se' },
|
|
34
|
+
{ name: 'minecraft', type: 'tcp', ports: [25565] },
|
|
35
|
+
],
|
|
36
|
+
})
|
|
37
|
+
expect(entries.map(e => e.name)).toEqual(['ossy-website-ossy'])
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('listPlatformServices', () => {
|
|
42
|
+
it('includes runtime and HTTP services, skips tcp and ossy-api', () => {
|
|
43
|
+
const services = listPlatformServices({
|
|
44
|
+
platformName: 'ossybot',
|
|
45
|
+
services: [
|
|
46
|
+
{
|
|
47
|
+
name: 'ossy-website-ossy',
|
|
48
|
+
domain: 'ossy.se',
|
|
49
|
+
image: 'ghcr.io/ossy-se/website-ossy:latest',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'ossy-api',
|
|
53
|
+
domain: 'api.ossy.se',
|
|
54
|
+
image: 'ghcr.io/ossy-se/ossy-api:latest',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'minecraft',
|
|
58
|
+
type: 'tcp',
|
|
59
|
+
image: 'itzg/minecraft-server',
|
|
60
|
+
ports: [25565],
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
expect(services.map(s => s.secretName)).toEqual([
|
|
66
|
+
'ossybot/runtime',
|
|
67
|
+
'ossybot/website-ossy',
|
|
68
|
+
])
|
|
69
|
+
expect(services.find(s => s.key === 'runtime').source).toBe('platform-runtime')
|
|
70
|
+
expect(services.find(s => s.key === 'website-ossy').source).toBe('ossy-website-ossy')
|
|
71
|
+
expect(services.find(s => s.key === 'website-ossy').entry.image).toBe(
|
|
72
|
+
'ghcr.io/ossy-se/website-ossy:latest'
|
|
73
|
+
)
|
|
74
|
+
expect(services.some(s => s.key === 'api' || s.secretName.endsWith('/api'))).toBe(false)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('requires platformName', () => {
|
|
78
|
+
expect(() => listPlatformServices({})).toThrow(/platformName is required/)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
@@ -3,14 +3,14 @@ const { readFileSync } = require('fs')
|
|
|
3
3
|
const { logError, logInfo } = require('../log')
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Platform template definition
|
|
6
|
+
* Platform template definition (raw entry from platforms.json before PlatformConfig defaults).
|
|
7
7
|
* @typedef {Object} PlatformTemplate
|
|
8
8
|
* @property {string} platformName - Name of platform
|
|
9
|
-
*
|
|
10
9
|
* @property {string} awsAccountId - Aws account id
|
|
11
|
-
* @property {string=} awsRegion -
|
|
12
|
-
* @property {string=}
|
|
13
|
-
* @property {string=}
|
|
10
|
+
* @property {string=} awsRegion - AWS region (defaults applied in PlatformConfigService)
|
|
11
|
+
* @property {string[]=} githubDeployRepos - repos trusted by platform-ci OIDC (#559)
|
|
12
|
+
* @property {string[]=} domains - Route53 → CloudFront known domains (#560)
|
|
13
|
+
* @property {string=} awsKeyPairName - unused after EC2 decommission (#560)
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @class
|
|
3
|
-
*/
|
|
4
|
-
class AwsProfile {
|
|
5
|
-
|
|
6
|
-
static writeFile(roleArn, region) {
|
|
7
|
-
|
|
8
|
-
const awsProfileFile = `
|
|
9
|
-
[profile ci-client]
|
|
10
|
-
role_arn = ${roleArn}
|
|
11
|
-
credential_source = Ec2InstanceMetadata
|
|
12
|
-
region = ${region}
|
|
13
|
-
`
|
|
14
|
-
return [
|
|
15
|
-
'sudo mkdir /home/caddy',
|
|
16
|
-
'sudo mkdir /home/caddy/.aws',
|
|
17
|
-
`sudo echo "${awsProfileFile}" >> /home/caddy/.aws/credentials`
|
|
18
|
-
]
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
module.exports = {
|
|
24
|
-
AwsProfile
|
|
25
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
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 `
|
|
14
|
-
{
|
|
15
|
-
on_demand_tls {
|
|
16
|
-
ask http://localhost:3001/api/v0/apps/ask
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
api.ossy.se {
|
|
21
|
-
tls {
|
|
22
|
-
dns route53 {
|
|
23
|
-
max_retries 10
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
reverse_proxy localhost:3001
|
|
27
|
-
}
|
|
28
|
-
${serviceBlocks}
|
|
29
|
-
|
|
30
|
-
:443 {
|
|
31
|
-
tls {
|
|
32
|
-
on_demand
|
|
33
|
-
dns route53 {
|
|
34
|
-
max_retries 10
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
reverse_proxy localhost:3000
|
|
38
|
-
}
|
|
39
|
-
`
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const systemdServiceFile = `
|
|
43
|
-
# caddy-route53.service — Caddy with Route53 DNS plugin for on-demand TLS.
|
|
44
|
-
# Configured via /etc/caddy/Caddyfile.
|
|
45
|
-
|
|
46
|
-
[Unit]
|
|
47
|
-
Description=Caddy
|
|
48
|
-
Documentation=https://caddyserver.com/docs/
|
|
49
|
-
After=network.target network-online.target
|
|
50
|
-
Requires=network-online.target
|
|
51
|
-
|
|
52
|
-
[Service]
|
|
53
|
-
Type=notify
|
|
54
|
-
User=caddy
|
|
55
|
-
Group=caddy
|
|
56
|
-
ExecStart=/usr/bin/caddy.route53 run --config /etc/caddy/Caddyfile
|
|
57
|
-
TimeoutStopSec=5s
|
|
58
|
-
LimitNOFILE=1048576
|
|
59
|
-
LimitNPROC=512
|
|
60
|
-
PrivateTmp=true
|
|
61
|
-
ProtectSystem=full
|
|
62
|
-
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
|
63
|
-
|
|
64
|
-
[Install]
|
|
65
|
-
WantedBy=multi-user.target cloud-init.target
|
|
66
|
-
`
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* @class
|
|
70
|
-
*/
|
|
71
|
-
class CaddyService {
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* @param {import('./container-service').ContainerService[]} services
|
|
75
|
-
*/
|
|
76
|
-
static install(services = []) {
|
|
77
|
-
const caddyfile = buildCaddyfile(services)
|
|
78
|
-
return [
|
|
79
|
-
// Write Caddyfile
|
|
80
|
-
'sudo mkdir -p /etc/caddy',
|
|
81
|
-
`sudo tee /etc/caddy/Caddyfile > /dev/null << 'CADDYEOF'\n${caddyfile}\nCADDYEOF`,
|
|
82
|
-
// Write systemd unit
|
|
83
|
-
`sudo tee /etc/systemd/system/caddy-route53.service > /dev/null << 'UNITEOF'\n${systemdServiceFile}\nUNITEOF`,
|
|
84
|
-
// Create caddy system user (apt install caddy may not run on all Ubuntu versions)
|
|
85
|
-
'sudo useradd --system --home /var/lib/caddy --shell /usr/sbin/nologin --create-home caddy || true',
|
|
86
|
-
'sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https',
|
|
87
|
-
'sudo curl -1sLf \'https://dl.cloudsmith.io/public/caddy/stable/gpg.key\' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg',
|
|
88
|
-
'sudo curl -1sLf \'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt\' | sudo tee /etc/apt/sources.list.d/caddy-stable.list',
|
|
89
|
-
'sudo curl -1sLf \'https://dl.cloudsmith.io/public/caddy/xcaddy/gpg.key\' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-xcaddy-archive-keyring.gpg',
|
|
90
|
-
'sudo curl -1sLf \'https://dl.cloudsmith.io/public/caddy/xcaddy/debian.deb.txt\' | sudo tee /etc/apt/sources.list.d/caddy-xcaddy.list',
|
|
91
|
-
'sudo add-apt-repository ppa:longsleep/golang-backports -y',
|
|
92
|
-
'sudo apt update',
|
|
93
|
-
'sudo apt install caddy xcaddy golang-go -y',
|
|
94
|
-
'sudo xcaddy build --with github.com/caddy-dns/route53',
|
|
95
|
-
'sudo mv ./caddy /usr/bin/caddy.route53'
|
|
96
|
-
]
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
static enable() {
|
|
100
|
-
return [
|
|
101
|
-
'sudo systemctl disable caddy.service',
|
|
102
|
-
'sudo systemctl enable caddy-route53.service'
|
|
103
|
-
]
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
static start() {
|
|
107
|
-
return [
|
|
108
|
-
'sudo systemctl stop caddy.service',
|
|
109
|
-
'sudo systemctl start caddy-route53.service'
|
|
110
|
-
]
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
module.exports = {
|
|
116
|
-
CaddyService
|
|
117
|
-
}
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
const { Construct } = require('constructs')
|
|
2
|
-
const {
|
|
3
|
-
Instance,
|
|
4
|
-
InstanceType,
|
|
5
|
-
InstanceClass,
|
|
6
|
-
InstanceSize,
|
|
7
|
-
GenericLinuxImage,
|
|
8
|
-
Vpc,
|
|
9
|
-
SecurityGroup,
|
|
10
|
-
Peer,
|
|
11
|
-
Port,
|
|
12
|
-
UserData,
|
|
13
|
-
KeyPair,
|
|
14
|
-
BlockDeviceVolume,
|
|
15
|
-
CfnEIP,
|
|
16
|
-
CfnEIPAssociation
|
|
17
|
-
} = require('aws-cdk-lib/aws-ec2')
|
|
18
|
-
const { Role, ServicePrincipal, Policy, PolicyStatement, Effect } = require('aws-cdk-lib/aws-iam')
|
|
19
|
-
const { Source, BucketDeployment } = require('aws-cdk-lib/aws-s3-deployment')
|
|
20
|
-
const { getInstallNodeJs, getInstallNpm, getInstallDocker } = require('./user-data-commands')
|
|
21
|
-
const { CaddyService } = require('./caddy.service')
|
|
22
|
-
const { OssyRuntimeService } = require('./ossy-runtime.service')
|
|
23
|
-
const { OssyApiService } = require('./ossy-api.service')
|
|
24
|
-
const { fromConfig: buildContainerServices } = require('./container-service')
|
|
25
|
-
const { SupportedRegions } = require('../../config')
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* ContainerServerProps
|
|
29
|
-
* @namespace ContainerServer
|
|
30
|
-
* @typedef {Object} ContainerServerProps
|
|
31
|
-
* @property {PlatformConfig} config - platform config
|
|
32
|
-
* @property {Bucket} bucket - s3 bucket
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
const InstanceImages = {
|
|
36
|
-
UBUNTU: 'ami-067bcf851477ebb78' // Ubuntu Server 24.04 LTS (HVM), eu-north-1
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* @class
|
|
41
|
-
*/
|
|
42
|
-
class ContainerDeploymentTarget extends Construct {
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* @param {object} scope - scope
|
|
46
|
-
* @param {string} id - id
|
|
47
|
-
* @param {ContainerServerProps} props - ContainerServerProps
|
|
48
|
-
*/
|
|
49
|
-
constructor(scope, id, props) {
|
|
50
|
-
super(scope, id)
|
|
51
|
-
|
|
52
|
-
const vpc = Vpc.fromLookup(this, 'VPC', { isDefault: true })
|
|
53
|
-
const securityGroup = new SecurityGroup(this, 'SecurityGroup', { vpc, allowAllOutbound: true })
|
|
54
|
-
|
|
55
|
-
securityGroup.addIngressRule(
|
|
56
|
-
Peer.anyIpv4(),
|
|
57
|
-
Port.tcp(22),
|
|
58
|
-
'allow SSH access from anywhere'
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
securityGroup.addIngressRule(
|
|
62
|
-
Peer.anyIpv4(),
|
|
63
|
-
Port.tcp(80),
|
|
64
|
-
'allow HTTP traffic from anywhere'
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
securityGroup.addIngressRule(
|
|
68
|
-
Peer.anyIpv4(),
|
|
69
|
-
Port.tcp(443),
|
|
70
|
-
'allow HTTPS traffic from anywhere'
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
// Build container services from config — opens additional security group ports for TCP/UDP services
|
|
74
|
-
const containerServices = buildContainerServices(props.config.services)
|
|
75
|
-
|
|
76
|
-
containerServices.forEach(service => {
|
|
77
|
-
service.securityGroupPorts.forEach(({ port, protocol }) => {
|
|
78
|
-
const portRule = protocol === 'udp' ? Port.udp(port) : Port.tcp(port)
|
|
79
|
-
securityGroup.addIngressRule(Peer.anyIpv4(), portRule, `${service.name} ${protocol.toUpperCase()} ${port}`)
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
const platformConfigDeployment = new BucketDeployment(this, 'PlatformConfigDeployment', {
|
|
84
|
-
sources: [Source.jsonData('platform-config.json', { ...props.config, env: undefined })],
|
|
85
|
-
destinationBucket: props.bucket,
|
|
86
|
-
// Default prune:true would delete every other object (e.g. media/*) on each infra deploy.
|
|
87
|
-
prune: false
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
const role = new Role(this, 'role', {
|
|
91
|
-
assumedBy: new ServicePrincipal('ec2.amazonaws.com')
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
role.attachInlinePolicy(new Policy(this, 'policy', {
|
|
95
|
-
statements: [
|
|
96
|
-
new PolicyStatement({
|
|
97
|
-
effect: Effect.ALLOW,
|
|
98
|
-
actions: [
|
|
99
|
-
'route53:ListResourceRecordSets',
|
|
100
|
-
'route53:GetChange',
|
|
101
|
-
'route53:ChangeResourceRecordSets'
|
|
102
|
-
],
|
|
103
|
-
resources: [
|
|
104
|
-
`arn:aws:route53:::hostedzone/*`,
|
|
105
|
-
'arn:aws:route53:::change/*'
|
|
106
|
-
]
|
|
107
|
-
}),
|
|
108
|
-
new PolicyStatement({
|
|
109
|
-
effect: Effect.ALLOW,
|
|
110
|
-
actions: [
|
|
111
|
-
'route53:ListHostedZonesByName',
|
|
112
|
-
'route53:ListHostedZones'
|
|
113
|
-
],
|
|
114
|
-
resources: ['*']
|
|
115
|
-
}),
|
|
116
|
-
]
|
|
117
|
-
}))
|
|
118
|
-
|
|
119
|
-
const userData = UserData.forLinux()
|
|
120
|
-
|
|
121
|
-
// Write platform env vars to /etc/environment so all systemd services pick them up.
|
|
122
|
-
const envLines = Object.entries(props.config.env ?? {}).map(([k, v]) => `${k}=${v}`)
|
|
123
|
-
|
|
124
|
-
userData.addCommands(
|
|
125
|
-
'sudo groupadd docker',
|
|
126
|
-
'sudo usermod -aG docker ubuntu',
|
|
127
|
-
'newgrp docker',
|
|
128
|
-
'sudo apt update -y',
|
|
129
|
-
...getInstallNodeJs(),
|
|
130
|
-
...getInstallNpm(),
|
|
131
|
-
...getInstallDocker(),
|
|
132
|
-
'sudo apt-get install awscli --yes',
|
|
133
|
-
// Write all platform env vars before starting any service
|
|
134
|
-
`sudo tee /etc/environment << 'ENVEOF'\n${envLines.join('\n')}\nENVEOF`,
|
|
135
|
-
// Create shared Docker network for inter-container communication
|
|
136
|
-
'docker network create ossy-network || true',
|
|
137
|
-
...CaddyService.install(containerServices.filter(s => s.caddyBlock !== null)),
|
|
138
|
-
...OssyRuntimeService.install(),
|
|
139
|
-
...OssyApiService.install(),
|
|
140
|
-
...containerServices.flatMap(s => s.install())
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
userData.addS3DownloadCommand({
|
|
144
|
-
bucket: props.bucket,
|
|
145
|
-
bucketKey: 'platform-config.json',
|
|
146
|
-
localFile: '/home/ubuntu/platform-config.json'
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
userData.addCommands(
|
|
150
|
-
'sudo systemctl daemon-reload',
|
|
151
|
-
...CaddyService.enable(),
|
|
152
|
-
...CaddyService.start(),
|
|
153
|
-
...OssyRuntimeService.enable(),
|
|
154
|
-
...OssyRuntimeService.start(),
|
|
155
|
-
...OssyApiService.enable(),
|
|
156
|
-
...OssyApiService.start(),
|
|
157
|
-
...containerServices.flatMap(s => [...s.enable(), ...s.start()])
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
const instanceClassMap = {
|
|
161
|
-
t2: InstanceClass.T2,
|
|
162
|
-
t3: InstanceClass.T3,
|
|
163
|
-
t3a: InstanceClass.T3A,
|
|
164
|
-
t4g: InstanceClass.T4G,
|
|
165
|
-
m5: InstanceClass.M5,
|
|
166
|
-
m6i: InstanceClass.M6I,
|
|
167
|
-
c5: InstanceClass.C5,
|
|
168
|
-
c6i: InstanceClass.C6I,
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const instanceSizeMap = {
|
|
172
|
-
nano: InstanceSize.NANO,
|
|
173
|
-
micro: InstanceSize.MICRO,
|
|
174
|
-
small: InstanceSize.SMALL,
|
|
175
|
-
medium: InstanceSize.MEDIUM,
|
|
176
|
-
large: InstanceSize.LARGE,
|
|
177
|
-
xlarge: InstanceSize.XLARGE,
|
|
178
|
-
'2xlarge': InstanceSize.XLARGE2,
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const instanceClass = instanceClassMap[props.config.awsInstanceClass ?? 't3']
|
|
182
|
-
const instanceSize = instanceSizeMap[props.config.awsInstanceSize ?? 'small']
|
|
183
|
-
|
|
184
|
-
if (!instanceClass) throw new Error(`[ContainerDeploymentTarget] Unknown awsInstanceClass: ${props.config.awsInstanceClass}`)
|
|
185
|
-
if (!instanceSize) throw new Error(`[ContainerDeploymentTarget] Unknown awsInstanceSize: ${props.config.awsInstanceSize}`)
|
|
186
|
-
|
|
187
|
-
const ec2Instance = new Instance(this, 'Ec2Instance', {
|
|
188
|
-
vpc,
|
|
189
|
-
securityGroup,
|
|
190
|
-
userData,
|
|
191
|
-
role,
|
|
192
|
-
instanceType: InstanceType.of(instanceClass, instanceSize),
|
|
193
|
-
machineImage: new GenericLinuxImage({
|
|
194
|
-
[SupportedRegions.North]: InstanceImages.UBUNTU
|
|
195
|
-
}),
|
|
196
|
-
blockDevices: [
|
|
197
|
-
{
|
|
198
|
-
deviceName: '/dev/sda1',
|
|
199
|
-
volume: BlockDeviceVolume.ebs(50)
|
|
200
|
-
}
|
|
201
|
-
],
|
|
202
|
-
keyPair: KeyPair.fromKeyPairName(this, 'KeyPair', props.config.awsKeyPairName)
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
props.bucket.grantRead(ec2Instance, '*')
|
|
206
|
-
|
|
207
|
-
// Elastic IP gives the instance a stable public IP that survives instance replacements,
|
|
208
|
-
// preventing CloudFormation cross-stack export conflicts when the EC2 instance is updated.
|
|
209
|
-
const eip = new CfnEIP(this, 'ElasticIp', { domain: 'vpc' })
|
|
210
|
-
new CfnEIPAssociation(this, 'EipAssociation', {
|
|
211
|
-
instanceId: ec2Instance.instanceId,
|
|
212
|
-
allocationId: eip.attrAllocationId
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
this.instancePublicIp = eip.attrPublicIp
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
module.exports = {
|
|
221
|
-
ContainerDeploymentTarget
|
|
222
|
-
}
|