percher 0.4.0 → 0.4.1
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/percher.js +111049 -18287
- package/dist/package.json +13 -4
- package/dist/src/cli-utils.d.ts +9 -0
- package/dist/src/cli-utils.d.ts.map +1 -1
- package/dist/src/cli-utils.js +18 -0
- package/dist/src/cli-utils.js.map +1 -1
- package/dist/src/commands/account.d.ts.map +1 -1
- package/dist/src/commands/account.js +63 -7
- package/dist/src/commands/account.js.map +1 -1
- package/dist/src/commands/admin.d.ts +17 -0
- package/dist/src/commands/admin.d.ts.map +1 -0
- package/dist/src/commands/admin.js +92 -0
- package/dist/src/commands/admin.js.map +1 -0
- package/dist/src/commands/apps.d.ts.map +1 -1
- package/dist/src/commands/apps.js +60 -8
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/deploys.d.ts.map +1 -1
- package/dist/src/commands/deploys.js +12 -2
- package/dist/src/commands/deploys.js.map +1 -1
- package/dist/src/commands/migrate.d.ts +4 -0
- package/dist/src/commands/migrate.d.ts.map +1 -0
- package/dist/src/commands/migrate.js +142 -0
- package/dist/src/commands/migrate.js.map +1 -0
- package/dist/src/commands/sharing.d.ts +23 -0
- package/dist/src/commands/sharing.d.ts.map +1 -0
- package/dist/src/commands/sharing.js +162 -0
- package/dist/src/commands/sharing.js.map +1 -0
- package/dist/src/commands/transfers.d.ts +20 -0
- package/dist/src/commands/transfers.d.ts.map +1 -0
- package/dist/src/commands/transfers.js +117 -0
- package/dist/src/commands/transfers.js.map +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +42 -4
- package/dist/src/index.js.map +1 -1
- package/dist/templates/astro/Caddyfile +6 -0
- package/dist/templates/astro/Dockerfile +34 -0
- package/dist/templates/astro/astro.config.mjs +3 -0
- package/dist/templates/astro/package.json +14 -0
- package/dist/templates/astro/percher.toml +11 -0
- package/dist/templates/astro/src/pages/index.astro +15 -0
- package/dist/templates/bun/index.ts +23 -0
- package/dist/templates/bun/package.json +8 -0
- package/dist/templates/bun/percher.toml +10 -0
- package/dist/templates/django/Procfile +1 -0
- package/dist/templates/django/app/__init__.py +0 -0
- package/dist/templates/django/app/settings.py +44 -0
- package/dist/templates/django/app/urls.py +21 -0
- package/dist/templates/django/app/wsgi.py +5 -0
- package/dist/templates/django/manage.py +19 -0
- package/dist/templates/django/percher.toml +11 -0
- package/dist/templates/django/requirements.txt +2 -0
- package/dist/templates/dotnet/Dockerfile +18 -0
- package/dist/templates/dotnet/Program.cs +23 -0
- package/dist/templates/dotnet/app.csproj +9 -0
- package/dist/templates/dotnet/percher.toml +11 -0
- package/dist/templates/eleventy/.eleventy.js +12 -0
- package/dist/templates/eleventy/Caddyfile +6 -0
- package/dist/templates/eleventy/Dockerfile +34 -0
- package/dist/templates/eleventy/package.json +12 -0
- package/dist/templates/eleventy/percher.toml +11 -0
- package/dist/templates/eleventy/src/_includes/base.njk +12 -0
- package/dist/templates/eleventy/src/assets/style.css +12 -0
- package/dist/templates/eleventy/src/index.md +10 -0
- package/dist/templates/elysia/index.ts +8 -0
- package/dist/templates/elysia/package.json +12 -0
- package/dist/templates/elysia/percher.toml +11 -0
- package/dist/templates/express/index.ts +16 -0
- package/dist/templates/express/package.json +11 -0
- package/dist/templates/express/percher.toml +11 -0
- package/dist/templates/fastify/index.ts +13 -0
- package/dist/templates/fastify/package.json +11 -0
- package/dist/templates/fastify/percher.toml +11 -0
- package/dist/templates/flask/main.py +23 -0
- package/dist/templates/flask/percher.toml +11 -0
- package/dist/templates/flask/requirements.txt +1 -0
- package/dist/templates/go/Dockerfile +11 -0
- package/dist/templates/go/go.mod +3 -0
- package/dist/templates/go/main.go +53 -0
- package/dist/templates/go/percher.toml +11 -0
- package/dist/templates/hono/index.ts +12 -0
- package/dist/templates/hono/package.json +11 -0
- package/dist/templates/hono/percher.toml +11 -0
- package/dist/templates/htmx/index.ts +32 -0
- package/dist/templates/htmx/package.json +15 -0
- package/dist/templates/htmx/percher.toml +11 -0
- package/dist/templates/hugo/Caddyfile +6 -0
- package/dist/templates/hugo/Dockerfile +32 -0
- package/dist/templates/hugo/content/_index.md +7 -0
- package/dist/templates/hugo/hugo.toml +11 -0
- package/dist/templates/hugo/layouts/index.html +22 -0
- package/dist/templates/hugo/percher.toml +11 -0
- package/dist/templates/java/Dockerfile +15 -0
- package/dist/templates/java/Main.java +77 -0
- package/dist/templates/java/percher.toml +11 -0
- package/dist/templates/laravel/Dockerfile +15 -0
- package/dist/templates/laravel/composer.json +15 -0
- package/dist/templates/laravel/percher.toml +11 -0
- package/dist/templates/laravel/public/index.php +37 -0
- package/dist/templates/nextjs/app/api/health/route.ts +3 -0
- package/dist/templates/nextjs/app/layout.tsx +11 -0
- package/dist/templates/nextjs/app/page.tsx +10 -0
- package/dist/templates/nextjs/next.config.ts +5 -0
- package/dist/templates/nextjs/package.json +15 -0
- package/dist/templates/nextjs/percher.toml +15 -0
- package/dist/templates/node/index.js +26 -0
- package/dist/templates/node/index.ts +12 -0
- package/dist/templates/node/package.json +8 -0
- package/dist/templates/node/percher.toml +10 -0
- package/dist/templates/nuxt/app.vue +6 -0
- package/dist/templates/nuxt/nuxt.config.ts +4 -0
- package/dist/templates/nuxt/package.json +15 -0
- package/dist/templates/nuxt/percher.toml +15 -0
- package/dist/templates/nuxt/server/api/health.ts +1 -0
- package/dist/templates/phoenix/Dockerfile +15 -0
- package/dist/templates/phoenix/lib/application.ex +15 -0
- package/dist/templates/phoenix/lib/router.ex +39 -0
- package/dist/templates/phoenix/mix.exs +30 -0
- package/dist/templates/phoenix/percher.toml +11 -0
- package/dist/templates/preact/Caddyfile +7 -0
- package/dist/templates/preact/Dockerfile +34 -0
- package/dist/templates/preact/index.html +12 -0
- package/dist/templates/preact/package.json +19 -0
- package/dist/templates/preact/percher.toml +11 -0
- package/dist/templates/preact/src/main.tsx +13 -0
- package/dist/templates/preact/vite.config.ts +6 -0
- package/dist/templates/python/main.py +13 -0
- package/dist/templates/python/percher.toml +11 -0
- package/dist/templates/python/requirements.txt +2 -0
- package/dist/templates/rails/Dockerfile +11 -0
- package/dist/templates/rails/Gemfile +12 -0
- package/dist/templates/rails/config.ru +32 -0
- package/dist/templates/rails/percher.toml +11 -0
- package/dist/templates/remix/app/root.tsx +19 -0
- package/dist/templates/remix/app/routes/_index.tsx +10 -0
- package/dist/templates/remix/app/routes/health.tsx +3 -0
- package/dist/templates/remix/package.json +30 -0
- package/dist/templates/remix/percher.toml +15 -0
- package/dist/templates/remix/vite.config.ts +7 -0
- package/dist/templates/rust/Cargo.toml +19 -0
- package/dist/templates/rust/Dockerfile +14 -0
- package/dist/templates/rust/percher.toml +11 -0
- package/dist/templates/rust/src/main.rs +45 -0
- package/dist/templates/solid/Caddyfile +7 -0
- package/dist/templates/solid/Dockerfile +34 -0
- package/dist/templates/solid/index.html +12 -0
- package/dist/templates/solid/package.json +19 -0
- package/dist/templates/solid/percher.toml +11 -0
- package/dist/templates/solid/src/main.tsx +13 -0
- package/dist/templates/solid/vite.config.ts +6 -0
- package/dist/templates/static/index.html +15 -0
- package/dist/templates/static/percher.toml +6 -0
- package/dist/templates/static/style.css +32 -0
- package/dist/templates/sveltekit/package.json +19 -0
- package/dist/templates/sveltekit/percher.toml +15 -0
- package/dist/templates/sveltekit/src/app.html +12 -0
- package/dist/templates/sveltekit/src/routes/+page.svelte +2 -0
- package/dist/templates/sveltekit/src/routes/health/+server.ts +5 -0
- package/dist/templates/sveltekit/svelte.config.js +8 -0
- package/dist/templates/sveltekit/vite.config.js +6 -0
- package/dist/templates/vite-react/Caddyfile +7 -0
- package/dist/templates/vite-react/Dockerfile +34 -0
- package/dist/templates/vite-react/index.html +12 -0
- package/dist/templates/vite-react/package.json +22 -0
- package/dist/templates/vite-react/percher.toml +11 -0
- package/dist/templates/vite-react/src/main.tsx +20 -0
- package/dist/templates/vite-react/vite.config.ts +6 -0
- package/dist/templates/vite-vue/Caddyfile +7 -0
- package/dist/templates/vite-vue/Dockerfile +34 -0
- package/dist/templates/vite-vue/index.html +12 -0
- package/dist/templates/vite-vue/package.json +19 -0
- package/dist/templates/vite-vue/percher.toml +11 -0
- package/dist/templates/vite-vue/src/main.ts +12 -0
- package/dist/templates/vite-vue/vite.config.ts +6 -0
- package/package.json +13 -4
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as core from "@percher/core";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import { writeLine } from "../cli-utils";
|
|
4
|
+
/**
|
|
5
|
+
* FUTURE15 Fas D — CLI surface for ownership transfer.
|
|
6
|
+
*
|
|
7
|
+
* percher transfer <email> [--app <name>]
|
|
8
|
+
* percher transfer --cancel <transferId> [--app <name>]
|
|
9
|
+
* percher transfer --list [--app <name>]
|
|
10
|
+
*
|
|
11
|
+
* percher transfers (bare = incoming list)
|
|
12
|
+
* percher transfers accept <transferId>
|
|
13
|
+
* percher transfers decline <transferId>
|
|
14
|
+
*
|
|
15
|
+
* The `transfer` (singular) command operates on ONE app; the
|
|
16
|
+
* `transfers` (plural) command is recipient-managed across apps.
|
|
17
|
+
* Same parent-action-dispatcher pattern as the share/invites
|
|
18
|
+
* commands so cac's first-token-only matching works.
|
|
19
|
+
*/
|
|
20
|
+
export function registerTransfersCommands(cli, ctx) {
|
|
21
|
+
// ── transfer (singular, owner-managed for one app) ─────────────────
|
|
22
|
+
cli
|
|
23
|
+
.command("transfer [emailOrTransferId]", "Transfer ownership of an app: `transfer <email>` initiates; `transfer --cancel <id>` cancels; `transfer --list` shows history")
|
|
24
|
+
.option("--app <name>", "App name (overrides percher.toml)")
|
|
25
|
+
.option("--cancel", "Cancel a pending transfer by id (positional arg is the transferId)")
|
|
26
|
+
.option("--list", "List the transfer history for this app")
|
|
27
|
+
.action(async (emailOrTransferId, options) => {
|
|
28
|
+
if (options.list) {
|
|
29
|
+
const rows = await core.transferListForApp(ctx, { app: options.app });
|
|
30
|
+
if (rows.length === 0) {
|
|
31
|
+
await writeLine(ctx, pc.dim("No transfers for this app."));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
for (const r of rows) {
|
|
35
|
+
const status = r.status === "pending"
|
|
36
|
+
? pc.yellow("pending")
|
|
37
|
+
: r.status === "accepted"
|
|
38
|
+
? pc.green("accepted")
|
|
39
|
+
: pc.dim(r.status);
|
|
40
|
+
await writeLine(ctx, `${r.id} ${status.padEnd(20)} → ${r.toEmail} (${r.createdAt})`);
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (options.cancel) {
|
|
45
|
+
if (!emailOrTransferId) {
|
|
46
|
+
throw new Error("Usage: percher transfer --cancel <transferId> [--app <name>]");
|
|
47
|
+
}
|
|
48
|
+
await core.transferCancel(ctx, {
|
|
49
|
+
app: options.app,
|
|
50
|
+
transferId: emailOrTransferId,
|
|
51
|
+
});
|
|
52
|
+
await writeLine(ctx, `${pc.green("✓")} Cancelled transfer ${emailOrTransferId}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// No flag → initiate. Positional arg is the recipient email.
|
|
56
|
+
if (!emailOrTransferId) {
|
|
57
|
+
throw new Error("Usage: percher transfer <recipient-email> [--app <name>] | percher transfer --cancel <id> | percher transfer --list");
|
|
58
|
+
}
|
|
59
|
+
const initiated = await core.transferInitiate(ctx, {
|
|
60
|
+
app: options.app,
|
|
61
|
+
email: emailOrTransferId,
|
|
62
|
+
});
|
|
63
|
+
await writeLine(ctx, `${pc.green("✓")} Transfer initiated to ${initiated.toEmail} (id ${initiated.id})`);
|
|
64
|
+
await writeLine(ctx, pc.dim(`Expires ${new Date(initiated.expiresAt).toLocaleDateString()}. The recipient must accept via 'percher transfers accept ${initiated.id}'.`));
|
|
65
|
+
if (initiated.notes) {
|
|
66
|
+
await writeLine(ctx, pc.yellow(` ⚠ ${initiated.notes}`));
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
// ── transfers (plural, recipient-managed across apps) ──────────────
|
|
70
|
+
cli
|
|
71
|
+
.command("transfers [action] [transferId]", "Incoming ownership transfers: list (default) | accept <transferId> | decline <transferId>")
|
|
72
|
+
.action(async (action, transferId) => {
|
|
73
|
+
if (action === undefined || action === "list") {
|
|
74
|
+
const rows = await core.transfersIncoming(ctx, {});
|
|
75
|
+
if (rows.length === 0) {
|
|
76
|
+
await writeLine(ctx, pc.dim("No pending incoming transfers."));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
for (const r of rows) {
|
|
80
|
+
await writeLine(ctx, `${r.id} ${r.appName.padEnd(24)} from ${r.fromEmail} (expires ${new Date(r.expiresAt).toLocaleDateString()})`);
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (action === "accept") {
|
|
85
|
+
if (!transferId) {
|
|
86
|
+
throw new Error("Usage: percher transfers accept <transferId>");
|
|
87
|
+
}
|
|
88
|
+
const accepted = await core.transfersAccept(ctx, { transferId });
|
|
89
|
+
await writeLine(ctx, `${pc.green("✓")} You now own ${pc.cyan(accepted.appName)}. Run 'percher open --app ${accepted.appName}' to see it.`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (action === "decline") {
|
|
93
|
+
if (!transferId) {
|
|
94
|
+
throw new Error("Usage: percher transfers decline <transferId>");
|
|
95
|
+
}
|
|
96
|
+
await core.transfersDecline(ctx, { transferId });
|
|
97
|
+
await writeLine(ctx, `${pc.green("✓")} Declined transfer ${transferId}.`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
throw new Error(`Unknown transfers action "${action}". Use list | accept <id> | decline <id>.`);
|
|
101
|
+
});
|
|
102
|
+
// Help-only registrations for --help discoverability.
|
|
103
|
+
cli.command("transfers list", "List pending incoming ownership transfers").action(async () => {
|
|
104
|
+
/* unreachable */
|
|
105
|
+
});
|
|
106
|
+
cli
|
|
107
|
+
.command("transfers accept [transferId]", "Accept an incoming ownership transfer")
|
|
108
|
+
.action(async () => {
|
|
109
|
+
/* unreachable */
|
|
110
|
+
});
|
|
111
|
+
cli
|
|
112
|
+
.command("transfers decline [transferId]", "Decline an incoming ownership transfer")
|
|
113
|
+
.action(async () => {
|
|
114
|
+
/* unreachable */
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=transfers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transfers.js","sourceRoot":"","sources":["../../../src/commands/transfers.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAoB7C,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAmEhF"}
|
package/dist/src/index.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import * as core from "@percher/core";
|
|
2
1
|
import cac from "cac";
|
|
3
|
-
import { SilentExitError } from "./cli-utils";
|
|
2
|
+
import { SilentExitError, writeUnknownCommand } from "./cli-utils";
|
|
4
3
|
import { registerAccountCommands } from "./commands/account";
|
|
4
|
+
import { registerAdminCommands } from "./commands/admin";
|
|
5
5
|
import { registerAppsCommands } from "./commands/apps";
|
|
6
6
|
import { registerAuthCommands } from "./commands/auth";
|
|
7
7
|
import { registerDataCommands } from "./commands/data";
|
|
8
8
|
import { registerDeploysCommands } from "./commands/deploys";
|
|
9
9
|
import { registerEnvCommands } from "./commands/env";
|
|
10
10
|
import { registerGithubCommands } from "./commands/github";
|
|
11
|
+
import { registerMigrateCommands } from "./commands/migrate";
|
|
12
|
+
import { registerSharingCommands } from "./commands/sharing";
|
|
11
13
|
import { registerStatusCommands } from "./commands/status";
|
|
14
|
+
import { registerTransfersCommands } from "./commands/transfers";
|
|
12
15
|
import { createCliContext } from "./context";
|
|
13
16
|
import { renderError } from "./output";
|
|
14
|
-
import {
|
|
17
|
+
import { showTelemetryNotice, trackEvent, VERSION } from "./telemetry";
|
|
15
18
|
import { checkForUpdates } from "./version-check";
|
|
16
19
|
export async function run(argv, ctxOverride) {
|
|
17
20
|
const cli = cac("percher");
|
|
@@ -23,14 +26,49 @@ export async function run(argv, ctxOverride) {
|
|
|
23
26
|
registerAuthCommands(cli, ctx);
|
|
24
27
|
registerAccountCommands(cli, ctx);
|
|
25
28
|
registerGithubCommands(cli, ctx);
|
|
29
|
+
registerMigrateCommands(cli, ctx);
|
|
30
|
+
registerSharingCommands(cli, ctx);
|
|
26
31
|
registerStatusCommands(cli, ctx);
|
|
32
|
+
registerTransfersCommands(cli, ctx);
|
|
33
|
+
registerAdminCommands(cli, ctx);
|
|
27
34
|
cli.help();
|
|
28
35
|
cli.version(VERSION);
|
|
29
36
|
const command = argv[0] ?? "help";
|
|
30
37
|
try {
|
|
31
38
|
await showTelemetryNotice();
|
|
32
39
|
cli.parse(["percher", "percher", ...argv], { run: false });
|
|
33
|
-
|
|
40
|
+
// cac silently no-ops when no command matches: runMatchedCommand()
|
|
41
|
+
// returns early on an undefined matchedCommand, so `percher
|
|
42
|
+
// bogus-cmd` used to exit 0 with no output. cac also unsets
|
|
43
|
+
// matchedCommand after printing help/version, so a typo paired
|
|
44
|
+
// with `--help` (`percher bogus-cmd --help`) looked identical to a
|
|
45
|
+
// valid `percher open --help`. Resolve the ambiguity by checking
|
|
46
|
+
// the requested positional against the registered command names
|
|
47
|
+
// directly, not by inspecting matchedCommand or the help flag.
|
|
48
|
+
// (dogfood Fas 2 F2-#1; --help variant caught in Codex review.)
|
|
49
|
+
const requested = argv[0];
|
|
50
|
+
if (cli.matchedCommand) {
|
|
51
|
+
await cli.runMatchedCommand();
|
|
52
|
+
}
|
|
53
|
+
else if (requested === undefined || requested === "help") {
|
|
54
|
+
// Bare `percher` / `percher help` — show the command list.
|
|
55
|
+
cli.outputHelp();
|
|
56
|
+
}
|
|
57
|
+
else if (requested.startsWith("-")) {
|
|
58
|
+
// A bare flag (`--help` / `--version`) — cli.parse() already
|
|
59
|
+
// printed the relevant output and unset matchedCommand.
|
|
60
|
+
}
|
|
61
|
+
else if (cli.commands.some((cmd) => cmd.isMatched(requested))) {
|
|
62
|
+
// A real command whose action was pre-empted by `--help` /
|
|
63
|
+
// `--version`: cli.parse() prints that command's help and unsets
|
|
64
|
+
// matchedCommand. The output already happened — nothing to do.
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Genuinely unknown command, with or without a trailing --help.
|
|
68
|
+
// Usage hint on stderr, exit 1 — a script must see the failure.
|
|
69
|
+
await writeUnknownCommand(ctx, requested);
|
|
70
|
+
throw new SilentExitError(1);
|
|
71
|
+
}
|
|
34
72
|
trackEvent(command, "success");
|
|
35
73
|
await checkForUpdates();
|
|
36
74
|
return 0;
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Multi-stage build: a Bun stage compiles the static bundle, then a
|
|
2
|
+
# minimal Caddy image serves it. This project uses runtime = "docker"
|
|
3
|
+
# (not "static") because Percher's static runtime serves pre-built
|
|
4
|
+
# files only — it does not run a build command. Generated by Percher;
|
|
5
|
+
# safe to edit and own.
|
|
6
|
+
|
|
7
|
+
# --- Build stage: install deps and build the static bundle -----------
|
|
8
|
+
FROM oven/bun:1-alpine AS build
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
COPY . .
|
|
11
|
+
RUN bun install && bun run build
|
|
12
|
+
|
|
13
|
+
# --- Serve stage: minimal Caddy image --------------------------------
|
|
14
|
+
# caddy:2-alpine ships /usr/bin/caddy with cap_net_bind_service file
|
|
15
|
+
# caps. Percher runs every container with --security-opt
|
|
16
|
+
# no-new-privileges, which makes execve refuse a cap-bearing binary
|
|
17
|
+
# (an "operation not permitted" crash-loop). Copy Caddy onto a plain
|
|
18
|
+
# alpine base and strip the caps with setcap -r — Caddy never needs
|
|
19
|
+
# the cap here, it listens on :8080 (>= 1024).
|
|
20
|
+
FROM caddy:2-alpine AS caddy-source
|
|
21
|
+
FROM alpine:3.21
|
|
22
|
+
COPY --from=caddy-source /usr/bin/caddy /usr/bin/caddy
|
|
23
|
+
RUN apk add --no-cache libcap \
|
|
24
|
+
&& setcap -r /usr/bin/caddy \
|
|
25
|
+
&& apk del libcap \
|
|
26
|
+
&& rm -rf /var/cache/apk/*
|
|
27
|
+
COPY Caddyfile /etc/caddy/Caddyfile
|
|
28
|
+
COPY --from=build /app/dist /srv
|
|
29
|
+
# HOME points at the writable tmpfs so non-root Caddy can persist its
|
|
30
|
+
# XDG state (storage-clean lock, config autosave) under Percher's
|
|
31
|
+
# read-only root filesystem.
|
|
32
|
+
ENV HOME=/tmp
|
|
33
|
+
EXPOSE 8080
|
|
34
|
+
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
const _title = "__APP_NAME__";
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<html lang="en">
|
|
6
|
+
<head>
|
|
7
|
+
<meta charset="utf-8" />
|
|
8
|
+
<meta name="viewport" content="width=device-width" />
|
|
9
|
+
<title>{title}</title>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<h1>Hello from {title}!</h1>
|
|
13
|
+
<p>Built with Astro on Percher.</p>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const port = Number(process.env.PORT ?? 3000);
|
|
2
|
+
|
|
3
|
+
Bun.serve({
|
|
4
|
+
port,
|
|
5
|
+
hostname: "0.0.0.0",
|
|
6
|
+
fetch(req) {
|
|
7
|
+
const url = new URL(req.url);
|
|
8
|
+
|
|
9
|
+
if (url.pathname === "/health") {
|
|
10
|
+
return Response.json({ status: "ok" });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (url.pathname === "/") {
|
|
14
|
+
return new Response("Hello from __APP_NAME__!", {
|
|
15
|
+
headers: { "Content-Type": "text/plain" },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return new Response("Not found", { status: 404 });
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
console.log(`[__APP_NAME__] listening on http://0.0.0.0:${port}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
web: gunicorn app.wsgi:application --bind 0.0.0.0:8000
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Minimal Django settings for Percher deploys."""
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
6
|
+
|
|
7
|
+
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "change-me-set-via-percher-env")
|
|
8
|
+
DEBUG = os.environ.get("DJANGO_DEBUG", "").lower() == "true"
|
|
9
|
+
ALLOWED_HOSTS = ["*"]
|
|
10
|
+
|
|
11
|
+
INSTALLED_APPS = [
|
|
12
|
+
"django.contrib.contenttypes",
|
|
13
|
+
"django.contrib.auth",
|
|
14
|
+
"django.contrib.staticfiles",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
MIDDLEWARE = [
|
|
18
|
+
"django.middleware.common.CommonMiddleware",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
ROOT_URLCONF = "app.urls"
|
|
22
|
+
|
|
23
|
+
TEMPLATES = [
|
|
24
|
+
{
|
|
25
|
+
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
26
|
+
"DIRS": [],
|
|
27
|
+
"APP_DIRS": True,
|
|
28
|
+
"OPTIONS": {},
|
|
29
|
+
},
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
WSGI_APPLICATION = "app.wsgi.application"
|
|
33
|
+
|
|
34
|
+
DATABASES = {
|
|
35
|
+
"default": {
|
|
36
|
+
"ENGINE": "django.db.backends.sqlite3",
|
|
37
|
+
"NAME": BASE_DIR / "/app/data/db.sqlite3",
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
STATIC_URL = "static/"
|
|
42
|
+
STATIC_ROOT = BASE_DIR / "staticfiles"
|
|
43
|
+
|
|
44
|
+
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from django.http import JsonResponse, HttpResponse
|
|
2
|
+
from django.urls import path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def index(_request):
|
|
6
|
+
return HttpResponse(
|
|
7
|
+
"<main style='font-family: system-ui; padding: 40px;'>"
|
|
8
|
+
"<h1>Hello from __APP_NAME__</h1>"
|
|
9
|
+
"<p>Django on Percher.</p>"
|
|
10
|
+
"</main>"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def health(_request):
|
|
15
|
+
return JsonResponse({"status": "ok"})
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
urlpatterns = [
|
|
19
|
+
path("", index),
|
|
20
|
+
path("health/", health),
|
|
21
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Django's command-line utility for administrative tasks."""
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
|
|
9
|
+
try:
|
|
10
|
+
from django.core.management import execute_from_command_line
|
|
11
|
+
except ImportError as exc:
|
|
12
|
+
raise ImportError(
|
|
13
|
+
"Couldn't import Django. Is it installed and on PYTHONPATH?"
|
|
14
|
+
) from exc
|
|
15
|
+
execute_from_command_line(sys.argv)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
main()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
|
|
2
|
+
WORKDIR /src
|
|
3
|
+
COPY app.csproj ./
|
|
4
|
+
RUN dotnet restore
|
|
5
|
+
COPY Program.cs ./
|
|
6
|
+
RUN dotnet publish -c Release -o /out --no-restore
|
|
7
|
+
|
|
8
|
+
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
# aspnet:8.0-alpine ships with a non-root `app` user (uid 1654)
|
|
11
|
+
# pre-baked. We explicitly chown + USER here so the image runs as
|
|
12
|
+
# non-root regardless of any future base-image USER-default change.
|
|
13
|
+
COPY --from=build --chown=app:app /out ./
|
|
14
|
+
USER app
|
|
15
|
+
ENV ASPNETCORE_URLS=http://+:8080
|
|
16
|
+
ENV ASPNETCORE_ENVIRONMENT=Production
|
|
17
|
+
EXPOSE 8080
|
|
18
|
+
ENTRYPOINT ["dotnet", "app.dll"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
var builder = WebApplication.CreateBuilder(args);
|
|
2
|
+
var app = builder.Build();
|
|
3
|
+
|
|
4
|
+
app.MapGet("/health", () => Results.Json(new { ok = true }));
|
|
5
|
+
|
|
6
|
+
app.MapGet("/", () => Results.Content(@"<!doctype html>
|
|
7
|
+
<html lang=""en"">
|
|
8
|
+
<head>
|
|
9
|
+
<meta charset=""utf-8"" />
|
|
10
|
+
<title>__APP_NAME__</title>
|
|
11
|
+
<style>
|
|
12
|
+
body { font-family: system-ui, sans-serif; max-width: 640px; margin: 48px auto; padding: 0 24px; line-height: 1.6; }
|
|
13
|
+
h1 { letter-spacing: -0.5px; }
|
|
14
|
+
</style>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<h1>__APP_NAME__</h1>
|
|
18
|
+
<p>A .NET app deployed on Percher.</p>
|
|
19
|
+
<p>Add endpoints in <code>Program.cs</code> and push to redeploy.</p>
|
|
20
|
+
</body>
|
|
21
|
+
</html>", "text/html; charset=utf-8"));
|
|
22
|
+
|
|
23
|
+
app.Run();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
2
|
+
<PropertyGroup>
|
|
3
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
4
|
+
<Nullable>enable</Nullable>
|
|
5
|
+
<ImplicitUsings>enable</ImplicitUsings>
|
|
6
|
+
<AssemblyName>app</AssemblyName>
|
|
7
|
+
<RootNamespace>PercherApp</RootNamespace>
|
|
8
|
+
</PropertyGroup>
|
|
9
|
+
</Project>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Multi-stage build: a Bun stage compiles the static bundle, then a
|
|
2
|
+
# minimal Caddy image serves it. This project uses runtime = "docker"
|
|
3
|
+
# (not "static") because Percher's static runtime serves pre-built
|
|
4
|
+
# files only — it does not run a build command. Generated by Percher;
|
|
5
|
+
# safe to edit and own.
|
|
6
|
+
|
|
7
|
+
# --- Build stage: install deps and build the static bundle -----------
|
|
8
|
+
FROM oven/bun:1-alpine AS build
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
COPY . .
|
|
11
|
+
RUN bun install && bun run build
|
|
12
|
+
|
|
13
|
+
# --- Serve stage: minimal Caddy image --------------------------------
|
|
14
|
+
# caddy:2-alpine ships /usr/bin/caddy with cap_net_bind_service file
|
|
15
|
+
# caps. Percher runs every container with --security-opt
|
|
16
|
+
# no-new-privileges, which makes execve refuse a cap-bearing binary
|
|
17
|
+
# (an "operation not permitted" crash-loop). Copy Caddy onto a plain
|
|
18
|
+
# alpine base and strip the caps with setcap -r — Caddy never needs
|
|
19
|
+
# the cap here, it listens on :8080 (>= 1024).
|
|
20
|
+
FROM caddy:2-alpine AS caddy-source
|
|
21
|
+
FROM alpine:3.21
|
|
22
|
+
COPY --from=caddy-source /usr/bin/caddy /usr/bin/caddy
|
|
23
|
+
RUN apk add --no-cache libcap \
|
|
24
|
+
&& setcap -r /usr/bin/caddy \
|
|
25
|
+
&& apk del libcap \
|
|
26
|
+
&& rm -rf /var/cache/apk/*
|
|
27
|
+
COPY Caddyfile /etc/caddy/Caddyfile
|
|
28
|
+
COPY --from=build /app/_site /srv
|
|
29
|
+
# HOME points at the writable tmpfs so non-root Caddy can persist its
|
|
30
|
+
# XDG state (storage-clean lock, config autosave) under Percher's
|
|
31
|
+
# read-only root filesystem.
|
|
32
|
+
ENV HOME=/tmp
|
|
33
|
+
EXPOSE 8080
|
|
34
|
+
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
|
|
@@ -0,0 +1,12 @@
|
|
|
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" />
|
|
6
|
+
<title>{{ title or "__APP_NAME__" }}</title>
|
|
7
|
+
<link rel="stylesheet" href="/assets/style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main>{{ content | safe }}</main>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|