@stacksjs/ts-cloud 0.2.26 → 0.2.27
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 +7 -0
- package/dist/bin/cli.js +588 -571
- 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 +30 -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 +76 -0
- package/dist/drivers/shared/notifications.d.ts +40 -0
- package/dist/drivers/shared/php-provision.d.ts +38 -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 +58606 -56962
- package/dist/security/pre-deploy-scanner.d.ts +12 -0
- package/package.json +3 -3
|
@@ -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,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provision on-box managed services (Forge single-server model): the database
|
|
3
|
+
* engine, cache, and search, plus create the application database + user.
|
|
4
|
+
*
|
|
5
|
+
* These run at bootstrap (cloud-init), not on deploy, since installing an
|
|
6
|
+
* engine is a machine-level, one-time operation. The app's database
|
|
7
|
+
* credentials are referenced from the site's `.env` (`DB_*`). When the app
|
|
8
|
+
* points at a managed/external database instead, install nothing and just wire
|
|
9
|
+
* `.env` — see {@link buildManagedDbEnv}.
|
|
10
|
+
*/
|
|
11
|
+
import type { ComputeServicesConfig, DatabaseConfig } from '@ts-cloud/core';
|
|
12
|
+
/**
|
|
13
|
+
* Build apt install + enable commands for each requested on-box service.
|
|
14
|
+
* Idempotent: apt install is a no-op when already present.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildServicesProvisionScript(services?: ComputeServicesConfig, options?: {
|
|
17
|
+
bindPrivate?: boolean;
|
|
18
|
+
}): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Build the commands that create the application database + user on the on-box
|
|
21
|
+
* engine. Idempotent (uses IF NOT EXISTS / existence guards). Returns `[]` when
|
|
22
|
+
* the database points at a managed host or lacks a name.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildDatabaseSetupScript(database: DatabaseConfig | undefined, services?: ComputeServicesConfig): string[];
|
|
25
|
+
/**
|
|
26
|
+
* `.env` key/value pairs wiring a Laravel app at the (on-box or managed)
|
|
27
|
+
* database. Merge into a site's `env` so `DB_*` is set without hand-copying
|
|
28
|
+
* credentials. Returns `{}` when there's nothing to wire.
|
|
29
|
+
*/
|
|
30
|
+
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;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assemble the remote deploy script for a Forge-style PHP/Laravel site: a
|
|
3
|
+
* git-clone-on-server, zero-downtime atomic-release deploy.
|
|
4
|
+
*
|
|
5
|
+
* The deploy script is a list of steps run in order on the box. Three Forge
|
|
6
|
+
* macros expand to the release machinery:
|
|
7
|
+
* $CREATE_RELEASE — clone the repo into a new release dir, link shared
|
|
8
|
+
* paths, and `cd` into it (subsequent steps run there).
|
|
9
|
+
* $ACTIVATE_RELEASE — flip the `current` symlink atomically, prune old
|
|
10
|
+
* releases, and reload php-fpm so opcache sees new code.
|
|
11
|
+
* $RESTART_QUEUES — gracefully restart Laravel queue workers / Horizon.
|
|
12
|
+
*
|
|
13
|
+
* Everything between $CREATE_RELEASE and $ACTIVATE_RELEASE runs inside the new
|
|
14
|
+
* (not-yet-live) release, so a failure leaves the previous release serving —
|
|
15
|
+
* the Envoyer zero-downtime guarantee.
|
|
16
|
+
*/
|
|
17
|
+
import type { SiteConfig } from '@ts-cloud/core';
|
|
18
|
+
export declare const MACRO_CREATE_RELEASE = "$CREATE_RELEASE";
|
|
19
|
+
export declare const MACRO_ACTIVATE_RELEASE = "$ACTIVATE_RELEASE";
|
|
20
|
+
export declare const MACRO_RESTART_QUEUES = "$RESTART_QUEUES";
|
|
21
|
+
/**
|
|
22
|
+
* Default deploy script (with macros) for a site type. Overridden by
|
|
23
|
+
* {@link SiteConfig.deployScript}.
|
|
24
|
+
*/
|
|
25
|
+
export declare function defaultDeployScriptFor(type: NonNullable<SiteConfig['type']>): string[];
|
|
26
|
+
export interface LaravelDeployOptions {
|
|
27
|
+
siteName: string;
|
|
28
|
+
site: SiteConfig;
|
|
29
|
+
/** Unique release identifier (timestamp or sha) → `releases/<id>`. */
|
|
30
|
+
releaseId: string;
|
|
31
|
+
/** Site base dir. @default `/var/www/<siteName>` */
|
|
32
|
+
appBase?: string;
|
|
33
|
+
/** Exact commit to deploy (else the branch tip). */
|
|
34
|
+
commit?: string;
|
|
35
|
+
/** PHP version selecting the `phpX.Y` binary. @default `site.phpVersion` ?? '8.3' */
|
|
36
|
+
defaultPhpVersion?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the full remote shell script for a PHP/Laravel git deploy, expanding the
|
|
40
|
+
* release macros. Requires `site.repository` to be set.
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildLaravelDeployScript(options: LaravelDeployOptions): string[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate the per-site runtime services for a Forge-style PHP box: queue
|
|
3
|
+
* workers / Horizon (systemd), the Laravel scheduler (cron), and arbitrary
|
|
4
|
+
* daemons (systemd). Reconciled on every deploy so units track the config —
|
|
5
|
+
* units no longer in the config are stopped and removed.
|
|
6
|
+
*
|
|
7
|
+
* Unit naming (so a site's units can be globbed for pruning):
|
|
8
|
+
* <slug>-<site>-queue-<i>.service
|
|
9
|
+
* <slug>-<site>-daemon-<slug-of-name>.service
|
|
10
|
+
* Scheduler cron lives at /etc/cron.d/<slug>-<site>-scheduler.
|
|
11
|
+
*
|
|
12
|
+
* ExecStart commands target `<base>/current/...` (the active-release symlink),
|
|
13
|
+
* so workers/daemons always run the live code; `queue:restart` (run by the
|
|
14
|
+
* deploy's $RESTART_QUEUES macro) cycles them onto the new release.
|
|
15
|
+
*/
|
|
16
|
+
import type { DaemonConfig, SiteConfig } from '@ts-cloud/core';
|
|
17
|
+
export interface SiteServicesOptions {
|
|
18
|
+
slug: string;
|
|
19
|
+
siteName: string;
|
|
20
|
+
site: SiteConfig;
|
|
21
|
+
/** PHP version selecting the `phpX.Y` binary. @default '8.3' */
|
|
22
|
+
phpVersion?: string;
|
|
23
|
+
/** Site base dir. @default `/var/www/<siteName>` */
|
|
24
|
+
appBase?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function queueUnitName(slug: string, siteName: string, index: number): string;
|
|
27
|
+
export declare function daemonUnitName(slug: string, siteName: string, daemon: DaemonConfig, index: number): string;
|
|
28
|
+
/** Path of the scheduler cron file for a site. */
|
|
29
|
+
export declare function schedulerCronPath(slug: string, siteName: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Build the full reconcile script: (re)write desired queue/daemon units +
|
|
32
|
+
* scheduler cron, prune stale units for this site, and reload systemd.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildSiteServicesScript(options: SiteServicesOptions): string[];
|
|
35
|
+
/** Whether a site declares any runtime services (avoids emitting an empty reconcile). */
|
|
36
|
+
export declare function siteHasServices(site: SiteConfig): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic unattended security/system updates, mirroring Forge's scheduled
|
|
3
|
+
* maintenance. Installs `unattended-upgrades` and enables the daily APT
|
|
4
|
+
* auto-update timers so security patches land without manual intervention.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Build the commands that enable automatic security updates. Idempotent.
|
|
8
|
+
* Returns `[]` when disabled.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildAutoUpdatesScript(enabled?: boolean): string[];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight server monitoring, mirroring Forge's basic server metrics.
|
|
3
|
+
*
|
|
4
|
+
* Dependency-free: a small shell collector reads load average, memory, and
|
|
5
|
+
* disk usage and writes a JSON snapshot to `/var/lib/ts-cloud/metrics.json`
|
|
6
|
+
* every minute via a systemd timer. The ts-cloud UI (and any operator tooling)
|
|
7
|
+
* can read that file for at-a-glance server health.
|
|
8
|
+
*/
|
|
9
|
+
/** Where the metrics snapshot is written. */
|
|
10
|
+
export declare const METRICS_PATH = "/var/lib/ts-cloud/metrics.json";
|
|
11
|
+
/**
|
|
12
|
+
* Build the commands that install the metrics collector + systemd timer.
|
|
13
|
+
* Idempotent. Returns `[]` when disabled.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildMonitoringScript(enabled?: boolean): string[];
|