@stacksjs/ts-cloud 0.2.26 → 0.3.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/dist/aws/ec2.d.ts +23 -0
- package/dist/aws/lambda.d.ts +17 -2
- package/dist/bin/cli.js +1012 -914
- package/dist/deploy/serverless-app.d.ts +53 -0
- package/dist/deploy/serverless-app.test.d.ts +1 -0
- package/dist/deploy/serverless-image.d.ts +32 -0
- package/dist/deploy/site-target.d.ts +7 -1
- package/dist/drivers/aws/driver.d.ts +21 -0
- package/dist/drivers/aws/provision.d.ts +41 -0
- package/dist/drivers/hetzner/client.d.ts +46 -0
- package/dist/drivers/hetzner/cloud-init.d.ts +7 -18
- package/dist/drivers/hetzner/driver.d.ts +24 -0
- package/dist/drivers/hetzner/state.d.ts +8 -2
- package/dist/drivers/shared/backups.d.ts +28 -0
- package/dist/drivers/shared/certbot.d.ts +38 -0
- package/dist/drivers/shared/compute-deploy.d.ts +1 -1
- package/dist/drivers/shared/compute-provision.d.ts +29 -0
- package/dist/drivers/shared/db-provision.d.ts +34 -0
- package/dist/drivers/shared/deploy-script.d.ts +0 -3
- package/dist/drivers/shared/env-file.d.ts +12 -0
- package/dist/drivers/shared/fleet.d.ts +34 -0
- package/dist/drivers/shared/git-deploy.d.ts +36 -0
- package/dist/drivers/shared/image-recipe.d.ts +40 -0
- package/dist/drivers/shared/laravel-deploy.d.ts +42 -0
- package/dist/drivers/shared/laravel-services.d.ts +36 -0
- package/dist/drivers/shared/maintenance.d.ts +10 -0
- package/dist/drivers/shared/monitoring.d.ts +15 -0
- package/dist/drivers/shared/nginx-vhost.d.ts +88 -0
- package/dist/drivers/shared/notifications.d.ts +40 -0
- package/dist/drivers/shared/package-manager.d.ts +84 -0
- package/dist/drivers/shared/php-provision.d.ts +36 -0
- package/dist/drivers/shared/releases.d.ts +52 -0
- package/dist/drivers/shared/ssh-keys.d.ts +21 -0
- package/dist/drivers/shared/ubuntu-bootstrap.d.ts +54 -0
- package/dist/drivers/shared/ufw.d.ts +17 -0
- package/dist/index.js +38356 -35292
- package/dist/security/pre-deploy-scanner.d.ts +12 -0
- package/package.json +3 -3
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serverless application deploy orchestrator (Laravel-Vapor-equivalent).
|
|
3
|
+
*
|
|
4
|
+
* Drives the full pipeline for `environments.<env>.app`:
|
|
5
|
+
* build hooks → package → ensure artifact bucket → upload artifact →
|
|
6
|
+
* deploy/update CloudFormation stack → update function code + env →
|
|
7
|
+
* sync assets → run deploy hooks → health check.
|
|
8
|
+
*
|
|
9
|
+
* Plus the operational verbs: redeploy (no rebuild), rollback (previous
|
|
10
|
+
* release), and maintenance mode (down/up).
|
|
11
|
+
*/
|
|
12
|
+
import type { CloudConfig, EnvironmentType, ServerlessAppConfig } from '@ts-cloud/core';
|
|
13
|
+
export interface DeployServerlessOptions {
|
|
14
|
+
/** Project root for entry resolution + build hooks. @default process.cwd() */
|
|
15
|
+
projectRoot?: string;
|
|
16
|
+
/** Skip the local build hooks (already run). */
|
|
17
|
+
skipBuild?: boolean;
|
|
18
|
+
/** Skip running the post-deploy `deploy` hooks. */
|
|
19
|
+
skipDeployHooks?: boolean;
|
|
20
|
+
/** Skip the post-deploy HTTP health check. */
|
|
21
|
+
skipHealthCheck?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface ResolvedContext {
|
|
24
|
+
app: ServerlessAppConfig;
|
|
25
|
+
slug: string;
|
|
26
|
+
region: string;
|
|
27
|
+
stackName: string;
|
|
28
|
+
artifactBucket: string;
|
|
29
|
+
assetsBucket: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Derive framework env vars (DB/Redis hosts) from the deployed stack outputs so
|
|
33
|
+
* a Laravel app connects to the Aurora/RDS-Proxy/ElastiCache resources the
|
|
34
|
+
* composer created. Returns an empty map when nothing data-related is attached.
|
|
35
|
+
*/
|
|
36
|
+
export declare function infraEnvFromOutputs(app: ServerlessAppConfig, outputs: Record<string, string>): Record<string, string>;
|
|
37
|
+
/** Build the complete environment for one function. Exported for testing. */
|
|
38
|
+
export declare function buildFunctionEnv(app: ServerlessAppConfig, ctx: ResolvedContext, environment: EnvironmentType, mode: 'http' | 'queue' | 'cli', secrets: Record<string, string>, assetUrl: string | undefined, queueName: string | undefined, infraEnv?: Record<string, string>): Record<string, string>;
|
|
39
|
+
/** Code source for a function update: an S3 zip or an ECR image. */
|
|
40
|
+
export type CodeSource = {
|
|
41
|
+
kind: 'zip';
|
|
42
|
+
bucket: string;
|
|
43
|
+
key: string;
|
|
44
|
+
} | {
|
|
45
|
+
kind: 'image';
|
|
46
|
+
imageUri: string;
|
|
47
|
+
};
|
|
48
|
+
export declare function deployServerlessApp(config: CloudConfig, environment: EnvironmentType, opts?: DeployServerlessOptions): Promise<void>;
|
|
49
|
+
export declare function redeployServerlessApp(config: CloudConfig, environment: EnvironmentType): Promise<void>;
|
|
50
|
+
export declare function rollbackServerlessApp(config: CloudConfig, environment: EnvironmentType): Promise<void>;
|
|
51
|
+
export declare function setMaintenance(config: CloudConfig, environment: EnvironmentType, enabled: boolean, bypassSecret?: string): Promise<void>;
|
|
52
|
+
/** Invoke the CLI function with an arbitrary command (e.g. `cloud command "migrate"`). */
|
|
53
|
+
export declare function runRemoteCommand(config: CloudConfig, environment: EnvironmentType, command: string): Promise<string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container-image build + push for serverless apps using `packaging: 'image'`.
|
|
3
|
+
*
|
|
4
|
+
* Stages a Docker build context (app files + a generated Dockerfile, plus the
|
|
5
|
+
* PHP runtime assets for PHP apps), builds the image, ensures an ECR repository,
|
|
6
|
+
* logs in, and pushes a content-tagged image. Returns the image URI the deploy
|
|
7
|
+
* orchestrator passes to CloudFormation + UpdateFunctionCode.
|
|
8
|
+
*/
|
|
9
|
+
import type { ServerlessAppConfig } from '@ts-cloud/core';
|
|
10
|
+
export interface BuildImageOptions {
|
|
11
|
+
app: ServerlessAppConfig;
|
|
12
|
+
projectRoot: string;
|
|
13
|
+
region: string;
|
|
14
|
+
/** ECR repository name (created if missing). */
|
|
15
|
+
repository: string;
|
|
16
|
+
/** Skip local build hooks. */
|
|
17
|
+
skipBuild?: boolean;
|
|
18
|
+
onStep?: (m: string) => void;
|
|
19
|
+
}
|
|
20
|
+
export interface BuiltImage {
|
|
21
|
+
/** Full image URI including the content tag (repoUri:tag). */
|
|
22
|
+
imageUri: string;
|
|
23
|
+
/** The content tag (artifact hash). */
|
|
24
|
+
tag: string;
|
|
25
|
+
/** Lambda handler strings per function. */
|
|
26
|
+
handlers: {
|
|
27
|
+
http: string;
|
|
28
|
+
queue: string;
|
|
29
|
+
cli: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export declare function buildAndPushServerlessImage(opts: BuildImageOptions): Promise<BuiltImage>;
|
|
@@ -7,7 +7,13 @@ import type { CloudConfig, SiteConfig, SiteDeployTarget } from '@ts-cloud/core';
|
|
|
7
7
|
* site built and shipped to `/var/www/<site>` on the box
|
|
8
8
|
* (served by the operator's own proxy, e.g. rpx + tlsx).
|
|
9
9
|
*/
|
|
10
|
-
export type SiteDeployKind = 'bucket' | 'server-app' | 'server-static';
|
|
10
|
+
export type SiteDeployKind = 'bucket' | 'server-app' | 'server-static' | 'server-php';
|
|
11
|
+
/**
|
|
12
|
+
* A PHP/Laravel site: deployed to the compute box via git clone into atomic
|
|
13
|
+
* release directories and served by nginx + php-fpm. Identified by a PHP
|
|
14
|
+
* `type` (laravel/php/statamic/wordpress).
|
|
15
|
+
*/
|
|
16
|
+
export declare function isPhpSite(site: SiteConfig): boolean;
|
|
11
17
|
/**
|
|
12
18
|
* Resolve the explicit-or-inferred {@link SiteDeployTarget} for a site.
|
|
13
19
|
*
|
|
@@ -8,9 +8,30 @@ export declare class AwsDriver implements CloudDriver {
|
|
|
8
8
|
private region;
|
|
9
9
|
constructor(options?: AwsDriverOptions);
|
|
10
10
|
private resolveRegion;
|
|
11
|
+
/**
|
|
12
|
+
* Provision a single Ubuntu EC2 box for the Forge/PHP path — mirroring the
|
|
13
|
+
* Hetzner driver, bypassing the heavy CloudFormation stack. Boots the shared
|
|
14
|
+
* Ubuntu bootstrap (or a baked golden AMI), fronted by a security group, and
|
|
15
|
+
* tagged so deploys (SSM) find it. Idempotent: reuses a running instance.
|
|
16
|
+
*
|
|
17
|
+
* NOTE: deploys run over SSM, so the instance needs the SSM agent + an IAM
|
|
18
|
+
* instance profile granting AmazonSSMManagedInstanceCore. Provide it via
|
|
19
|
+
* `compute.server.iamInstanceProfile` (live-verified step).
|
|
20
|
+
*/
|
|
21
|
+
provisionComputeInfrastructure(options: ProvisionComputeOptions): Promise<ComputeStackOutputs>;
|
|
22
|
+
/** Terminate the lightweight EC2 box + delete its security group. */
|
|
23
|
+
destroyCompute(options: ProvisionComputeOptions): Promise<{
|
|
24
|
+
destroyed: string[];
|
|
25
|
+
}>;
|
|
11
26
|
getComputeOutputs(options: ProvisionComputeOptions): Promise<ComputeStackOutputs>;
|
|
12
27
|
uploadRelease(options: UploadReleaseOptions): Promise<UploadReleaseResult>;
|
|
13
28
|
findComputeTargets(options: FindComputeTargetsOptions): Promise<ComputeTarget[]>;
|
|
29
|
+
/**
|
|
30
|
+
* SSM AWS-RunShellScript executes the joined commands with `/bin/sh` (dash on
|
|
31
|
+
* Ubuntu), which rejects bash-only syntax like `set -o pipefail`. Our deploy
|
|
32
|
+
* scripts are bash, so run them through a bash heredoc.
|
|
33
|
+
*/
|
|
34
|
+
private bashWrap;
|
|
14
35
|
runRemoteDeploy(options: RunRemoteDeployOptions): Promise<RemoteDeployResult>;
|
|
15
36
|
private pollSsmCommand;
|
|
16
37
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS EC2 provisioning composition for the Forge/PHP path — the pure, testable
|
|
3
|
+
* pieces shared by {@link import('./driver').AwsDriver.provisionComputeInfrastructure}.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the Hetzner path: boot a single **Ubuntu** box (same base as Hetzner,
|
|
6
|
+
* so the apt provisioning + nginx vhosts + php-fpm sockets are identical) with
|
|
7
|
+
* the shared bootstrap as UserData, fronted by a security group. The live API
|
|
8
|
+
* orchestration (AMI resolve, VPC/subnet, runInstances, wait) lives in the
|
|
9
|
+
* driver; this module builds the inputs.
|
|
10
|
+
*/
|
|
11
|
+
import type { CloudConfig } from '@ts-cloud/core';
|
|
12
|
+
/**
|
|
13
|
+
* SSM public parameter for the latest Canonical Ubuntu 24.04 (Noble) AMI —
|
|
14
|
+
* region-agnostic, so we never hardcode region-specific AMI ids.
|
|
15
|
+
*/
|
|
16
|
+
export declare const UBUNTU_AMI_SSM_PARAM = "/aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id";
|
|
17
|
+
/** A security-group ingress rule. */
|
|
18
|
+
export interface AwsIngressRule {
|
|
19
|
+
port: number;
|
|
20
|
+
protocol: 'tcp';
|
|
21
|
+
cidr: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Ingress rules for a PHP/app box: SSH (deploy fallback), HTTP, HTTPS, and any
|
|
25
|
+
* extra app ports any site declares. Deploys themselves run over SSM.
|
|
26
|
+
*/
|
|
27
|
+
export declare function awsComputeIngressRules(config: CloudConfig): AwsIngressRule[];
|
|
28
|
+
/**
|
|
29
|
+
* Build the EC2 UserData (raw bash, not yet base64) from the shared Ubuntu
|
|
30
|
+
* bootstrap — the exact same recipe Hetzner uses. Honors a baked golden image
|
|
31
|
+
* (`compute.bakedImage`) by skipping the install-heavy steps.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildAwsUserData(config: CloudConfig): string;
|
|
34
|
+
/** Base64-encode UserData for the EC2 RunInstances API. */
|
|
35
|
+
export declare function encodeUserData(userData: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the AMI to boot: an explicit `compute.image` (a golden AMI), else the
|
|
38
|
+
* caller resolves {@link UBUNTU_AMI_SSM_PARAM} via SSM. Returns the explicit id
|
|
39
|
+
* or `null` to signal "resolve Ubuntu via SSM".
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveAwsImageId(config: CloudConfig): string | null;
|
|
@@ -76,6 +76,44 @@ export interface CreateServerOptions {
|
|
|
76
76
|
firewalls?: Array<{
|
|
77
77
|
firewall: number;
|
|
78
78
|
}>;
|
|
79
|
+
/** Private networks to attach the server to (fleet topology). */
|
|
80
|
+
networks?: number[];
|
|
81
|
+
}
|
|
82
|
+
export interface HetznerNetwork {
|
|
83
|
+
id: number;
|
|
84
|
+
name: string;
|
|
85
|
+
ip_range: string;
|
|
86
|
+
}
|
|
87
|
+
export interface HetznerLoadBalancer {
|
|
88
|
+
id: number;
|
|
89
|
+
name: string;
|
|
90
|
+
public_net?: {
|
|
91
|
+
ipv4?: {
|
|
92
|
+
ip?: string;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export interface CreateNetworkOptions {
|
|
97
|
+
name: string;
|
|
98
|
+
ipRange?: string;
|
|
99
|
+
labels?: Record<string, string>;
|
|
100
|
+
}
|
|
101
|
+
export interface CreateLoadBalancerOptions {
|
|
102
|
+
name: string;
|
|
103
|
+
/** LB type (e.g. 'lb11'). @default 'lb11' */
|
|
104
|
+
type?: string;
|
|
105
|
+
location?: string;
|
|
106
|
+
networkZone?: string;
|
|
107
|
+
network?: number;
|
|
108
|
+
labels?: Record<string, string>;
|
|
109
|
+
/** Listener services (e.g. 80→80, 443→443). */
|
|
110
|
+
services: Array<{
|
|
111
|
+
listenPort: number;
|
|
112
|
+
destinationPort: number;
|
|
113
|
+
protocol?: 'tcp' | 'http';
|
|
114
|
+
}>;
|
|
115
|
+
/** Target app servers by label selector (e.g. `ts-cloud/role=app`). */
|
|
116
|
+
labelSelector: string;
|
|
79
117
|
}
|
|
80
118
|
export interface CreateFirewallOptions {
|
|
81
119
|
name: string;
|
|
@@ -111,6 +149,12 @@ export declare class HetznerClient {
|
|
|
111
149
|
server: HetznerServer;
|
|
112
150
|
action: HetznerAction;
|
|
113
151
|
}>;
|
|
152
|
+
listNetworks(): Promise<HetznerNetwork[]>;
|
|
153
|
+
createNetwork(options: CreateNetworkOptions): Promise<HetznerNetwork>;
|
|
154
|
+
deleteNetwork(id: number): Promise<void>;
|
|
155
|
+
listLoadBalancers(): Promise<HetznerLoadBalancer[]>;
|
|
156
|
+
createLoadBalancer(options: CreateLoadBalancerOptions): Promise<HetznerLoadBalancer>;
|
|
157
|
+
deleteLoadBalancer(id: number): Promise<void>;
|
|
114
158
|
deleteServer(id: number): Promise<HetznerAction>;
|
|
115
159
|
listFirewalls(): Promise<HetznerFirewall[]>;
|
|
116
160
|
createFirewall(options: CreateFirewallOptions): Promise<{
|
|
@@ -122,6 +166,8 @@ export declare class HetznerClient {
|
|
|
122
166
|
* firewall's rules in sync with the desired config without recreating it.
|
|
123
167
|
*/
|
|
124
168
|
setFirewallRules(firewallId: number, rules: HetznerFirewallRule[]): Promise<HetznerAction[]>;
|
|
169
|
+
/** Delete a firewall. A firewall still applied to a server cannot be deleted. */
|
|
170
|
+
deleteFirewall(firewallId: number): Promise<void>;
|
|
125
171
|
applyFirewallToResources(firewallId: number, applyTo: Array<{
|
|
126
172
|
type: 'server';
|
|
127
173
|
server: number;
|
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Hetzner cloud-init: wraps the shared Ubuntu bootstrap as #cloud-config.
|
|
3
|
+
*
|
|
4
|
+
* The provisioning recipe itself lives in
|
|
5
|
+
* {@link import('../shared/ubuntu-bootstrap')} (shared with the AWS path and
|
|
6
|
+
* the golden-image bake). `generateUbuntuAppCloudInit` is kept as a back-compat
|
|
7
|
+
* alias for the shared builder.
|
|
5
8
|
*/
|
|
6
|
-
export
|
|
7
|
-
runtime?: 'bun' | 'node' | 'deno';
|
|
8
|
-
runtimeVersion?: string;
|
|
9
|
-
systemPackages?: string[];
|
|
10
|
-
database?: 'sqlite' | 'mysql' | 'postgres';
|
|
11
|
-
caddyfile?: string;
|
|
12
|
-
/**
|
|
13
|
-
* Shell commands that install + start the rpx reverse-proxy gateway, built by
|
|
14
|
-
* {@link import('../shared/rpx-gateway').buildRpxProvisionScript}. Appended
|
|
15
|
-
* after the runtime is installed so `bun add -g @stacksjs/rpx` works. Mutually
|
|
16
|
-
* exclusive with `caddyfile` (the box runs one gateway).
|
|
17
|
-
*/
|
|
18
|
-
rpxProvision?: string[];
|
|
19
|
-
}
|
|
20
|
-
export declare function generateUbuntuAppCloudInit(options?: UbuntuBootstrapOptions): string;
|
|
9
|
+
export { buildUbuntuBootstrapScript as generateUbuntuAppCloudInit, type UbuntuBootstrapOptions } from '../shared/ubuntu-bootstrap';
|
|
21
10
|
/**
|
|
22
11
|
* Wrap a bash bootstrap script as Hetzner cloud-init user_data (#cloud-config).
|
|
23
12
|
*
|
|
@@ -41,6 +41,21 @@ export declare class HetznerDriver implements CloudDriver {
|
|
|
41
41
|
private bootWait;
|
|
42
42
|
constructor(options?: HetznerDriverOptions);
|
|
43
43
|
provisionComputeInfrastructure(options: ProvisionComputeOptions): Promise<ComputeStackOutputs>;
|
|
44
|
+
/**
|
|
45
|
+
* Provision a load-balanced fleet: a private network, a dedicated services
|
|
46
|
+
* box (DB/cache/search), N app servers (nginx + php-fpm), and a load
|
|
47
|
+
* balancer fronting the app servers. App servers connect to the services box
|
|
48
|
+
* over the private network (wired into their `.env` at deploy time).
|
|
49
|
+
*/
|
|
50
|
+
private provisionFleet;
|
|
51
|
+
/**
|
|
52
|
+
* Tear down the compute — single server or full fleet (load balancer, all
|
|
53
|
+
* app + services servers, firewalls, and the private network) — and clear
|
|
54
|
+
* local state.
|
|
55
|
+
*/
|
|
56
|
+
destroyCompute(options: ProvisionComputeOptions): Promise<{
|
|
57
|
+
destroyed: string[];
|
|
58
|
+
}>;
|
|
44
59
|
getComputeOutputs(options: ProvisionComputeOptions): Promise<ComputeStackOutputs>;
|
|
45
60
|
uploadRelease(options: UploadReleaseOptions): Promise<UploadReleaseResult>;
|
|
46
61
|
findComputeTargets(options: FindComputeTargetsOptions): Promise<ComputeTarget[]>;
|
|
@@ -87,6 +102,15 @@ export declare class HetznerDriver implements CloudDriver {
|
|
|
87
102
|
*/
|
|
88
103
|
private waitForCloudInit;
|
|
89
104
|
private outputsFromState;
|
|
105
|
+
/**
|
|
106
|
+
* SSH options for connecting to freshly-created cloud servers. Host-key
|
|
107
|
+
* pinning is disabled (`StrictHostKeyChecking=no` + `UserKnownHostsFile`
|
|
108
|
+
* `/dev/null`): the box is identified + trusted via the Hetzner API, and
|
|
109
|
+
* providers recycle public IPs, so a stale `known_hosts` entry from a prior
|
|
110
|
+
* (now-deleted) server would otherwise abort the deploy with
|
|
111
|
+
* "REMOTE HOST IDENTIFICATION HAS CHANGED".
|
|
112
|
+
*/
|
|
113
|
+
private static readonly SSH_HOST_KEY_OPTS;
|
|
90
114
|
private sshBaseArgs;
|
|
91
115
|
private scpToHost;
|
|
92
116
|
private sshExec;
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
export interface HetznerDriverState {
|
|
2
2
|
provider: 'hetzner';
|
|
3
3
|
stackName: string;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/** Absent after teardown (destroyCompute clears it). */
|
|
5
|
+
serverId?: number;
|
|
6
|
+
serverName?: string;
|
|
6
7
|
firewallId?: number;
|
|
7
8
|
publicIp?: string;
|
|
8
9
|
deployStoragePath?: string;
|
|
9
10
|
sshUser?: string;
|
|
11
|
+
/** Fleet: network/LB ids + the services box private IP, for deploy + teardown. */
|
|
12
|
+
networkId?: number;
|
|
13
|
+
loadBalancerId?: number;
|
|
14
|
+
servicesServerId?: number;
|
|
15
|
+
servicesPrivateIp?: string;
|
|
10
16
|
}
|
|
11
17
|
export declare function driverStatePath(stackName: string): string;
|
|
12
18
|
export declare function readDriverState(stackName: string): Promise<HetznerDriverState | null>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduled database backups powered by `ts-backups`, synced to object storage.
|
|
3
|
+
*
|
|
4
|
+
* On the box we generate a `ts-backups` config from the ts-cloud database
|
|
5
|
+
* config, install the tool (Bun + `ts-backups`), and add a cron job that runs
|
|
6
|
+
* the backup and syncs the output directory to an S3-compatible bucket
|
|
7
|
+
* (AWS S3 or Hetzner object storage). `ts-backups` handles dump + local
|
|
8
|
+
* retention; ts-cloud handles the off-box copy.
|
|
9
|
+
*/
|
|
10
|
+
import type { ComputeBackupConfig, DatabaseConfig } from '@ts-cloud/core';
|
|
11
|
+
/** Where backups are written on the box before being synced off. */
|
|
12
|
+
export declare const BACKUP_OUTPUT_DIR = "/var/backups/ts-cloud";
|
|
13
|
+
/** Generated ts-backups config location. */
|
|
14
|
+
export declare const BACKUP_CONFIG_PATH = "/etc/ts-cloud/backups.config.ts";
|
|
15
|
+
/** Cron file + runner paths. */
|
|
16
|
+
export declare const BACKUP_CRON_PATH = "/etc/cron.d/ts-cloud-backups";
|
|
17
|
+
export declare const BACKUP_RUNNER_PATH = "/usr/local/bin/ts-cloud-backup.sh";
|
|
18
|
+
/** Generate the `ts-backups` config file content from the database config. */
|
|
19
|
+
export declare function buildBackupsConfigTs(database: DatabaseConfig | undefined, backups: ComputeBackupConfig): string;
|
|
20
|
+
export interface BackupProvisionOptions {
|
|
21
|
+
database?: DatabaseConfig;
|
|
22
|
+
backups: ComputeBackupConfig;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build the commands that install + schedule backups. Returns `[]` when
|
|
26
|
+
* disabled. Assumes the box can install Bun (to run `ts-backups`).
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildBackupProvisionScript(options: BackupProvisionOptions): string[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Let's Encrypt TLS for nginx vhosts via certbot — issuance + automatic
|
|
3
|
+
* renewal, mirroring Forge's SSL handling.
|
|
4
|
+
*
|
|
5
|
+
* `certbot --nginx` reads the site's :80 server block, obtains a certificate
|
|
6
|
+
* for its `server_name`s, and rewrites the vhost to serve :443 + redirect
|
|
7
|
+
* :80 → :443. The apt `certbot` package installs a `certbot.timer` that renews
|
|
8
|
+
* twice daily; we add a deploy-hook so nginx reloads after a renewal.
|
|
9
|
+
*
|
|
10
|
+
* For the `custom` provider the vhost is rendered with the operator's cert
|
|
11
|
+
* directly (see nginx-vhost's `ssl` option), so certbot is not involved.
|
|
12
|
+
*/
|
|
13
|
+
import type { SiteConfig } from '@ts-cloud/core';
|
|
14
|
+
/** Resolve the effective SSL provider for a site (Let's Encrypt by default when it has a domain). */
|
|
15
|
+
export declare function resolveSslProvider(site: SiteConfig): 'letsencrypt' | 'custom' | 'none';
|
|
16
|
+
/** Install certbot + the nginx plugin and ensure the auto-renew timer is enabled. */
|
|
17
|
+
export declare function buildCertbotInstallScript(): string[];
|
|
18
|
+
export interface CertbotIssueOptions {
|
|
19
|
+
/** Primary domain. */
|
|
20
|
+
domain: string;
|
|
21
|
+
/** Additional SANs (site aliases). */
|
|
22
|
+
aliases?: string[];
|
|
23
|
+
/** Contact email for registration / expiry notices. */
|
|
24
|
+
email?: string;
|
|
25
|
+
/** Redirect HTTP → HTTPS (Forge default). @default true */
|
|
26
|
+
redirect?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Issue (or expand) a Let's Encrypt cert for the domain via the nginx plugin.
|
|
30
|
+
* Idempotent: `--keep-until-expiring` reuses a valid cert, so re-running on
|
|
31
|
+
* every deploy is safe.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildCertbotIssueScript(options: CertbotIssueOptions): string[];
|
|
34
|
+
/**
|
|
35
|
+
* Full SSL script for a site, dispatched on its provider. Returns `[]` for
|
|
36
|
+
* `custom`/`none` (custom certs are baked into the vhost already).
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildSslScript(site: SiteConfig): string[];
|
|
@@ -15,7 +15,7 @@ export interface DeployAllSitesOptions {
|
|
|
15
15
|
environment: EnvironmentType;
|
|
16
16
|
driver: CloudDriver;
|
|
17
17
|
sha: string;
|
|
18
|
-
runtime: 'bun' | 'node' | 'deno';
|
|
18
|
+
runtime: 'bun' | 'node' | 'deno' | 'php';
|
|
19
19
|
tarballForSite: (siteName: string) => string;
|
|
20
20
|
logger?: ComputeDeployLogger;
|
|
21
21
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose the full machine-provisioning scripts for a compute box from a
|
|
3
|
+
* CloudConfig — PHP/nginx/Composer, on-box services + app database, host
|
|
4
|
+
* firewall, auto-updates, monitoring, SSH keys, notifier, and scheduled
|
|
5
|
+
* backups.
|
|
6
|
+
*
|
|
7
|
+
* Single source of truth shared by:
|
|
8
|
+
* - the **driver cold-boot** path (Hetzner cloud-init / AWS UserData), and
|
|
9
|
+
* - the **golden-image bake** (which runs the same provisioning then snapshots),
|
|
10
|
+
* so a baked image and a cold boot install exactly the same stack.
|
|
11
|
+
*/
|
|
12
|
+
import type { CloudConfig } from '@ts-cloud/core';
|
|
13
|
+
export interface ComputeProvisionScripts {
|
|
14
|
+
/** Effective runtime to install (bun/node/deno/php). */
|
|
15
|
+
runtime: 'bun' | 'node' | 'deno' | 'php';
|
|
16
|
+
/** Pinned runtime version (or 'latest'). */
|
|
17
|
+
runtimeVersion: string;
|
|
18
|
+
/** Whether this box runs PHP (drives UFW/auto-updates/monitoring defaults). */
|
|
19
|
+
phpBox: boolean;
|
|
20
|
+
/** nginx + php-fpm + Composer install commands (undefined for non-PHP boxes). */
|
|
21
|
+
phpProvision?: string[];
|
|
22
|
+
/** services + db + firewall + updates + monitoring + ssh + notifier + backups. */
|
|
23
|
+
servicesProvision?: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build the machine provisioning scripts from a CloudConfig. Returns the
|
|
27
|
+
* pieces the Ubuntu bootstrap (and the image bake) splice in.
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildComputeProvisionScripts(config: CloudConfig): ComputeProvisionScripts;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provision on-box managed services (Forge single-server model) via **pantry**:
|
|
3
|
+
* the database engine, cache, and search are installed from the pantry registry
|
|
4
|
+
* and run as boot-time systemd services, plus the application database + user
|
|
5
|
+
* are created. When the app points at a managed/external database instead,
|
|
6
|
+
* install nothing and just wire `.env` — see {@link buildManagedDbEnv}.
|
|
7
|
+
*
|
|
8
|
+
* pantry services listen on TCP localhost ports (mysql/mariadb 3306, postgres
|
|
9
|
+
* 5432, redis 6379, memcached 11211, meilisearch 7700); DB setup connects over
|
|
10
|
+
* TCP, and the engine clients are on PATH via `pantry env`.
|
|
11
|
+
*/
|
|
12
|
+
import type { ComputeServicesConfig, DatabaseConfig } from '@ts-cloud/core';
|
|
13
|
+
/**
|
|
14
|
+
* Build pantry install + enable/start commands for each requested on-box
|
|
15
|
+
* service. Idempotent (pantry install/enable/start are no-ops when satisfied).
|
|
16
|
+
* `options.bindPrivate` is accepted for fleet compatibility; pantry's services
|
|
17
|
+
* already bind all interfaces behind the firewall.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildServicesProvisionScript(services?: ComputeServicesConfig, _options?: {
|
|
20
|
+
bindPrivate?: boolean;
|
|
21
|
+
}): string[];
|
|
22
|
+
/**
|
|
23
|
+
* Build the commands that create the application database + user on the on-box
|
|
24
|
+
* engine. Idempotent (uses IF NOT EXISTS / existence guards). Returns `[]` when
|
|
25
|
+
* the database points at a managed host or lacks a name. The engine client is
|
|
26
|
+
* put on PATH via `pantry env` and connects over TCP localhost.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildDatabaseSetupScript(database: DatabaseConfig | undefined, services?: ComputeServicesConfig): string[];
|
|
29
|
+
/**
|
|
30
|
+
* `.env` key/value pairs wiring a Laravel app at the (on-box or managed)
|
|
31
|
+
* database. Merge into a site's `env` so `DB_*` is set without hand-copying
|
|
32
|
+
* credentials. Returns `{}` when there's nothing to wire.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildManagedDbEnv(database: DatabaseConfig | undefined): Record<string, string>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode an environment map as a `.env` file body that round-trips correctly
|
|
3
|
+
* through PHP dotenv (Laravel) and Bun's `.env` loader.
|
|
4
|
+
*
|
|
5
|
+
* Values are double-quoted with the standard dotenv escapes (`\\`, `\"`, `\n`,
|
|
6
|
+
* `\r`, `\t`) so secrets/keys containing spaces, `#`, `=`, quotes, backslashes,
|
|
7
|
+
* or newlines survive intact. (The previous `JSON.stringify` approach
|
|
8
|
+
* over-escaped some values and corrupted multi-line ones.)
|
|
9
|
+
*/
|
|
10
|
+
export declare function formatEnvFile(env: Record<string, string>): string;
|
|
11
|
+
/** Double-quote + escape a single `.env` value. */
|
|
12
|
+
export declare function quoteEnvValue(value: string): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a Forge-style fleet topology from a compute config, and wire app
|
|
3
|
+
* servers at a dedicated services box.
|
|
4
|
+
*
|
|
5
|
+
* Roles:
|
|
6
|
+
* - `app` — runs nginx + php-fpm, the app is deployed to every app server.
|
|
7
|
+
* - `services` — a dedicated box running the database/cache/search.
|
|
8
|
+
* - `lb` — the load balancer fronting the app servers.
|
|
9
|
+
*
|
|
10
|
+
* Single-server (the common case) resolves to one `app` box with services
|
|
11
|
+
* co-located. A multi-app fleet (`appServers > 1`) gets a load balancer, a
|
|
12
|
+
* private network, and (required) a dedicated services box so every app server
|
|
13
|
+
* shares one database/cache.
|
|
14
|
+
*/
|
|
15
|
+
import type { ComputeConfig, DatabaseConfig } from '@ts-cloud/core';
|
|
16
|
+
export type FleetRole = 'app' | 'services' | 'lb';
|
|
17
|
+
export interface FleetTopology {
|
|
18
|
+
/** Number of application servers. */
|
|
19
|
+
appServers: number;
|
|
20
|
+
/** Provision a load balancer in front of the app servers. */
|
|
21
|
+
loadBalancer: boolean;
|
|
22
|
+
/** Provision a dedicated services box (DB/cache/search off the app servers). */
|
|
23
|
+
dedicatedServices: boolean;
|
|
24
|
+
/** Whether the app servers should install services locally (single-box only). */
|
|
25
|
+
servicesOnApp: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Resolve the fleet topology from the compute config. */
|
|
28
|
+
export declare function resolveFleetTopology(compute?: ComputeConfig): FleetTopology;
|
|
29
|
+
/**
|
|
30
|
+
* Build the `.env` overrides that point a PHP app at a dedicated services box
|
|
31
|
+
* over the private network (DB + Redis + Meilisearch all live there). Merge
|
|
32
|
+
* under `site.env` (explicit values win).
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildFleetServicesEnv(servicesPrivateIp: string, database?: DatabaseConfig): Record<string, string>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forge-style git-clone-on-server deploys: the box clones the site's repository
|
|
3
|
+
* into a fresh release directory rather than receiving a tarball over SCP.
|
|
4
|
+
*
|
|
5
|
+
* The clone is shallow (`--depth 1`) on the configured branch for speed. When a
|
|
6
|
+
* specific commit is requested, it is fetched and checked out so deploys are
|
|
7
|
+
* reproducible. The resolved commit SHA is written to `<release>/.ts-cloud-sha`
|
|
8
|
+
* so later steps (and rollbacks) can identify the release.
|
|
9
|
+
*/
|
|
10
|
+
import type { SiteRepositoryConfig } from '@ts-cloud/core';
|
|
11
|
+
export interface GitCheckoutOptions {
|
|
12
|
+
/** Repository to clone. */
|
|
13
|
+
repository: SiteRepositoryConfig;
|
|
14
|
+
/** Absolute release directory to clone into (`<base>/releases/<id>`). */
|
|
15
|
+
releaseDir: string;
|
|
16
|
+
/**
|
|
17
|
+
* Specific commit to deploy. When omitted, the branch tip is used (Forge's
|
|
18
|
+
* push-to-deploy behaviour).
|
|
19
|
+
*/
|
|
20
|
+
commit?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Default branch when the repository config omits one. */
|
|
23
|
+
export declare const DEFAULT_DEPLOY_BRANCH = "main";
|
|
24
|
+
/** Default tag glob when `strategy: 'tag'` and no explicit tag is set. */
|
|
25
|
+
export declare const DEFAULT_TAG_PATTERN = "v*";
|
|
26
|
+
/**
|
|
27
|
+
* Build the shell commands that clone + checkout the repository into the
|
|
28
|
+
* release directory. Assumes `git` is installed (the bootstrap installs it).
|
|
29
|
+
*
|
|
30
|
+
* Strategy is taken from `repository.strategy`:
|
|
31
|
+
* - `'push'` (default) — clone the branch tip (or a pinned `commit`).
|
|
32
|
+
* - `'tag'` — clone a version tag: the explicit `repository.tag`, else the
|
|
33
|
+
* highest tag matching `repository.tagPattern`, resolved on the box via
|
|
34
|
+
* `git ls-remote --sort=-v:refname`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildGitCheckoutScript(options: GitCheckoutOptions): string[];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Golden-image bake recipe.
|
|
3
|
+
*
|
|
4
|
+
* Produces the bash script Packer (or a manual snapshot run) executes on a base
|
|
5
|
+
* Ubuntu box to pre-install the full ts-cloud stack (nginx, php-fpm, Composer,
|
|
6
|
+
* services, hardening). The box is then snapshotted into a Hetzner snapshot /
|
|
7
|
+
* AWS AMI and referenced via `compute.image` + `compute.bakedImage: true`, so
|
|
8
|
+
* production boots are near-instant.
|
|
9
|
+
*
|
|
10
|
+
* Because it reuses {@link buildComputeProvisionScripts} + the shared
|
|
11
|
+
* {@link buildUbuntuBootstrapScript}, a baked image and a cold boot install the
|
|
12
|
+
* exact same stack — there is no separate, drift-prone image definition.
|
|
13
|
+
*/
|
|
14
|
+
import type { CloudConfig } from '@ts-cloud/core';
|
|
15
|
+
export interface ImageRecipeOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Exclude per-app/per-deploy state from the image so it stays generic and
|
|
18
|
+
* reusable across projects: SSH keys, the app database creation, and
|
|
19
|
+
* scheduled backups are applied at boot/deploy instead of baked in.
|
|
20
|
+
* @default true
|
|
21
|
+
*/
|
|
22
|
+
generic?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Append an image-minimization pass (clear apt/composer caches, logs, tmp;
|
|
25
|
+
* reset machine-id + cloud-init so clones boot fresh; trim free space). Keeps
|
|
26
|
+
* the published snapshot/AMI as small as possible. @default true
|
|
27
|
+
*/
|
|
28
|
+
optimize?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Image-minimization commands appended after provisioning so the published
|
|
32
|
+
* snapshot/AMI is as small as possible. Safe to run at the end of a bake (the
|
|
33
|
+
* box is snapshotted immediately after).
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildImageCleanupScript(): string[];
|
|
36
|
+
/**
|
|
37
|
+
* Build the bake recipe (a bash script) for a config. Runs the full
|
|
38
|
+
* provisioning with `baked: false` so everything is installed into the image.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildImageRecipe(config: CloudConfig, options?: ImageRecipeOptions): string;
|