@saws/core 2.0.0-beta.0 → 2.0.0-beta.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.
Files changed (76) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/Host.d.ts +62 -0
  3. package/dist/Host.js +202 -0
  4. package/dist/ServiceDefinition.d.ts +46 -0
  5. package/dist/ServiceDefinition.js +119 -0
  6. package/dist/find-service-definition.d.ts +1 -8
  7. package/dist/find-service-definition.js +0 -6
  8. package/dist/get-saws-config.d.ts +7 -3
  9. package/dist/get-saws-config.js +6 -56
  10. package/dist/host-readiness.d.ts +12 -0
  11. package/dist/host-readiness.js +230 -0
  12. package/dist/index.d.ts +4 -5
  13. package/dist/index.js +4 -4
  14. package/dist/secrets-manager.d.ts +63 -0
  15. package/dist/secrets-manager.js +323 -0
  16. package/dist/tsconfig.tsbuildinfo +1 -0
  17. package/dist/utils/constants.d.ts +4 -0
  18. package/dist/utils/constants.js +5 -0
  19. package/dist/utils/create-directories.d.ts +2 -0
  20. package/dist/utils/create-directories.js +20 -0
  21. package/dist/utils/create-file-if-not-exists.d.ts +1 -0
  22. package/dist/utils/create-file-if-not-exists.js +12 -0
  23. package/dist/utils/create-file-if-note-exists.d.ts +1 -0
  24. package/dist/utils/create-file-if-note-exists.js +11 -0
  25. package/dist/utils/dependency-management.d.ts +16 -0
  26. package/dist/utils/dependency-management.js +111 -0
  27. package/dist/utils/file-exists.d.ts +1 -0
  28. package/dist/utils/file-exists.js +12 -0
  29. package/dist/utils/generate-token.d.ts +1 -0
  30. package/dist/utils/generate-token.js +13 -0
  31. package/dist/utils/get-project-name.d.ts +1 -0
  32. package/dist/utils/get-project-name.js +5 -0
  33. package/dist/utils/get-service-path.js +1 -0
  34. package/dist/utils/list-files.d.ts +1 -0
  35. package/dist/utils/list-files.js +17 -0
  36. package/dist/utils/on-exit.d.ts +1 -0
  37. package/dist/utils/on-exit.js +14 -0
  38. package/dist/utils/parameterized-env-var-name.d.ts +1 -0
  39. package/dist/utils/parameterized-env-var-name.js +1 -0
  40. package/dist/utils/recursively-read-dir.d.ts +1 -0
  41. package/dist/utils/recursively-read-dir.js +15 -0
  42. package/dist/utils/retry-until.d.ts +1 -0
  43. package/dist/utils/retry-until.js +9 -0
  44. package/dist/utils/run-local.d.ts +8 -0
  45. package/dist/utils/run-local.js +86 -0
  46. package/dist/utils/shell-quote.d.ts +1 -0
  47. package/dist/utils/shell-quote.js +3 -0
  48. package/dist/utils/stage-outputs.d.ts +3 -0
  49. package/dist/utils/stage-outputs.js +24 -0
  50. package/dist/utils/uppercase.d.ts +1 -0
  51. package/dist/utils/uppercase.js +4 -0
  52. package/package.json +32 -24
  53. package/dist/context.d.ts +0 -41
  54. package/dist/context.d.ts.map +0 -1
  55. package/dist/context.js +0 -38
  56. package/dist/context.test.d.ts +0 -2
  57. package/dist/context.test.d.ts.map +0 -1
  58. package/dist/context.test.js +0 -13
  59. package/dist/find-service-definition.d.ts.map +0 -1
  60. package/dist/find-service-definition.test.d.ts +0 -2
  61. package/dist/find-service-definition.test.d.ts.map +0 -1
  62. package/dist/find-service-definition.test.js +0 -44
  63. package/dist/get-saws-config.d.ts.map +0 -1
  64. package/dist/index.d.ts.map +0 -1
  65. package/dist/service-commands.d.ts +0 -10
  66. package/dist/service-commands.d.ts.map +0 -1
  67. package/dist/service-commands.js +0 -25
  68. package/dist/service-commands.test.d.ts +0 -2
  69. package/dist/service-commands.test.d.ts.map +0 -1
  70. package/dist/service-commands.test.js +0 -26
  71. package/dist/service-definition.d.ts +0 -36
  72. package/dist/service-definition.d.ts.map +0 -1
  73. package/dist/service-definition.js +0 -104
  74. package/dist/service-types.d.ts +0 -9
  75. package/dist/service-types.d.ts.map +0 -1
  76. /package/dist/{service-types.js → utils/get-service-path.d.ts} +0 -0
@@ -0,0 +1,119 @@
1
+ import { writeStageOutputs } from "./utils/stage-outputs.js";
2
+ import { parameterizedEnvVarName } from "./utils/parameterized-env-var-name.js";
3
+ import { SecretReference } from "./secrets-manager.js";
4
+ export class ServiceDefinition {
5
+ name;
6
+ dependencies;
7
+ environment;
8
+ outputs = {};
9
+ deved = false;
10
+ deployed = false;
11
+ runtimeLogSink;
12
+ constructor(config) {
13
+ this.name = config.name;
14
+ this.dependencies = config.dependencies ?? [];
15
+ this.environment = config.environment ?? {};
16
+ }
17
+ async init() {
18
+ return;
19
+ }
20
+ async dev() {
21
+ this.writeRuntimeLog(`Start dev ${this.name}\n`);
22
+ await this.forEachDependencyAsync(async (dependency) => {
23
+ if (dependency.deved)
24
+ return;
25
+ await dependency.dev();
26
+ dependency.deved = true;
27
+ });
28
+ }
29
+ async deploy(stage) {
30
+ console.log("Start deploy", this.name);
31
+ await this.forEachDependencyAsync(async (dependency) => {
32
+ if (dependency.deployed)
33
+ return;
34
+ await dependency.deploy(stage);
35
+ dependency.deployed = true;
36
+ });
37
+ }
38
+ setRuntimeLogSink(sink) {
39
+ this.runtimeLogSink = sink;
40
+ this.forEachDependency((dependency) => dependency.setRuntimeLogSink(sink));
41
+ }
42
+ writeRuntimeLog(chunk, stream = "stdout") {
43
+ if (this.runtimeLogSink == null) {
44
+ const output = stream === "stderr" ? process.stderr : process.stdout;
45
+ output.write(chunk);
46
+ return;
47
+ }
48
+ this.runtimeLogSink({
49
+ serviceName: this.name,
50
+ stream,
51
+ chunk,
52
+ timestamp: new Date(),
53
+ });
54
+ }
55
+ getRuntimeLogSink() {
56
+ return this.runtimeLogSink;
57
+ }
58
+ getOutputs() {
59
+ return this.outputs;
60
+ }
61
+ async setOutputs(outputs, stage) {
62
+ this.outputs = {
63
+ ...this.outputs,
64
+ ...outputs,
65
+ };
66
+ await writeStageOutputs({
67
+ [this.name]: this.outputs,
68
+ }, stage);
69
+ }
70
+ forEachDependency(callback) {
71
+ for (const dependency of this.dependencies) {
72
+ callback(dependency);
73
+ }
74
+ }
75
+ async forEachDependencyAsync(callback) {
76
+ for (const dependency of this.dependencies) {
77
+ await callback(dependency);
78
+ }
79
+ }
80
+ getAllDependencies() {
81
+ const all = [this];
82
+ for (const dependency of this.dependencies) {
83
+ all.push(dependency);
84
+ all.push(...dependency.getAllDependencies());
85
+ }
86
+ return all;
87
+ }
88
+ // this needs to be recursive down dependencies
89
+ exit() {
90
+ this.forEachDependency((dependency) => dependency.exit());
91
+ }
92
+ parameterizedEnvVarName(envVarName) {
93
+ return parameterizedEnvVarName(this.name, envVarName);
94
+ }
95
+ // this needs to be recursive down dependencies
96
+ async getEnvironmentVariables(stage, _target = "container") {
97
+ return {};
98
+ }
99
+ async getDependenciesEnvironmentVariables(stage, target = "container") {
100
+ const environmentVariables = {};
101
+ await this.forEachDependencyAsync(async (definition) => {
102
+ Object.assign(environmentVariables, await definition.getEnvironmentVariables(stage, target));
103
+ });
104
+ return environmentVariables;
105
+ }
106
+ async getStageEnvironmentVariables(stage) {
107
+ const environment = this.environment[stage] ?? {};
108
+ return Object.fromEntries(await Promise.all(Object.entries(environment).map(async ([name, value]) => [
109
+ name,
110
+ value instanceof SecretReference ? await value.resolve({ stage }) : value,
111
+ ])));
112
+ }
113
+ getStdOut() {
114
+ return null;
115
+ }
116
+ getStdErr() {
117
+ return null;
118
+ }
119
+ }
@@ -1,9 +1,2 @@
1
- import type { ServiceDefinition } from "./service-definition.js";
2
- /**
3
- * Finds one configured service by name in a dependency graph.
4
- *
5
- * Service names are CLI identifiers, so a name must resolve to exactly one
6
- * service instance. Reusing the same instance in multiple branches is allowed.
7
- */
1
+ import type { ServiceDefinition } from "./ServiceDefinition.js";
8
2
  export declare function findServiceDefinition(root: ServiceDefinition, name: string): ServiceDefinition;
9
- //# sourceMappingURL=find-service-definition.d.ts.map
@@ -1,9 +1,3 @@
1
- /**
2
- * Finds one configured service by name in a dependency graph.
3
- *
4
- * Service names are CLI identifiers, so a name must resolve to exactly one
5
- * service instance. Reusing the same instance in multiple branches is allowed.
6
- */
7
1
  export function findServiceDefinition(root, name) {
8
2
  const matches = [];
9
3
  const visited = new Set();
@@ -1,3 +1,7 @@
1
- import type { ServiceDefinition } from "./service-definition.js";
2
- export declare function getSawsConfig(configPath?: string): Promise<ServiceDefinition>;
3
- //# sourceMappingURL=get-saws-config.d.ts.map
1
+ import { type ServiceDefinition } from "./ServiceDefinition.js";
2
+ export type SawsConfigModule = {
3
+ default: ServiceDefinition;
4
+ [name: string]: unknown;
5
+ };
6
+ export declare function getSawsConfigModule(path?: string): Promise<SawsConfigModule>;
7
+ export declare function getSawsConfig(path?: string): Promise<ServiceDefinition>;
@@ -1,58 +1,8 @@
1
- import { pathToFileURL } from "node:url";
2
- import path from "node:path";
3
- import { access, rm, writeFile } from "node:fs/promises";
4
- import ts from "typescript";
5
- export async function getSawsConfig(configPath) {
6
- const resolvedPath = await resolveConfigPath(configPath);
7
- const importPath = resolvedPath.endsWith(".ts")
8
- ? await transpileConfig(resolvedPath)
9
- : resolvedPath;
10
- try {
11
- const moduleUrl = pathToFileURL(importPath).href;
12
- const imported = await import(`${moduleUrl}?t=${Date.now()}`);
13
- const serviceDefinition = imported.default ?? imported;
14
- if (serviceDefinition == null || typeof serviceDefinition.deploy !== "function") {
15
- throw new Error(`Expected ${resolvedPath} to export a ServiceDefinition as the default export`);
16
- }
17
- return serviceDefinition;
18
- }
19
- finally {
20
- if (importPath !== resolvedPath) {
21
- await rm(importPath, { force: true });
22
- }
23
- }
1
+ import { resolve } from "path";
2
+ export async function getSawsConfigModule(path = "./saws.ts") {
3
+ const pathToConfig = resolve(path);
4
+ return (await import(pathToConfig));
24
5
  }
25
- async function resolveConfigPath(configPath) {
26
- if (configPath != null)
27
- return path.resolve(configPath);
28
- for (const candidate of ["./saws.ts", "./saws.js"]) {
29
- const resolved = path.resolve(candidate);
30
- try {
31
- await access(resolved);
32
- return resolved;
33
- }
34
- catch (error) {
35
- if (error.code !== "ENOENT")
36
- throw error;
37
- }
38
- }
39
- return path.resolve("./saws.ts");
40
- }
41
- async function transpileConfig(configPath) {
42
- const outputPath = path.join(path.dirname(configPath), `.saws-${path.basename(configPath, ".ts")}-${process.pid}-${Date.now()}.mjs`);
43
- const source = await ts.sys.readFile(configPath);
44
- if (source == null) {
45
- throw new Error(`Unable to read ${configPath}`);
46
- }
47
- const transpiled = ts.transpileModule(source, {
48
- compilerOptions: {
49
- target: ts.ScriptTarget.ES2022,
50
- module: ts.ModuleKind.ES2022,
51
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
52
- esModuleInterop: true,
53
- },
54
- fileName: configPath,
55
- });
56
- await writeFile(outputPath, transpiled.outputText);
57
- return outputPath;
6
+ export async function getSawsConfig(path = "./saws.ts") {
7
+ return (await getSawsConfigModule(path)).default;
58
8
  }
@@ -0,0 +1,12 @@
1
+ export type HostExposure = "tunnel" | "public";
2
+ export interface HostReadinessConfig {
3
+ name: string;
4
+ deploymentUser: string;
5
+ deploymentPublicKey: string;
6
+ exposure: HostExposure;
7
+ sshPort: number;
8
+ allowedTcpPorts: number[];
9
+ }
10
+ export declare function getReadinessHash(config: HostReadinessConfig): string;
11
+ export declare function readinessCheckScript(config: HostReadinessConfig): string;
12
+ export declare function readinessConfigureScript(config: HostReadinessConfig): string;
@@ -0,0 +1,230 @@
1
+ import { createHash } from "node:crypto";
2
+ import { shellQuote } from "./utils/shell-quote.js";
3
+ const READINESS_VERSION = 4;
4
+ const READINESS_DIRECTORY = "/etc/saws";
5
+ const READINESS_FILE = `${READINESS_DIRECTORY}/host-readiness`;
6
+ const DEFAULT_APP_DIRECTORY = "/opt/saws";
7
+ export function getReadinessHash(config) {
8
+ return createHash("sha256")
9
+ .update(JSON.stringify({
10
+ version: READINESS_VERSION,
11
+ deploymentUser: config.deploymentUser,
12
+ deploymentPublicKeyHash: createHash("sha256")
13
+ .update(config.deploymentPublicKey)
14
+ .digest("hex"),
15
+ exposure: config.exposure,
16
+ sshPort: config.sshPort,
17
+ allowedTcpPorts: config.allowedTcpPorts,
18
+ }))
19
+ .digest("hex");
20
+ }
21
+ export function readinessCheckScript(config) {
22
+ const hash = getReadinessHash(config);
23
+ const configureCommand = `saws host configure ${config.name}`;
24
+ return `expected=${shellQuote(hash)}
25
+ actual=$(cat ${shellQuote(READINESS_FILE)} 2>/dev/null || true)
26
+ if [ "$actual" != "$expected" ]; then
27
+ echo ${shellQuote(`Host "${config.name}" is not configured for this deployment. Run: ${configureCommand}`)} >&2
28
+ exit 78
29
+ fi
30
+ deployment_user=${shellQuote(config.deploymentUser)}
31
+ deployment_public_key=${shellQuote(config.deploymentPublicKey)}
32
+ deployment_home=$(getent passwd "$deployment_user" | cut -d: -f6)
33
+ if [ -z "$deployment_home" ] ||
34
+ ! grep -Fqx -- "$deployment_public_key" "$deployment_home/.ssh/authorized_keys" 2>/dev/null; then
35
+ echo ${shellQuote(`Deployment SSH access has drifted on host "${config.name}". Run: ${configureCommand}`)} >&2
36
+ exit 78
37
+ fi
38
+ if [ ! -d ${shellQuote(DEFAULT_APP_DIRECTORY)} ] || [ ! -w ${shellQuote(DEFAULT_APP_DIRECTORY)} ]; then
39
+ echo ${shellQuote(`Deployment directory ${DEFAULT_APP_DIRECTORY} is not writable on host "${config.name}". Run: ${configureCommand}`)} >&2
40
+ exit 78
41
+ fi
42
+ if ! command -v docker >/dev/null 2>&1 || ! docker info >/dev/null 2>&1; then
43
+ echo ${shellQuote(`Docker is not ready on host "${config.name}". Run: ${configureCommand}`)} >&2
44
+ exit 78
45
+ fi
46
+ if ! systemctl is-active --quiet docker ||
47
+ ! systemctl is-active --quiet fail2ban ||
48
+ ! systemctl is-active --quiet ufw ||
49
+ ! systemctl is-active --quiet saws-docker-firewall ||
50
+ [ ! -r /etc/ssh/sshd_config.d/00-saws-hardening.conf ] ||
51
+ [ ! -r /etc/sysctl.d/60-saws-hardening.conf ] ||
52
+ [ ! -r /etc/apt/apt.conf.d/20auto-upgrades ] ||
53
+ [ ! -x /usr/local/sbin/saws-docker-firewall ]; then
54
+ echo ${shellQuote(`Host "${config.name}" has drifted from the required security baseline. Run: ${configureCommand}`)} >&2
55
+ exit 78
56
+ fi`;
57
+ }
58
+ export function readinessConfigureScript(config) {
59
+ const applicationPorts = config.allowedTcpPorts.join(" ");
60
+ const firewallPorts = [config.sshPort, ...config.allowedTcpPorts]
61
+ .filter((port, index, ports) => ports.indexOf(port) === index)
62
+ .join(" ");
63
+ const hash = getReadinessHash(config);
64
+ return `export DEBIAN_FRONTEND=noninteractive
65
+ deployment_user=${shellQuote(config.deploymentUser)}
66
+ deployment_public_key=${shellQuote(config.deploymentPublicKey)}
67
+ if [ -z "$deployment_user" ] || [ -z "$deployment_public_key" ]; then
68
+ echo "Deployment user and public key are required" >&2
69
+ exit 1
70
+ fi
71
+ if [ "$(uname -s)" != "Linux" ] || [ ! -r /etc/os-release ]; then
72
+ echo "SAWS host configuration supports Debian and Ubuntu Linux only" >&2
73
+ exit 1
74
+ fi
75
+ . /etc/os-release
76
+ case "\${ID:-}" in
77
+ debian|ubuntu) ;;
78
+ *)
79
+ echo "SAWS host configuration does not support \${PRETTY_NAME:-this operating system}" >&2
80
+ exit 1
81
+ ;;
82
+ esac
83
+
84
+ apt-get update
85
+ apt-get install -y ca-certificates fail2ban ufw unattended-upgrades
86
+ if ! command -v docker >/dev/null 2>&1; then
87
+ apt-get install -y docker.io
88
+ fi
89
+ systemctl enable --now docker
90
+ docker info >/dev/null
91
+
92
+ if ! id "$deployment_user" >/dev/null 2>&1; then
93
+ useradd --create-home --shell /bin/bash "$deployment_user"
94
+ fi
95
+ if [ "$deployment_user" != root ]; then
96
+ usermod -aG docker "$deployment_user"
97
+ fi
98
+
99
+ deployment_home=$(getent passwd "$deployment_user" | cut -d: -f6)
100
+ deployment_group=$(id -gn "$deployment_user")
101
+ if [ -z "$deployment_home" ] || [ ! -d "$deployment_home" ]; then
102
+ echo "Deployment user $deployment_user does not have a usable home directory" >&2
103
+ exit 1
104
+ fi
105
+ chown "$deployment_user:$deployment_group" "$deployment_home"
106
+ install -d -m 0700 -o "$deployment_user" -g "$deployment_group" "$deployment_home/.ssh"
107
+ install -d -m 0755 -o "$deployment_user" -g "$deployment_group" ${shellQuote(DEFAULT_APP_DIRECTORY)}
108
+ authorized_keys="$deployment_home/.ssh/authorized_keys"
109
+ touch "$authorized_keys"
110
+ chown "$deployment_user:$deployment_group" "$authorized_keys"
111
+ chmod 0600 "$authorized_keys"
112
+ if ! grep -Fqx -- "$deployment_public_key" "$authorized_keys"; then
113
+ printf '%s\\n' "$deployment_public_key" >> "$authorized_keys"
114
+ fi
115
+ if ! grep -Fqx -- "$deployment_public_key" "$authorized_keys"; then
116
+ echo "Refusing to disable SSH passwords: deployment public key installation failed" >&2
117
+ exit 1
118
+ fi
119
+
120
+ install -d -m 0755 /etc/ssh/sshd_config.d
121
+ rm -f /etc/ssh/sshd_config.d/60-saws-hardening.conf
122
+ cat > /etc/ssh/sshd_config.d/00-saws-hardening.conf <<'EOF'
123
+ PasswordAuthentication no
124
+ KbdInteractiveAuthentication no
125
+ PermitEmptyPasswords no
126
+ PermitRootLogin prohibit-password
127
+ PubkeyAuthentication yes
128
+ X11Forwarding no
129
+ MaxAuthTries 3
130
+ EOF
131
+ sshd -t
132
+ sshd -T | grep -qx 'passwordauthentication no'
133
+ sshd -T | grep -qx 'kbdinteractiveauthentication no'
134
+ sshd -T | grep -qx 'pubkeyauthentication yes'
135
+
136
+ cat > /etc/sysctl.d/60-saws-hardening.conf <<'EOF'
137
+ net.ipv4.conf.all.accept_redirects=0
138
+ net.ipv4.conf.default.accept_redirects=0
139
+ net.ipv4.conf.all.send_redirects=0
140
+ net.ipv4.conf.default.send_redirects=0
141
+ net.ipv4.conf.all.rp_filter=1
142
+ net.ipv4.conf.default.rp_filter=1
143
+ net.ipv4.tcp_syncookies=1
144
+ net.ipv6.conf.all.accept_redirects=0
145
+ net.ipv6.conf.default.accept_redirects=0
146
+ EOF
147
+ sysctl --system >/dev/null
148
+
149
+ cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
150
+ APT::Periodic::Update-Package-Lists "1";
151
+ APT::Periodic::Unattended-Upgrade "1";
152
+ EOF
153
+ install -d -m 0755 /etc/fail2ban/jail.d
154
+ cat > /etc/fail2ban/jail.d/saws.conf <<EOF
155
+ [sshd]
156
+ enabled = true
157
+ port = ${config.sshPort}
158
+ EOF
159
+ systemctl enable --now fail2ban
160
+ systemctl restart fail2ban
161
+
162
+ ufw --force reset
163
+ ufw default deny incoming
164
+ ufw default allow outgoing
165
+ for port in ${firewallPorts}; do
166
+ ufw allow "$port/tcp"
167
+ done
168
+ ufw --force enable
169
+
170
+ cat > /usr/local/sbin/saws-docker-firewall <<EOF
171
+ #!/bin/sh
172
+ set -eu
173
+ application_ports="${applicationPorts}"
174
+
175
+ external_interface=\\$(ip -4 route list default | awk 'NR == 1 { print \\$5 }')
176
+ if [ -z "\\$external_interface" ]; then
177
+ echo "Could not determine the host's external network interface" >&2
178
+ exit 1
179
+ fi
180
+ iptables -N DOCKER-USER 2>/dev/null || true
181
+ iptables -N SAWS-DOCKER 2>/dev/null || true
182
+ iptables -F SAWS-DOCKER
183
+ iptables -A SAWS-DOCKER -i "\\$external_interface" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
184
+ for port in \\$application_ports; do
185
+ iptables -A SAWS-DOCKER -i "\\$external_interface" -p tcp -m conntrack --ctorigdstport "\\$port" -j ACCEPT
186
+ done
187
+ iptables -A SAWS-DOCKER -i "\\$external_interface" -j DROP
188
+ iptables -A SAWS-DOCKER -j RETURN
189
+ iptables -C DOCKER-USER -j SAWS-DOCKER 2>/dev/null || iptables -I DOCKER-USER 1 -j SAWS-DOCKER
190
+
191
+ if ip6tables -nL DOCKER-USER >/dev/null 2>&1; then
192
+ external_interface6=\\$(ip -6 route list default | awk 'NR == 1 { print \\$5 }')
193
+ if [ -n "\\$external_interface6" ]; then
194
+ ip6tables -N SAWS-DOCKER 2>/dev/null || true
195
+ ip6tables -F SAWS-DOCKER
196
+ ip6tables -A SAWS-DOCKER -i "\\$external_interface6" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
197
+ for port in \\$application_ports; do
198
+ ip6tables -A SAWS-DOCKER -i "\\$external_interface6" -p tcp -m conntrack --ctorigdstport "\\$port" -j ACCEPT
199
+ done
200
+ ip6tables -A SAWS-DOCKER -i "\\$external_interface6" -j DROP
201
+ ip6tables -A SAWS-DOCKER -j RETURN
202
+ ip6tables -C DOCKER-USER -j SAWS-DOCKER 2>/dev/null || ip6tables -I DOCKER-USER 1 -j SAWS-DOCKER
203
+ fi
204
+ fi
205
+ EOF
206
+ chmod 0755 /usr/local/sbin/saws-docker-firewall
207
+ cat > /etc/systemd/system/saws-docker-firewall.service <<'EOF'
208
+ [Unit]
209
+ Description=SAWS Docker firewall rules
210
+ After=network-online.target docker.service ufw.service
211
+ Wants=network-online.target docker.service
212
+
213
+ [Service]
214
+ Type=oneshot
215
+ RemainAfterExit=yes
216
+ ExecStart=/usr/local/sbin/saws-docker-firewall
217
+
218
+ [Install]
219
+ WantedBy=multi-user.target
220
+ EOF
221
+ systemctl daemon-reload
222
+ systemctl enable saws-docker-firewall
223
+ systemctl restart saws-docker-firewall
224
+
225
+ systemctl reload ssh 2>/dev/null || systemctl reload sshd
226
+ install -d -m 0755 ${shellQuote(READINESS_DIRECTORY)}
227
+ printf '%s\\n' ${shellQuote(hash)} > ${shellQuote(READINESS_FILE)}
228
+ chmod 0644 ${shellQuote(READINESS_FILE)}
229
+ echo "SAWS host configuration complete"`;
230
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- export * from "./context.js";
1
+ export * from "./ServiceDefinition.js";
2
2
  export * from "./find-service-definition.js";
3
3
  export * from "./get-saws-config.js";
4
- export * from "./service-commands.js";
5
- export * from "./service-definition.js";
6
- export * from "./service-types.js";
7
- //# sourceMappingURL=index.d.ts.map
4
+ export * from "./Host.js";
5
+ export * from "./host-readiness.js";
6
+ export * from "./secrets-manager.js";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- export * from "./context.js";
1
+ export * from "./ServiceDefinition.js";
2
2
  export * from "./find-service-definition.js";
3
3
  export * from "./get-saws-config.js";
4
- export * from "./service-commands.js";
5
- export * from "./service-definition.js";
6
- export * from "./service-types.js";
4
+ export * from "./Host.js";
5
+ export * from "./host-readiness.js";
6
+ export * from "./secrets-manager.js";
@@ -0,0 +1,63 @@
1
+ export interface SecretsManagerConfig {
2
+ /** Overrides the stage derived from the command's runtime context or STAGE. */
3
+ stage?: string;
4
+ rootDir?: string;
5
+ /** Overrides SAWS_SECRETS_PASSCODE and the project-root .env file. */
6
+ passcode?: string;
7
+ }
8
+ export type SecretScope = "stage" | "global";
9
+ export interface SecretResolutionContext {
10
+ stage: string;
11
+ rootDir?: string;
12
+ }
13
+ export declare class ParameterNotFoundError extends Error {
14
+ constructor(name: string);
15
+ }
16
+ export declare class SecretsDecryptionError extends Error {
17
+ constructor();
18
+ }
19
+ /**
20
+ * A lazy reference to either a stage-aware or global secret.
21
+ *
22
+ * References can safely be created while saws.ts is loaded, before the CLI
23
+ * knows which runtime stage will consume a stage-aware secret.
24
+ */
25
+ export declare class SecretReference {
26
+ readonly name: string;
27
+ readonly scope: SecretScope;
28
+ private readonly manager;
29
+ constructor(manager: SecretsManager, name: string, scope: SecretScope);
30
+ resolve(context?: SecretResolutionContext): Promise<string>;
31
+ /** @internal */
32
+ isManagedBy(manager: SecretsManager): boolean;
33
+ }
34
+ export declare class GlobalSecrets {
35
+ private readonly manager;
36
+ constructor(manager: SecretsManager);
37
+ reference(name: string): SecretReference;
38
+ get(name: string): Promise<string>;
39
+ set(name: string, value: string): Promise<void>;
40
+ get secretsFilePath(): string;
41
+ }
42
+ export declare class SecretsManager {
43
+ readonly global: GlobalSecrets;
44
+ readonly rootDir: string;
45
+ private readonly configuredStage?;
46
+ private readonly configuredPasscode?;
47
+ constructor(config?: SecretsManagerConfig);
48
+ get stage(): string;
49
+ reference(name: string): SecretReference;
50
+ get(name: string): Promise<string>;
51
+ set(name: string, value: string): Promise<void>;
52
+ get secretsFilePath(): string;
53
+ /** @internal */
54
+ getFromScope(name: string, scope: SecretScope, resolutionStage?: string): Promise<string>;
55
+ /** @internal */
56
+ setInScope(name: string, value: string, scope: SecretScope, resolutionStage?: string): Promise<void>;
57
+ /** @internal */
58
+ getSecretsFilePath(scope: SecretScope, resolutionStage?: string): string;
59
+ private resolveStage;
60
+ private readSecrets;
61
+ private writeSecrets;
62
+ private getPasscode;
63
+ }