@ts-cloud/core 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/types.d.ts CHANGED
@@ -77,6 +77,12 @@ export interface CloudConfig {
77
77
  environments: Record<string, EnvironmentConfig>;
78
78
  infrastructure?: InfrastructureConfig;
79
79
  sites?: Record<string, SiteConfig>;
80
+ /**
81
+ * Notification channels for deploy, SSL, health-check, and backup events
82
+ * (Slack, Discord, Telegram, email, generic webhook). Project-wide default;
83
+ * a site may override via {@link SiteConfig.notifications}.
84
+ */
85
+ notifications?: NotificationsConfig;
80
86
  /**
81
87
  * AWS-specific configuration
82
88
  */
@@ -153,6 +159,13 @@ export interface EnvironmentConfig {
153
159
  * Example: smaller instances in dev, larger in production
154
160
  */
155
161
  infrastructure?: Partial<InfrastructureConfig>;
162
+ /**
163
+ * Serverless application manifest (Laravel-Vapor-equivalent). Defining this opts
164
+ * the environment into the serverless app deploy pipeline: one codebase deployed
165
+ * as http/queue/cli Lambda functions with assets, hooks, and atomic activation.
166
+ * @see ServerlessAppConfig
167
+ */
168
+ app?: ServerlessAppConfig;
156
169
  }
157
170
  /**
158
171
  * Network/VPC configuration
@@ -294,6 +307,14 @@ export interface InfrastructureConfig {
294
307
  * - `'postgres'` → RDS Postgres with sane defaults, DATABASE_URL injected into env
295
308
  */
296
309
  database?: 'sqlite' | 'mysql' | 'postgres';
310
+ /**
311
+ * Application database connection (object form) for the Forge-style on-box /
312
+ * managed database path. Provides the name/user/password that
313
+ * `compute.managedServices` creates on the box, and the `DB_*` values
314
+ * auto-wired into PHP sites' `.env`. Distinct from the {@link database}
315
+ * string shorthand.
316
+ */
317
+ appDatabase?: DatabaseConfig;
297
318
  cache?: CacheConfig;
298
319
  cdn?: Record<string, CdnItemConfig & ResourceConditions> | CdnItemConfig;
299
320
  /**
@@ -824,6 +845,252 @@ export interface SiteConfig {
824
845
  * Example: ['bun install --frozen-lockfile', 'bun run build']
825
846
  */
826
847
  preStart?: string[];
848
+ /**
849
+ * SSR only. tar `--exclude` patterns applied when packaging the release
850
+ * tarball. Keep host-specific / heavy paths out of the artifact — most
851
+ * importantly `node_modules` (host-built native binaries won't run on the
852
+ * target OS; install fresh via `preStart` instead), plus `.git`, dev caches,
853
+ * and the built frontend.
854
+ *
855
+ * Example: ['node_modules', '.git', 'dist']
856
+ */
857
+ exclude?: string[];
858
+ /**
859
+ * Application type. Drives the default deploy script and the nginx vhost
860
+ * template:
861
+ * - `'laravel'` — `public/` web root, Laravel deploy script (composer,
862
+ * artisan caches, migrate, storage:link, queue:restart).
863
+ * - `'php'` — generic PHP app behind php-fpm (vanilla PHP, custom framework).
864
+ * - `'statamic'` / `'wordpress'` — PHP apps with framework-specific defaults.
865
+ * - `'static'` — plain static files served by nginx.
866
+ * - `'spa'` — single-page app with a `try_files … /index.html` fallback.
867
+ *
868
+ * When omitted the legacy inference applies (`start` ⇒ a systemd runtime app,
869
+ * otherwise a bucket static site) — so existing bun/node sites are unaffected.
870
+ */
871
+ type?: 'laravel' | 'php' | 'statamic' | 'wordpress' | 'static' | 'spa';
872
+ /**
873
+ * PHP version for this site (e.g. `'8.3'`). Selects the php-fpm pool/socket
874
+ * the nginx vhost points at. Must be one of `compute.php.versions`. Defaults
875
+ * to `compute.php.default`.
876
+ */
877
+ phpVersion?: PhpVersion;
878
+ /**
879
+ * Web root relative to the release directory. Defaults to `'public'` for
880
+ * `laravel`/`statamic`/`wordpress`, and `''` (the release root) for `php`,
881
+ * `static`, and `spa`.
882
+ */
883
+ webDirectory?: string;
884
+ /**
885
+ * Git repository the server clones/pulls on deploy (Forge-style). When set,
886
+ * the deploy clones `branch` into `releases/<sha>` rather than shipping a
887
+ * tarball over SCP.
888
+ */
889
+ repository?: SiteRepositoryConfig;
890
+ /**
891
+ * Override the deploy script run inside the new release directory. When
892
+ * omitted, a sensible default for `type` is used (e.g. the Laravel script).
893
+ * The special tokens `$CREATE_RELEASE`, `$ACTIVATE_RELEASE`, and
894
+ * `$RESTART_QUEUES` expand to the zero-downtime release macros.
895
+ */
896
+ deployScript?: string[];
897
+ /**
898
+ * Paths symlinked from the site's `shared/` directory into every release so
899
+ * they persist across deploys (e.g. `storage`, uploaded files, a SQLite db).
900
+ * `.env` is always shared and need not be listed.
901
+ * @default ['storage', '.env']
902
+ */
903
+ sharedPaths?: string[];
904
+ /**
905
+ * Number of past releases to retain on the box for rollback.
906
+ * @default 4
907
+ */
908
+ keepReleases?: number;
909
+ /**
910
+ * Use zero-downtime atomic releases (Envoyer-style: clone → build → flip the
911
+ * `current` symlink only after every step succeeds, with the previous release
912
+ * kept for instant rollback). On by default for git-deployed PHP sites — set
913
+ * `false` only to deploy in place.
914
+ * @default true
915
+ */
916
+ zeroDowntime?: boolean;
917
+ /** Laravel queue workers to run for this site (systemd-managed). */
918
+ queues?: QueueWorkerConfig[];
919
+ /**
920
+ * Run the Laravel scheduler for this site
921
+ * (`* * * * * php artisan schedule:run`).
922
+ */
923
+ scheduler?: boolean;
924
+ /** Arbitrary long-running processes to keep alive (systemd-managed). */
925
+ daemons?: DaemonConfig[];
926
+ /** TLS configuration for this site's nginx vhost. */
927
+ ssl?: SiteSslConfig;
928
+ /** Additional hostnames served by the same vhost (nginx `server_name`). */
929
+ aliases?: string[];
930
+ /** `from` path/host → `to` URL redirects emitted into the nginx vhost. */
931
+ redirects?: Record<string, string>;
932
+ /**
933
+ * Give this site a dedicated php-fpm pool (isolated user/process) rather than
934
+ * sharing the default pool.
935
+ */
936
+ isolation?: boolean;
937
+ /** Post-deploy health check (Forge-style) pinged after `current` is flipped. */
938
+ healthCheck?: {
939
+ path?: string;
940
+ };
941
+ /**
942
+ * Per-site notification channels, overriding the project-wide
943
+ * {@link CloudConfig.notifications} for this site's events.
944
+ */
945
+ notifications?: NotificationsConfig;
946
+ /**
947
+ * HTTP Basic auth (htpasswd) protecting the whole site at the nginx layer.
948
+ * Typically driven from an env value, e.g. `{ username: 'admin', password:
949
+ * process.env.UI_PASSWORD }`. The htpasswd file is generated on the box.
950
+ */
951
+ auth?: SiteAuthConfig;
952
+ }
953
+ /** HTTP Basic auth for a site's nginx vhost. See {@link SiteConfig.auth}. */
954
+ export interface SiteAuthConfig {
955
+ /** Enable basic auth. @default true when this object is present */
956
+ enabled?: boolean;
957
+ /** Username. @default 'admin' */
958
+ username?: string;
959
+ /** Plaintext password (hashed on the box). Usually `process.env.X`. */
960
+ password?: string;
961
+ /** Realm shown in the browser auth prompt. @default 'Restricted' */
962
+ realm?: string;
963
+ }
964
+ /**
965
+ * Git source for a Forge-style git-clone deploy. See {@link SiteConfig.repository}.
966
+ */
967
+ export interface SiteRepositoryConfig {
968
+ /** Clone URL (https or git@). */
969
+ url: string;
970
+ /** Branch to deploy. @default 'main' */
971
+ branch?: string;
972
+ /** Hosting provider — drives push-to-deploy hook wiring. @default 'github' */
973
+ provider?: 'github' | 'gitlab' | 'bitbucket' | 'custom';
974
+ /**
975
+ * Deploy strategy:
976
+ * - `'push'` (default) — deploy the tip of `branch` (push-to-deploy).
977
+ * - `'tag'` — deploy a git version tag: a specific {@link tag}, or the latest
978
+ * tag matching {@link tagPattern} (e.g. release `v*` tags). Useful for
979
+ * promoting tagged releases rather than every push.
980
+ */
981
+ strategy?: 'push' | 'tag';
982
+ /** Exact tag to deploy when `strategy: 'tag'`. Overrides {@link tagPattern}. */
983
+ tag?: string;
984
+ /**
985
+ * Glob matching the tags to consider when `strategy: 'tag'` and no explicit
986
+ * {@link tag} is set; the highest version (`-sort=-v:refname`) is deployed.
987
+ * @default 'v*'
988
+ */
989
+ tagPattern?: string;
990
+ }
991
+ /**
992
+ * TLS for a PHP/static site's nginx vhost. See {@link SiteConfig.ssl}.
993
+ */
994
+ export interface SiteSslConfig {
995
+ /**
996
+ * Certificate source:
997
+ * - `'letsencrypt'` — issue + auto-renew via certbot (default for sites with
998
+ * a `domain`).
999
+ * - `'custom'` — install operator-provided `certPath`/`keyPath`.
1000
+ * - `'none'` — serve plain HTTP only.
1001
+ */
1002
+ provider?: 'letsencrypt' | 'custom' | 'none';
1003
+ /** Contact email for Let's Encrypt registration/expiry notices. */
1004
+ email?: string;
1005
+ /** Path to the certificate (PEM) when `provider: 'custom'`. */
1006
+ certPath?: string;
1007
+ /** Path to the private key (PEM) when `provider: 'custom'`. */
1008
+ keyPath?: string;
1009
+ }
1010
+ /**
1011
+ * A Laravel queue worker (or Horizon supervisor) run as a systemd service.
1012
+ * Mirrors Forge's queue configuration. See {@link SiteConfig.queues}.
1013
+ */
1014
+ export interface QueueWorkerConfig {
1015
+ /**
1016
+ * Use `php artisan horizon` instead of `queue:work`. When true, connection /
1017
+ * queue / worker tuning is taken from the app's `config/horizon.php`.
1018
+ * @default false
1019
+ */
1020
+ horizon?: boolean;
1021
+ /** Queue connection (`php artisan queue:work <connection>`). @default 'default' */
1022
+ connection?: string;
1023
+ /** Comma-separated queues to consume, highest priority first. @default 'default' */
1024
+ queue?: string;
1025
+ /** Number of worker processes to run in parallel. @default 1 */
1026
+ processes?: number;
1027
+ /** `--timeout`: seconds a child job may run before being killed. @default 60 */
1028
+ timeout?: number;
1029
+ /** `--sleep`: seconds to wait when no job is available. @default 3 */
1030
+ sleep?: number;
1031
+ /** `--tries`: attempts before a job is marked failed. @default 3 */
1032
+ tries?: number;
1033
+ /** `--max-jobs`: restart the worker after N jobs (0 = unlimited). */
1034
+ maxJobs?: number;
1035
+ /** `--max-time`: restart the worker after N seconds (0 = unlimited). */
1036
+ maxTime?: number;
1037
+ /** `--memory`: restart the worker when it exceeds N MB. @default 128 */
1038
+ memory?: number;
1039
+ /** Seconds to wait for in-flight jobs to finish on stop/restart. @default 90 */
1040
+ stopWaitSecs?: number;
1041
+ }
1042
+ /**
1043
+ * A generic long-running process kept alive by systemd. Mirrors Forge daemons.
1044
+ * See {@link SiteConfig.daemons}.
1045
+ */
1046
+ export interface DaemonConfig {
1047
+ /** Command to run (becomes systemd `ExecStart`). */
1048
+ command: string;
1049
+ /** Working directory. Defaults to the site's `current` release directory. */
1050
+ directory?: string;
1051
+ /** User to run as. Defaults to the deploy user. */
1052
+ user?: string;
1053
+ /** Number of identical processes to run. @default 1 */
1054
+ processes?: number;
1055
+ /** Restart policy. @default 'always' */
1056
+ restart?: 'always' | 'on-failure' | 'no';
1057
+ /** Optional explicit unit name; defaults to a slug of the command. */
1058
+ name?: string;
1059
+ }
1060
+ /** A lifecycle event that can trigger a notification. */
1061
+ export type NotifyEvent = 'deploy' | 'deploy-failed' | 'ssl' | 'health' | 'backup';
1062
+ /**
1063
+ * Notification channels (Forge-style). Configure any subset; each configured
1064
+ * channel receives the events listed in {@link events} (all events by default).
1065
+ */
1066
+ export interface NotificationsConfig {
1067
+ /** Slack incoming-webhook URL. */
1068
+ slack?: {
1069
+ webhookUrl: string;
1070
+ };
1071
+ /** Discord webhook URL. */
1072
+ discord?: {
1073
+ webhookUrl: string;
1074
+ };
1075
+ /** Telegram bot token + chat id. */
1076
+ telegram?: {
1077
+ botToken: string;
1078
+ chatId: string;
1079
+ };
1080
+ /** Email recipients (sent via ts-cloud's email/SES client). */
1081
+ email?: {
1082
+ to: string | string[];
1083
+ from?: string;
1084
+ };
1085
+ /** Generic webhook — receives `{ event, message }` as JSON. */
1086
+ webhook?: {
1087
+ url: string;
1088
+ method?: 'POST' | 'GET';
1089
+ };
1090
+ /**
1091
+ * Which events to notify on. @default all events
1092
+ */
1093
+ events?: NotifyEvent[];
827
1094
  }
828
1095
  export interface VpcConfig {
829
1096
  cidr?: string;
@@ -844,8 +1111,18 @@ export interface BucketConfig {
844
1111
  }
845
1112
  export interface DatabaseConfig {
846
1113
  type?: 'rds' | 'dynamodb';
847
- engine?: 'postgres' | 'mysql';
1114
+ engine?: 'postgres' | 'mysql' | 'mariadb';
848
1115
  instanceType?: string;
1116
+ /** Database/schema name to create (e.g. `forge`). */
1117
+ name?: string;
1118
+ /** Application database user to create. */
1119
+ username?: string;
1120
+ /** Password for {@link username}. */
1121
+ password?: string;
1122
+ /** Hostname for a managed/external database (default `127.0.0.1` on-box). */
1123
+ host?: string;
1124
+ /** Port (defaults: mysql/mariadb 3306, postgres 5432). */
1125
+ port?: number;
849
1126
  }
850
1127
  export interface CacheConfig {
851
1128
  type?: 'redis' | 'memcached';
@@ -1113,6 +1390,175 @@ export interface FunctionConfig {
1113
1390
  }>;
1114
1391
  environment?: Record<string, string>;
1115
1392
  }
1393
+ /**
1394
+ * Serverless application configuration — the Laravel-Vapor-equivalent manifest.
1395
+ *
1396
+ * One application codebase is deployed as three Lambda functions sharing a single
1397
+ * code artifact:
1398
+ * - **http** fronted by API Gateway v2 (or v1) + an optional custom domain
1399
+ * - **queue** triggered by an SQS event source mapping (one job per invocation)
1400
+ * - **cli** invoked by an EventBridge schedule (`schedule:run`) and on demand
1401
+ * (deploy hooks, migrations, `command`)
1402
+ *
1403
+ * Declared per-environment via {@link EnvironmentConfig.app}. Defining it opts a
1404
+ * project into the serverless application deploy pipeline (`cloud deploy:serverless`).
1405
+ *
1406
+ * @example
1407
+ * environments: {
1408
+ * production: {
1409
+ * type: 'production',
1410
+ * domain: 'app.example.com',
1411
+ * app: {
1412
+ * runtime: 'nodejs20.x',
1413
+ * entry: 'src/server.ts',
1414
+ * memory: 1024,
1415
+ * build: ['bun install', 'bun run build'],
1416
+ * deploy: ['migrate'],
1417
+ * queues: true,
1418
+ * scheduler: 'on',
1419
+ * },
1420
+ * },
1421
+ * }
1422
+ */
1423
+ export interface ServerlessAppConfig {
1424
+ /**
1425
+ * Lambda runtime shared by all three functions.
1426
+ * Use `provided.al2023` for PHP (custom runtime layer) or Bun custom runtimes.
1427
+ * @default 'nodejs20.x'
1428
+ */
1429
+ runtime?: 'nodejs20.x' | 'nodejs22.x' | 'provided.al2023' | (string & {});
1430
+ /**
1431
+ * Application kind. Drives packaging + runtime selection.
1432
+ * - `node` / `bun`: bundle a JS/TS handler artifact
1433
+ * - `php`: build/attach the PHP runtime layer and FPM bridge (Laravel)
1434
+ * @default 'node'
1435
+ */
1436
+ kind?: 'node' | 'bun' | 'php';
1437
+ /**
1438
+ * Entry file (relative to project root) that exports the request handler.
1439
+ * Used when `handlers` is not provided; a single shim re-exports http/queue/cli.
1440
+ * @example 'src/server.ts'
1441
+ */
1442
+ entry?: string;
1443
+ /**
1444
+ * Explicit per-function handlers, overriding the single-`entry` shim.
1445
+ * Values are Lambda handler strings (e.g. `dist/index.http`).
1446
+ */
1447
+ handlers?: {
1448
+ http?: string;
1449
+ queue?: string;
1450
+ cli?: string;
1451
+ };
1452
+ /** HTTP function memory in MB. @default 1024 */
1453
+ memory?: number;
1454
+ /** HTTP request timeout in seconds (API Gateway caps at 29s for v2). @default 28 */
1455
+ timeout?: number;
1456
+ /** Reserved concurrency for the HTTP function. */
1457
+ concurrency?: number;
1458
+ /** API Gateway version: 2 = HTTP API (cheaper, default), 1 = REST API. @default 2 */
1459
+ gatewayVersion?: 1 | 2;
1460
+ /**
1461
+ * Keep-warm count. Sets provisioned concurrency on the HTTP alias, or drives a
1462
+ * scheduled warmer rule. 0/undefined disables warming.
1463
+ */
1464
+ warm?: number;
1465
+ /** CLI function memory in MB. @default 1024 */
1466
+ cliMemory?: number;
1467
+ /** CLI command timeout in seconds (allow room for migrations). @default 900 */
1468
+ cliTimeout?: number;
1469
+ /**
1470
+ * Queue names to process. `true` provisions a single `default` queue; `false`
1471
+ * disables the queue function entirely. Entries may carry a per-queue
1472
+ * concurrency, e.g. `[{ emails: 10 }]`.
1473
+ */
1474
+ queues?: boolean | Array<string | Record<string, number>>;
1475
+ /** Max concurrent queue job executions (SQS event source mapping). @default 1000 */
1476
+ queueConcurrency?: number;
1477
+ /** Queue visibility timeout in seconds. @default 120 */
1478
+ queueTimeout?: number;
1479
+ /** Queue function memory in MB. @default 1024 */
1480
+ queueMemory?: number;
1481
+ /** Max receive count before a message is sent to the DLQ. @default 3 */
1482
+ queueTries?: number;
1483
+ /**
1484
+ * Task scheduler mode:
1485
+ * - `on` EventBridge rule invokes the CLI fn (`schedule:run`) every minute
1486
+ * - `sub-minute` adds a self-rescheduling runner for sub-minute tasks
1487
+ * - `off` no scheduler
1488
+ * @default 'on'
1489
+ */
1490
+ scheduler?: 'off' | 'on' | 'sub-minute';
1491
+ /** Commands run locally before packaging (e.g. `composer install`, `bun run build`). */
1492
+ build?: string[];
1493
+ /**
1494
+ * Commands run remotely after the new code is live, by invoking the CLI function
1495
+ * (e.g. `migrate --force`). A failing hook aborts the deploy and rolls back.
1496
+ */
1497
+ deploy?: string[];
1498
+ /**
1499
+ * Persistent application mode (Laravel Octane / long-lived server) instead of
1500
+ * per-request FPM/handler boot. Lower latency; requires an Octane-safe app.
1501
+ * @default false
1502
+ */
1503
+ octane?: boolean;
1504
+ /**
1505
+ * Deployment package format:
1506
+ * - `zip` ship a ZIP artifact (250 MB unzipped layer+code limit)
1507
+ * - `image` ship a container image to ECR (up to 10 GB) for large apps
1508
+ * @default 'zip'
1509
+ */
1510
+ packaging?: 'zip' | 'image';
1511
+ /** Attach the functions to a VPC (required for ElastiCache / private RDS). */
1512
+ vpc?: {
1513
+ subnets?: string[];
1514
+ securityGroups?: string[];
1515
+ };
1516
+ /** Front the database with an RDS Proxy for Lambda connection pooling. */
1517
+ rdsProxy?: boolean | {
1518
+ name?: string;
1519
+ };
1520
+ /** Ephemeral `/tmp` size in MB (512–10240). @default 512 */
1521
+ tmpStorage?: number;
1522
+ /** Database attachment. */
1523
+ database?: {
1524
+ connection?: 'rds-proxy' | 'aurora-serverless' | 'rds';
1525
+ cluster?: string;
1526
+ };
1527
+ /** Cache attachment. DynamoDB cache table is the zero-NAT default. */
1528
+ cache?: {
1529
+ driver?: 'dynamodb' | 'elasticache';
1530
+ cluster?: string;
1531
+ };
1532
+ /** Application object-storage bucket (Vapor `storage:`). */
1533
+ storage?: {
1534
+ bucket?: string;
1535
+ };
1536
+ /** Managed WAF in front of the HTTP API / CloudFront. */
1537
+ firewall?: WafConfig;
1538
+ /** Custom domain for the app (overrides {@link EnvironmentConfig.domain}). */
1539
+ domain?: string | string[];
1540
+ /** Pre-issued ACM certificate ARN for the custom domain. */
1541
+ certificateArn?: string;
1542
+ /** Local directory whose contents are uploaded to S3/CloudFront as versioned assets. */
1543
+ assets?: string;
1544
+ /** PHP version for the runtime layer. @default '8.3' */
1545
+ phpVersion?: PhpVersion;
1546
+ /** CPU architecture. @default 'x86_64' */
1547
+ architecture?: 'x86_64' | 'arm64';
1548
+ /**
1549
+ * Lambda layer version ARNs attached to all functions. For PHP apps this is
1550
+ * the ts-cloud PHP runtime layer; if omitted, the deployer falls back to the
1551
+ * `TSCLOUD_PHP_LAYER_ARN` environment variable.
1552
+ */
1553
+ layers?: string[];
1554
+ /** Plaintext environment variables injected into all functions. */
1555
+ env?: Record<string, string>;
1556
+ /**
1557
+ * Secret names resolved from Secrets Manager / SSM at deploy time and injected
1558
+ * as environment variables. Array of names, or name→source map.
1559
+ */
1560
+ secrets?: string[] | Record<string, string>;
1561
+ }
1116
1562
  /**
1117
1563
  * Elastic File System (EFS) configuration
1118
1564
  */
@@ -1316,6 +1762,23 @@ export interface ComputeConfig {
1316
1762
  * @default 'micro'
1317
1763
  */
1318
1764
  size?: InstanceSize;
1765
+ /**
1766
+ * Number of application servers (Forge load-balanced fleet). When > 1, a load
1767
+ * balancer is provisioned in front and a private network connects the fleet;
1768
+ * the app is deployed to every app server. Pair with {@link servicesServer}
1769
+ * so the database/cache/search live on one shared box. @default 1
1770
+ */
1771
+ appServers?: number;
1772
+ /**
1773
+ * Provision a **dedicated services server** (its own box) running the
1774
+ * configured {@link managedServices} (MySQL/Redis/Meilisearch), instead of
1775
+ * co-locating them on the app server(s). App servers then point their `.env`
1776
+ * at this box over the private network. Required for a multi-app fleet so all
1777
+ * app servers share one database/cache. `true` uses the default size.
1778
+ */
1779
+ servicesServer?: boolean | {
1780
+ size?: InstanceSize;
1781
+ };
1319
1782
  /**
1320
1783
  * Mixed instance fleet for cost optimization
1321
1784
  * Allows combining different sizes and spot instances
@@ -1329,10 +1792,20 @@ export interface ComputeConfig {
1329
1792
  */
1330
1793
  fleet?: InstanceConfig[];
1331
1794
  /**
1332
- * Custom machine image (optional)
1333
- * If not specified, uses the provider's default Linux image
1795
+ * Custom machine image (optional). For the Forge path this is a ts-cloud
1796
+ * **golden image** (a Hetzner snapshot / AWS AMI baked with the full stack
1797
+ * nginx, php-fpm, Composer, services). If not specified, the provider's
1798
+ * default Ubuntu image is used and the stack is installed at first boot.
1799
+ * @see bakedImage
1334
1800
  */
1335
1801
  image?: string;
1802
+ /**
1803
+ * The configured {@link image} is a pre-provisioned golden image that already
1804
+ * has the runtime + PHP + services + base packages installed. Boot skips the
1805
+ * install-heavy provisioning for a near-instant start. Build + publish the
1806
+ * image with the bake recipe (see scripts/build-image.ts). @default false
1807
+ */
1808
+ bakedImage?: boolean;
1336
1809
  /**
1337
1810
  * CloudFront custom origin for the registry/app server (when the site stack
1338
1811
  * fronts EC2 instead of S3-only). Use the EC2 public DNS name, not a raw IP.
@@ -1349,6 +1822,12 @@ export interface ComputeConfig {
1349
1822
  instanceType?: string;
1350
1823
  ami?: string;
1351
1824
  keyPair?: string;
1825
+ /**
1826
+ * IAM instance profile name attached at launch. For the lightweight EC2
1827
+ * boot path, this should grant `AmazonSSMManagedInstanceCore` so deploys
1828
+ * (SSM Run Command) reach the box.
1829
+ */
1830
+ iamInstanceProfile?: string;
1352
1831
  autoScaling?: {
1353
1832
  min?: number;
1354
1833
  max?: number;
@@ -1515,9 +1994,10 @@ export interface ComputeConfig {
1515
1994
  };
1516
1995
  /**
1517
1996
  * Application runtime to install on the instance.
1518
- * Shared by every site that gets deployed to this compute.
1997
+ * Shared by every site that gets deployed to this compute. `'php'` provisions
1998
+ * nginx + php-fpm + Composer (see {@link php}) for Laravel/PHP sites.
1519
1999
  */
1520
- runtime?: 'bun' | 'node' | 'deno';
2000
+ runtime?: 'bun' | 'node' | 'deno' | 'php';
1521
2001
  /**
1522
2002
  * Pinned runtime version (e.g. '1.3.13'). Defaults to 'latest'.
1523
2003
  */
@@ -1552,6 +2032,132 @@ export interface ComputeConfig {
1552
2032
  * static dirs — so an app, docs, and a public site can share one domain.
1553
2033
  */
1554
2034
  proxy?: ComputeProxyConfig;
2035
+ /**
2036
+ * PHP-FPM provisioning. When set (or `runtime: 'php'`), the box installs the
2037
+ * requested PHP versions (via `ppa:ondrej/php`), Composer, and the common
2038
+ * Laravel extension set. Each site picks its version with `SiteConfig.phpVersion`.
2039
+ */
2040
+ php?: ComputePhpConfig;
2041
+ /**
2042
+ * Web server that fronts the box.
2043
+ * - `'nginx'` (default) — per-site nginx vhost + php-fpm, Let's Encrypt via certbot.
2044
+ * - `'rpx'` — the existing `@stacksjs/rpx` gateway with on-demand TLS.
2045
+ * Independent of {@link proxy}, which only configures the rpx engine details.
2046
+ * @default 'nginx'
2047
+ */
2048
+ webServer?: 'nginx' | 'rpx';
2049
+ /**
2050
+ * On-box managed services to install (Forge's single-server model): the
2051
+ * database engine, cache, and search. Each may be `true` for defaults or an
2052
+ * object for pinning a version. Omit to install nothing (e.g. when pointing
2053
+ * the app at a managed/RDS database instead).
2054
+ *
2055
+ * Named `managedServices` to avoid colliding with the ECS microservices
2056
+ * `services` array above.
2057
+ */
2058
+ managedServices?: ComputeServicesConfig;
2059
+ /**
2060
+ * Host firewall (UFW). When enabled, only SSH + the listed ports are open.
2061
+ * On Hetzner this complements the cloud firewall; on a bare box it's the
2062
+ * primary firewall. @default { enabled: true } for PHP boxes
2063
+ */
2064
+ firewall?: ComputeFirewallConfig;
2065
+ /**
2066
+ * Automatic unattended security/system updates (Forge's "maintenance"). When
2067
+ * enabled, installs `unattended-upgrades` and enables daily auto-updates.
2068
+ * @default true for PHP boxes
2069
+ */
2070
+ autoUpdates?: boolean;
2071
+ /**
2072
+ * Scheduled database backups (powered by `ts-backups`), synced to object
2073
+ * storage. Off unless configured.
2074
+ */
2075
+ backups?: ComputeBackupConfig;
2076
+ /**
2077
+ * Operator SSH keys authorized on the box, in addition to the deploy key.
2078
+ * Managed declaratively: keys are written to `authorized_keys` inside a
2079
+ * ts-cloud-managed block on every provision/deploy, so adding one is as
2080
+ * simple as adding an entry here and redeploying.
2081
+ */
2082
+ sshKeys?: SshKeyConfig[];
2083
+ }
2084
+ /** An operator SSH key authorized on the box. See {@link ComputeConfig.sshKeys}. */
2085
+ export interface SshKeyConfig {
2086
+ /** Human label for the key (comment). */
2087
+ name: string;
2088
+ /** The public key line (e.g. `ssh-ed25519 AAAA… user@host`). */
2089
+ publicKey: string;
2090
+ }
2091
+ /** Host firewall (UFW) configuration. See {@link ComputeConfig.firewall}. */
2092
+ export interface ComputeFirewallConfig {
2093
+ /** Enable UFW. @default true */
2094
+ enabled?: boolean;
2095
+ /** TCP ports to allow in addition to SSH/80/443 (always allowed). */
2096
+ allowedPorts?: number[];
2097
+ }
2098
+ /** Scheduled database backup configuration. See {@link ComputeConfig.backups}. */
2099
+ export interface ComputeBackupConfig {
2100
+ /** Enable scheduled backups. @default false */
2101
+ enabled?: boolean;
2102
+ /** Cron schedule for the backup run. @default '0 2 * * *' (daily 02:00) */
2103
+ schedule?: string;
2104
+ /** Keep the newest N backups locally. @default 5 */
2105
+ retentionCount?: number;
2106
+ /** Delete local backups older than N days. @default 30 */
2107
+ retentionDays?: number;
2108
+ /** Object-storage bucket (S3 or Hetzner) the backups are synced to. */
2109
+ bucket?: string;
2110
+ /** S3-compatible endpoint (e.g. Hetzner object storage). Omit for AWS S3. */
2111
+ endpoint?: string;
2112
+ }
2113
+ /**
2114
+ * A PHP version selector. Known versions provide editor autocomplete; the
2115
+ * `(string & {})` arm keeps it open for versions released after this type was
2116
+ * written (e.g. a future `'8.5'`) without a type error.
2117
+ */
2118
+ export type PhpVersion = '8.1' | '8.2' | '8.3' | '8.4' | (string & {});
2119
+ /**
2120
+ * PHP-FPM provisioning for a compute box. See {@link ComputeConfig.php}.
2121
+ */
2122
+ export interface ComputePhpConfig {
2123
+ /**
2124
+ * PHP versions to install (e.g. `['8.3', '8.2']`). Each gets its own php-fpm
2125
+ * pool/socket so sites can pin different versions. @default ['8.3']
2126
+ */
2127
+ versions?: PhpVersion[];
2128
+ /** Default PHP version for sites that don't set `phpVersion`. @default first of `versions` */
2129
+ default?: PhpVersion;
2130
+ /**
2131
+ * Extra PHP extensions to install beyond the Laravel baseline (mbstring, xml,
2132
+ * curl, mysql, pgsql, redis, gd, bcmath, zip, intl). apt package suffixes,
2133
+ * e.g. `['imagick', 'swoole']`.
2134
+ */
2135
+ extensions?: string[];
2136
+ }
2137
+ /**
2138
+ * On-box managed services (database / cache / search) for a compute box.
2139
+ * Each entry is `true` (install with defaults) or an object pinning a version.
2140
+ * See {@link ComputeConfig.services}.
2141
+ */
2142
+ export interface ComputeServicesConfig {
2143
+ mysql?: boolean | {
2144
+ version?: string;
2145
+ };
2146
+ mariadb?: boolean | {
2147
+ version?: string;
2148
+ };
2149
+ postgres?: boolean | {
2150
+ version?: string;
2151
+ };
2152
+ redis?: boolean | {
2153
+ version?: string;
2154
+ };
2155
+ memcached?: boolean | {
2156
+ version?: string;
2157
+ };
2158
+ meilisearch?: boolean | {
2159
+ version?: string;
2160
+ };
1555
2161
  }
1556
2162
  /**
1557
2163
  * Reverse-proxy gateway provisioning for a compute box. The gateway is