@ossy/deployment-tools 3.0.8 → 3.1.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 +18 -0
- package/README.md +160 -113
- 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 +111 -0
- package/src/ecs/platform-ecs-services.spec.js +77 -0
- package/src/edge/platform-edge-domains.js +101 -0
- package/src/edge/platform-edge-domains.spec.js +87 -0
- package/src/index.js +33 -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 +326 -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
|
@@ -9,13 +9,12 @@ const { S3BucketOrigin } = require('aws-cdk-lib/aws-cloudfront-origins')
|
|
|
9
9
|
* StorageStaticStack provisions the S3 bucket used for static assets and media,
|
|
10
10
|
* a CloudFront CDN distribution serving the /media prefix, and a daily backup plan.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* or replaced, avoiding CloudFormation cross-stack export conflicts.
|
|
12
|
+
* Kept separate from compute stacks so media storage is not replaced when ECS/edge
|
|
13
|
+
* stacks change (avoids CloudFormation cross-stack export conflicts).
|
|
15
14
|
*
|
|
16
|
-
* Exports (consumed by
|
|
15
|
+
* Exports (consumed by platform-ecs via cross-stack reference):
|
|
17
16
|
* - `bucket` — the S3 Bucket construct
|
|
18
|
-
* - `cdnDomainName` — the CloudFront distribution domain name
|
|
17
|
+
* - `cdnDomainName` — the CloudFront distribution domain name (media CDN)
|
|
19
18
|
*/
|
|
20
19
|
class StorageStaticStack extends Stack {
|
|
21
20
|
constructor(scope, id, props) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform secret service targets for Secrets Manager.
|
|
3
|
+
*
|
|
4
|
+
* Naming and service set come from shared `platform-services` (#557 review).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { listPlatformServices } = require('../services/platform-services')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} PlatformSecretService
|
|
11
|
+
* @property {string} key - short key used in the secret name (after platform prefix)
|
|
12
|
+
* @property {string} secretName - full Secrets Manager name `{platform}/{key}`
|
|
13
|
+
* @property {string} source - origin label for docs/logs (`platform-runtime` or `services[].name`)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* List of platform services that should have a Secrets Manager secret.
|
|
18
|
+
*
|
|
19
|
+
* @param {{ platformName: string, services?: Array<{ name: string, type?: string }> }} config
|
|
20
|
+
* @returns {PlatformSecretService[]}
|
|
21
|
+
*/
|
|
22
|
+
function listPlatformSecretServices(config) {
|
|
23
|
+
return listPlatformServices(config).map(({ key, secretName, source }) => ({
|
|
24
|
+
key,
|
|
25
|
+
secretName,
|
|
26
|
+
source,
|
|
27
|
+
}))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = {
|
|
31
|
+
listPlatformSecretServices,
|
|
32
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const { describe, expect, it } = require('@jest/globals')
|
|
2
|
+
const { listPlatformSecretServices } = require('./platform-secret-services')
|
|
3
|
+
|
|
4
|
+
describe('listPlatformSecretServices', () => {
|
|
5
|
+
it('includes runtime and HTTP services, skips tcp and ossy-api', () => {
|
|
6
|
+
const services = listPlatformSecretServices({
|
|
7
|
+
platformName: 'ossybot',
|
|
8
|
+
services: [
|
|
9
|
+
{
|
|
10
|
+
name: 'ossy-website-ossy',
|
|
11
|
+
domain: 'ossy.se',
|
|
12
|
+
image: 'ghcr.io/ossy-se/website-ossy:latest',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: 'ossy-api',
|
|
16
|
+
domain: 'api.ossy.se',
|
|
17
|
+
image: 'ghcr.io/ossy-se/ossy-api:latest',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'minecraft',
|
|
21
|
+
type: 'tcp',
|
|
22
|
+
image: 'itzg/minecraft-server',
|
|
23
|
+
ports: [25565],
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
expect(services.map(s => s.secretName)).toEqual([
|
|
29
|
+
'ossybot/runtime',
|
|
30
|
+
'ossybot/website-ossy',
|
|
31
|
+
])
|
|
32
|
+
expect(services.find(s => s.key === 'runtime').source).toBe('platform-runtime')
|
|
33
|
+
expect(services.find(s => s.key === 'website-ossy').source).toBe('ossy-website-ossy')
|
|
34
|
+
expect(services.some(s => s.key === 'api' || s.secretName.endsWith('/api'))).toBe(false)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('requires platformName', () => {
|
|
38
|
+
expect(() => listPlatformSecretServices({})).toThrow(/platformName is required/)
|
|
39
|
+
})
|
|
40
|
+
})
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Upsert Secrets Manager secret *values* from platforms.json env blocks.
|
|
4
|
+
*
|
|
5
|
+
* CDK (`PlatformSecretsStack`) owns the secret resources and IAM. This script
|
|
6
|
+
* only writes values — it never prints secret contents.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npm run sync-secrets -- --profile ossybot
|
|
10
|
+
* node src/secrets/sync-platform-secrets.js --platforms-path ../infrastructure/platforms.json --profile ossybot
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { resolve } = require('path')
|
|
14
|
+
const {
|
|
15
|
+
SecretsManagerClient,
|
|
16
|
+
PutSecretValueCommand,
|
|
17
|
+
DescribeSecretCommand,
|
|
18
|
+
} = require('@aws-sdk/client-secrets-manager')
|
|
19
|
+
const { PlatformTemplateService } = require('../template')
|
|
20
|
+
const { PlatformConfigService } = require('../config')
|
|
21
|
+
const { listPlatformSecretServices } = require('./platform-secret-services')
|
|
22
|
+
const { logInfo, logError } = require('../log')
|
|
23
|
+
|
|
24
|
+
function getArg(argv, name) {
|
|
25
|
+
const flag = `--${name}`
|
|
26
|
+
const index = argv.indexOf(flag)
|
|
27
|
+
if (index === -1) return undefined
|
|
28
|
+
return argv[index + 1]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hasFlag(argv, name) {
|
|
32
|
+
return argv.includes(`--${name}`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {object} options
|
|
37
|
+
* @param {string} options.platformsPath
|
|
38
|
+
* @param {string=} options.profile
|
|
39
|
+
* @param {string=} options.platformName - sync a single platform when set
|
|
40
|
+
* @param {boolean=} options.dryRun
|
|
41
|
+
*/
|
|
42
|
+
async function syncPlatformSecrets({
|
|
43
|
+
platformsPath,
|
|
44
|
+
profile,
|
|
45
|
+
platformName,
|
|
46
|
+
dryRun = false,
|
|
47
|
+
}) {
|
|
48
|
+
if (profile) {
|
|
49
|
+
process.env.AWS_PROFILE = profile
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const templates = await PlatformTemplateService.readFromFile(platformsPath)
|
|
53
|
+
const configs = templates.map(PlatformConfigService.from)
|
|
54
|
+
.filter(config => !platformName || config.platformName === platformName)
|
|
55
|
+
|
|
56
|
+
if (configs.length === 0) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
platformName
|
|
59
|
+
? `No platform named "${platformName}" in ${platformsPath}`
|
|
60
|
+
: `No platforms found in ${platformsPath}`
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const config of configs) {
|
|
65
|
+
const region = config.awsRegion
|
|
66
|
+
const client = new SecretsManagerClient({ region })
|
|
67
|
+
const services = listPlatformSecretServices(config)
|
|
68
|
+
const env = config.env || {}
|
|
69
|
+
const keyCount = Object.keys(env).length
|
|
70
|
+
|
|
71
|
+
logInfo({
|
|
72
|
+
message: `[sync-platform-secrets] ${config.platformName}: upserting ${services.length} secret(s), ${keyCount} env key(s) each`,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
for (const service of services) {
|
|
76
|
+
if (dryRun) {
|
|
77
|
+
logInfo({
|
|
78
|
+
message: `[sync-platform-secrets] dry-run: would put ${service.secretName} (${keyCount} keys)`,
|
|
79
|
+
})
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await client.send(new DescribeSecretCommand({ SecretId: service.secretName }))
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error.name === 'ResourceNotFoundException' || error.__type === 'ResourceNotFoundException') {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Secret "${service.secretName}" does not exist. Deploy the platform-secrets CDK stack first ` +
|
|
89
|
+
`(npx cdk deploy '${config.platformName}/platform-secrets'), then re-run sync-secrets.`
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
throw error
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await client.send(new PutSecretValueCommand({
|
|
96
|
+
SecretId: service.secretName,
|
|
97
|
+
SecretString: JSON.stringify(env),
|
|
98
|
+
}))
|
|
99
|
+
|
|
100
|
+
logInfo({
|
|
101
|
+
message: `[sync-platform-secrets] upserted ${service.secretName} (${keyCount} keys)`,
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function main(argv = process.argv.slice(2)) {
|
|
108
|
+
const platformsPath = resolve(
|
|
109
|
+
getArg(argv, 'platforms-path') || '../infrastructure/platforms.json'
|
|
110
|
+
)
|
|
111
|
+
const profile = getArg(argv, 'profile')
|
|
112
|
+
const platformName = getArg(argv, 'platform')
|
|
113
|
+
const dryRun = hasFlag(argv, 'dry-run')
|
|
114
|
+
|
|
115
|
+
await syncPlatformSecrets({
|
|
116
|
+
platformsPath,
|
|
117
|
+
profile,
|
|
118
|
+
platformName,
|
|
119
|
+
dryRun,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (require.main === module) {
|
|
124
|
+
main().catch(error => {
|
|
125
|
+
logError({
|
|
126
|
+
message: '[sync-platform-secrets] failed',
|
|
127
|
+
error: error?.message || error,
|
|
128
|
+
})
|
|
129
|
+
process.exitCode = 1
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
syncPlatformSecrets,
|
|
135
|
+
main,
|
|
136
|
+
}
|
|
@@ -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
|
-
}
|