@stacksjs/ts-cloud 0.4.1 → 0.5.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/bin/cli.js +7257 -1035
- package/dist/deploy/management-dashboard.d.ts +39 -0
- package/dist/index.js +79 -14
- package/dist/ui/404.html +22 -0
- package/dist/ui/index.html +995 -0
- package/dist/ui/serverless.html +995 -0
- package/package.json +3 -3
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-deploy of the ts-cloud management dashboard (the `@ts-cloud/ui` stx app)
|
|
3
|
+
* on every server provision/deploy.
|
|
4
|
+
*
|
|
5
|
+
* Resolves the UI directory (the repo's local `ui/`, else the prebuilt UI that
|
|
6
|
+
* ships inside the installed package at `dist/ui`), derives a `dashboard.<apex>`
|
|
7
|
+
* host, and injects it into `config.sites` as a server-static site. It is served
|
|
8
|
+
* behind htpasswd ONLY when `TS_CLOUD_UI_PASSWORD` is set; when it is not, the
|
|
9
|
+
* dashboard is served without auth (no password is invented).
|
|
10
|
+
*
|
|
11
|
+
* Env:
|
|
12
|
+
* - `TS_CLOUD_UI_PASSWORD` htpasswd password (unset ⇒ no auth)
|
|
13
|
+
* - `TS_CLOUD_UI_USERNAME` htpasswd user (default `admin`)
|
|
14
|
+
* - `TS_CLOUD_UI_DOMAIN` explicit dashboard host (else `dashboard.<apex>`)
|
|
15
|
+
* - `TS_CLOUD_UI_REALM` browser auth realm
|
|
16
|
+
* - `TS_CLOUD_UI_DISABLE` set truthy to skip auto-deploy
|
|
17
|
+
*/
|
|
18
|
+
import type { CloudConfig } from '@stacksjs/ts-cloud';
|
|
19
|
+
export interface EnsureDashboardLogger {
|
|
20
|
+
info: (msg: string) => void;
|
|
21
|
+
warn: (msg: string) => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the UI source to ship. Prefers the repo's local `ui/` (built on the
|
|
25
|
+
* deploy machine), then the prebuilt UI bundled in the installed package.
|
|
26
|
+
* Returns `{ uiRoot, build }` or null when no UI is available.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveUiSource(cwd: string): {
|
|
29
|
+
uiRoot: string;
|
|
30
|
+
build: string | false;
|
|
31
|
+
} | null;
|
|
32
|
+
/**
|
|
33
|
+
* Inject the management dashboard into `config.sites` for a server deploy.
|
|
34
|
+
* Mutates and returns `config`. Idempotent and safe to call on every deploy.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ensureManagementDashboard(config: CloudConfig, options?: {
|
|
37
|
+
cwd?: string;
|
|
38
|
+
logger?: EnsureDashboardLogger;
|
|
39
|
+
}): CloudConfig;
|
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@ import { readFileSync as readFileSync5 } from "node:fs";
|
|
|
48
48
|
import { dirname as dirname42, join as join8 } from "node:path";
|
|
49
49
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
50
50
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
51
|
-
import { existsSync as existsSync52, mkdtempSync as mkdtempSync3, readdirSync as readdirSync52, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync32, writeFileSync as writeFileSync52 } from "node:fs";
|
|
51
|
+
import { existsSync as existsSync52, mkdirSync as mkdirSync22, mkdtempSync as mkdtempSync3, readdirSync as readdirSync52, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync32, writeFileSync as writeFileSync52 } from "node:fs";
|
|
52
52
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
53
53
|
import { join as join9, relative as relative22 } from "node:path";
|
|
54
54
|
import { execSync as execSync2 } from "node:child_process";
|
|
@@ -7654,6 +7654,56 @@ function createDashboardSite(options) {
|
|
|
7654
7654
|
}
|
|
7655
7655
|
};
|
|
7656
7656
|
}
|
|
7657
|
+
function apexOf(domain) {
|
|
7658
|
+
const parts = domain.split(".").filter(Boolean);
|
|
7659
|
+
return parts.length <= 2 ? domain : parts.slice(-2).join(".");
|
|
7660
|
+
}
|
|
7661
|
+
function resolveDashboardDomain(config6, environment, explicit) {
|
|
7662
|
+
if (explicit)
|
|
7663
|
+
return explicit;
|
|
7664
|
+
const candidates = [];
|
|
7665
|
+
for (const site of Object.values(config6.sites ?? {})) {
|
|
7666
|
+
const d = site?.domain;
|
|
7667
|
+
if (typeof d === "string")
|
|
7668
|
+
candidates.push(d);
|
|
7669
|
+
else if (Array.isArray(d))
|
|
7670
|
+
candidates.push(...d.filter((x) => typeof x === "string"));
|
|
7671
|
+
}
|
|
7672
|
+
if (environment && config6.environments?.[environment]?.domain)
|
|
7673
|
+
candidates.push(config6.environments[environment].domain);
|
|
7674
|
+
const dnsDomain = config6.infrastructure?.dns?.domain;
|
|
7675
|
+
if (dnsDomain)
|
|
7676
|
+
candidates.push(dnsDomain);
|
|
7677
|
+
const base = candidates.find((d) => d && !d.startsWith("dashboard."));
|
|
7678
|
+
if (!base)
|
|
7679
|
+
return null;
|
|
7680
|
+
return `dashboard.${apexOf(base)}`;
|
|
7681
|
+
}
|
|
7682
|
+
function hasManagementDashboardSite(config6) {
|
|
7683
|
+
return Object.entries(config6.sites ?? {}).some(([name, site]) => {
|
|
7684
|
+
if (!site)
|
|
7685
|
+
return false;
|
|
7686
|
+
const root = site.root ?? "";
|
|
7687
|
+
return name === "dashboard" || root === "ui/dist" || root.endsWith("/ui/dist") || root.endsWith("/dist/ui");
|
|
7688
|
+
});
|
|
7689
|
+
}
|
|
7690
|
+
function resolveManagementDashboardSite(config6, environment, opts) {
|
|
7691
|
+
if (hasManagementDashboardSite(config6))
|
|
7692
|
+
return null;
|
|
7693
|
+
const domain = resolveDashboardDomain(config6, environment, opts.domain);
|
|
7694
|
+
if (!domain)
|
|
7695
|
+
return null;
|
|
7696
|
+
const site = {
|
|
7697
|
+
root: opts.uiRoot,
|
|
7698
|
+
deploy: "server",
|
|
7699
|
+
type: "static",
|
|
7700
|
+
domain,
|
|
7701
|
+
ssl: { provider: "letsencrypt" },
|
|
7702
|
+
...opts.build === false || opts.build === undefined ? {} : { build: opts.build },
|
|
7703
|
+
...opts.password ? { auth: { username: opts.username || "admin", password: opts.password, realm: opts.realm } } : {}
|
|
7704
|
+
};
|
|
7705
|
+
return { name: "dashboard", site };
|
|
7706
|
+
}
|
|
7657
7707
|
function deepMerge5(target, source) {
|
|
7658
7708
|
const result = { ...target };
|
|
7659
7709
|
for (const key in source) {
|
|
@@ -26343,13 +26393,18 @@ function phpLayerBuildStage(phpVersion, asName) {
|
|
|
26343
26393
|
const scl = `php${phpVersion.replace(".", "")}`;
|
|
26344
26394
|
const packages = phpLayerPackages(phpVersion).join(" \\\n ");
|
|
26345
26395
|
const sclRoot = `/opt/remi/${scl}/root`;
|
|
26346
|
-
const
|
|
26396
|
+
const sclEtc = `/etc/opt/remi/${scl}`;
|
|
26397
|
+
const from = asName ? `FROM almalinux:9 AS ${asName}` : "FROM almalinux:9";
|
|
26347
26398
|
return `${from}
|
|
26348
26399
|
|
|
26349
|
-
# Remi
|
|
26350
|
-
|
|
26400
|
+
# Build PHP on AlmaLinux 9 (where Remi's version-isolated SCL packages install
|
|
26401
|
+
# cleanly — Remi's release RPM rejects Amazon Linux 2023). AlmaLinux 9 shares
|
|
26402
|
+
# glibc 2.34 with the provided.al2023 Lambda runtime, and the relocation step
|
|
26403
|
+
# below bundles every non-glibc shared lib under /opt/php/lib, so the resulting
|
|
26404
|
+
# /opt tree runs on Lambda's provided.al2023.
|
|
26405
|
+
RUN dnf -y install epel-release dnf-plugins-core 'dnf-command(config-manager)' && \\
|
|
26406
|
+
dnf config-manager --set-enabled crb && \\
|
|
26351
26407
|
dnf -y install https://rpms.remirepo.net/enterprise/remi-release-9.rpm && \\
|
|
26352
|
-
dnf -y update && \\
|
|
26353
26408
|
dnf -y install \\
|
|
26354
26409
|
${packages} \\
|
|
26355
26410
|
findutils tar gzip && \\
|
|
@@ -26362,7 +26417,10 @@ RUN set -eux; \\
|
|
|
26362
26417
|
cp ${sclRoot}/usr/sbin/php-fpm /opt/php/sbin/php-fpm; \\
|
|
26363
26418
|
EXT_DIR="$(${sclRoot}/usr/bin/php -r 'echo ini_get("extension_dir");')"; \\
|
|
26364
26419
|
cp -a "$EXT_DIR"/*.so /opt/php/lib/php/modules/ || true; \\
|
|
26365
|
-
cp -a ${
|
|
26420
|
+
cp -a ${sclEtc}/php.d/*.ini /opt/php/etc/php.d/ 2>/dev/null || true; \\
|
|
26421
|
+
# Rewrite any absolute extension/zend_extension paths to bare names so they
|
|
26422
|
+
# resolve against the relocated extension_dir.
|
|
26423
|
+
sed -i -E "s#(zend_)?extension *= *.*/([^/]+\\.so)#\\1extension=\\2#" /opt/php/etc/php.d/*.ini 2>/dev/null || true; \\
|
|
26366
26424
|
# Copy shared-library dependencies of php, php-fpm, and the extension modules.
|
|
26367
26425
|
for bin in /opt/php/bin/php /opt/php/sbin/php-fpm /opt/php/lib/php/modules/*.so; do \\
|
|
26368
26426
|
ldd "$bin" 2>/dev/null | awk '/=>/{print $3}/ld-linux/{print $1}' | sort -u | while read -r lib; do \\
|
|
@@ -26370,10 +26428,9 @@ RUN set -eux; \\
|
|
|
26370
26428
|
done; \\
|
|
26371
26429
|
done
|
|
26372
26430
|
|
|
26373
|
-
#
|
|
26431
|
+
# Build the relocated main php.ini: extension_dir + every per-extension directive.
|
|
26374
26432
|
RUN printf 'extension_dir=/opt/php/lib/php/modules\\n' > /opt/php/etc/php.ini && \\
|
|
26375
26433
|
cat /opt/php/etc/php.d/*.ini >> /opt/php/etc/php.ini 2>/dev/null || true
|
|
26376
|
-
ENV PHP_INI_SCAN_DIR=/opt/php/etc/php.d
|
|
26377
26434
|
|
|
26378
26435
|
# Runtime assets (bootstrap, runtime loops, fpm config) are added by the build
|
|
26379
26436
|
# orchestrator after the image is produced. Export /opt as the layer payload.
|
|
@@ -26685,16 +26742,17 @@ function buildPhpRuntimeLayerZip(options = {}) {
|
|
|
26685
26742
|
writeFileSync52(join9(stage, "Dockerfile"), generatePhpLayerDockerfile(options));
|
|
26686
26743
|
step("Building PHP runtime image (docker)");
|
|
26687
26744
|
execFileSync2("docker", ["build", "--platform", platform, "-t", imageTag, stage], { stdio: "inherit" });
|
|
26688
|
-
step("Extracting /opt from image");
|
|
26745
|
+
step("Extracting /opt/php from image");
|
|
26689
26746
|
const cid = execFileSync2("docker", ["create", "--platform", platform, imageTag], { encoding: "utf-8" }).trim();
|
|
26690
26747
|
const optDir = join9(stage, "opt");
|
|
26748
|
+
mkdirSync22(optDir, { recursive: true });
|
|
26691
26749
|
try {
|
|
26692
|
-
execFileSync2("docker", ["cp", `${cid}:/opt
|
|
26750
|
+
execFileSync2("docker", ["cp", `${cid}:/opt/php`, join9(optDir, "php")], { stdio: "inherit" });
|
|
26693
26751
|
} finally {
|
|
26694
26752
|
execFileSync2("docker", ["rm", cid], { stdio: "ignore" });
|
|
26695
26753
|
}
|
|
26696
|
-
if (!existsSync52(optDir))
|
|
26697
|
-
throw new Error("layer build produced no /opt
|
|
26754
|
+
if (!existsSync52(join9(optDir, "php", "bin", "php")))
|
|
26755
|
+
throw new Error("layer build produced no /opt/php/bin/php");
|
|
26698
26756
|
const entries = [];
|
|
26699
26757
|
for (const file of walk(optDir)) {
|
|
26700
26758
|
const rel = relative22(optDir, file).replace(/\\/g, "/");
|
|
@@ -45734,6 +45792,7 @@ async function sendFallbackSms(to, message) {
|
|
|
45734
45792
|
PHP_LAYER_EXTENSIONS = [
|
|
45735
45793
|
"cli",
|
|
45736
45794
|
"fpm",
|
|
45795
|
+
"curl",
|
|
45737
45796
|
"mbstring",
|
|
45738
45797
|
"xml",
|
|
45739
45798
|
"pdo",
|
|
@@ -83441,15 +83500,18 @@ function buildDatabaseSetupScript(database, services = {}) {
|
|
|
83441
83500
|
"TS_CLOUD_PG_EOF"
|
|
83442
83501
|
];
|
|
83443
83502
|
}
|
|
83503
|
+
const sock = useMariadb ? "/var/lib/pantry/mariadb/mariadbd.sock" : "/var/lib/pantry/mysql/mysqld.sock";
|
|
83444
83504
|
const ident = (v) => v.replace(/`/g, "``");
|
|
83445
83505
|
const lit = (v) => v.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
83446
83506
|
return [
|
|
83447
83507
|
pantryEnvActivation(),
|
|
83448
|
-
|
|
83449
|
-
|
|
83508
|
+
`for i in $(seq 1 30); do mysqladmin --socket=${sock} -u root ping 2>/dev/null | grep -q alive && break; sleep 2; done`,
|
|
83509
|
+
`mysql --socket=${sock} -u root <<'TS_CLOUD_SQL_EOF'`,
|
|
83450
83510
|
`CREATE DATABASE IF NOT EXISTS \`${ident(name)}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;`,
|
|
83451
83511
|
`CREATE USER IF NOT EXISTS '${lit(user)}'@'%' IDENTIFIED BY '${lit(pass)}';`,
|
|
83512
|
+
`CREATE USER IF NOT EXISTS '${lit(user)}'@'localhost' IDENTIFIED BY '${lit(pass)}';`,
|
|
83452
83513
|
`GRANT ALL PRIVILEGES ON \`${ident(name)}\`.* TO '${lit(user)}'@'%';`,
|
|
83514
|
+
`GRANT ALL PRIVILEGES ON \`${ident(name)}\`.* TO '${lit(user)}'@'localhost';`,
|
|
83453
83515
|
"FLUSH PRIVILEGES;",
|
|
83454
83516
|
"TS_CLOUD_SQL_EOF"
|
|
83455
83517
|
];
|
|
@@ -86266,9 +86328,11 @@ export {
|
|
|
86266
86328
|
resolveQueueNames,
|
|
86267
86329
|
resolveProjectStackName,
|
|
86268
86330
|
resolveObjectStorage,
|
|
86331
|
+
resolveManagementDashboardSite,
|
|
86269
86332
|
resolveHetznerApiToken,
|
|
86270
86333
|
resolveExecStart,
|
|
86271
86334
|
resolveDeployBucketName,
|
|
86335
|
+
resolveDashboardDomain,
|
|
86272
86336
|
resolveCredentials,
|
|
86273
86337
|
resolveCloudProvider,
|
|
86274
86338
|
resolveApp,
|
|
@@ -86331,6 +86395,7 @@ export {
|
|
|
86331
86395
|
hashFile,
|
|
86332
86396
|
hashDirectory,
|
|
86333
86397
|
hashBuffer,
|
|
86398
|
+
hasManagementDashboardSite,
|
|
86334
86399
|
guardDutyManager,
|
|
86335
86400
|
globalResourceManager,
|
|
86336
86401
|
getTimestamp,
|
package/dist/ui/404.html
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Page Not Found</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
|
|
9
|
+
.container { text-align: center; }
|
|
10
|
+
h1 { font-size: 6rem; margin: 0; color: #333; }
|
|
11
|
+
p { color: #666; }
|
|
12
|
+
a { color: #0066cc; }
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div class="container">
|
|
17
|
+
<h1>404</h1>
|
|
18
|
+
<p>Page not found</p>
|
|
19
|
+
<a href="/">Go home</a>
|
|
20
|
+
</div>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|