percher 0.5.9 → 0.5.10

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.
@@ -90873,10 +90873,6 @@ var CAC = class extends EventTarget {
90873
90873
  };
90874
90874
  var cac = (name = "") => new CAC(name);
90875
90875
 
90876
- // ../core/dist/app-name.js
90877
- import { existsSync, readFileSync } from "node:fs";
90878
- import { join } from "node:path";
90879
-
90880
90876
  // ../toml/dist/errors.js
90881
90877
  class PercherTomlError extends Error {
90882
90878
  code;
@@ -104801,6 +104797,8 @@ function serialize(config2) {
104801
104797
  return import_toml2.default.stringify(config2);
104802
104798
  }
104803
104799
  // ../core/dist/app-name.js
104800
+ import { existsSync, readFileSync } from "node:fs";
104801
+ import { join } from "node:path";
104804
104802
  function readPercherTomlAppName(cwd) {
104805
104803
  const p = join(cwd, "percher.toml");
104806
104804
  if (!existsSync(p))
@@ -105526,7 +105524,7 @@ async function appstoreKit(ctx, input = {}) {
105526
105524
  const themeHex = themeColorForToken(null);
105527
105525
  const base = resolve3(ctx.cwd, input.outDir ?? ".");
105528
105526
  const baseRel = relative(ctx.cwd, base);
105529
- if (baseRel.startsWith("..") || isAbsolute(baseRel)) {
105527
+ if (baseRel === ".." || baseRel.startsWith(`..${sep}`) || isAbsolute(baseRel)) {
105530
105528
  throw new PercherCoreError("Output directory must stay inside the project", {
105531
105529
  code: "appstore-kit.bad-out-dir",
105532
105530
  hint: "Pass a path inside the current project (e.g. --out public), not an absolute or parent path."
@@ -109566,7 +109564,8 @@ FROM caddy:2-alpine AS caddy-source
109566
109564
  FROM alpine:3.21
109567
109565
  COPY --from=caddy-source /usr/bin/caddy /usr/bin/caddy
109568
109566
  RUN apk add --no-cache libcap \\
109569
- && setcap -r /usr/bin/caddy \\
109567
+ && (setcap -r /usr/bin/caddy 2>/dev/null || true) \\
109568
+ && test -z "$(getcap /usr/bin/caddy)" \\
109570
109569
  && apk del libcap \\
109571
109570
  && rm -rf /var/cache/apk/*
109572
109571
  COPY Caddyfile /etc/caddy/Caddyfile
@@ -114449,6 +114448,17 @@ async function runDeployWithRetry(opts) {
114449
114448
  throw lastErr;
114450
114449
  }
114451
114450
 
114451
+ // ../core/dist/toml-recovery.js
114452
+ function tomlErrorToRecoveryProblems(err) {
114453
+ if (err.issues?.length) {
114454
+ return err.issues.map((i) => ({
114455
+ file: "percher.toml",
114456
+ message: `${i.path.join(".") || "(root)"}: ${i.message}`
114457
+ }));
114458
+ }
114459
+ return [{ file: "percher.toml", message: err.message }];
114460
+ }
114461
+
114452
114462
  // ../core/dist/commands/env-scan.js
114453
114463
  import { readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync3 } from "node:fs";
114454
114464
  import { join as join22 } from "node:path";
@@ -114964,9 +114974,9 @@ function buildLiveSummary(opts) {
114964
114974
  const replaced = opts.replacedPreview ? " — replaced previous preview" : "";
114965
114975
  return `${opts.appName} ${verb}${fwSuffix} → ${opts.url}${replaced}`;
114966
114976
  }
114967
- async function publish(ctx, input = {}) {
114977
+ async function publish(ctx, input = {}, preParsedConfig) {
114968
114978
  try {
114969
- return await publishInner(ctx, input);
114979
+ return await publishInner(ctx, input, preParsedConfig);
114970
114980
  } catch (err) {
114971
114981
  const classified = classifyPublishApiError(err, {
114972
114982
  configPath: tomlPathFor2(ctx.cwd),
@@ -114995,7 +115005,7 @@ async function publish(ctx, input = {}) {
114995
115005
  };
114996
115006
  }
114997
115007
  }
114998
- async function publishInner(ctx, input) {
115008
+ async function publishInner(ctx, input, preParsedConfig) {
114999
115009
  const t0 = Date.now();
115000
115010
  if (!input.dryRun) {
115001
115011
  try {
@@ -115006,7 +115016,7 @@ async function publishInner(ctx, input) {
115006
115016
  ctx.status("[1/4] Reading config...");
115007
115017
  let config2;
115008
115018
  try {
115009
- config2 = await loadOrGenerate(ctx);
115019
+ config2 = await loadOrGenerate(ctx, preParsedConfig);
115010
115020
  } catch (err) {
115011
115021
  if (err instanceof PercherTomlError) {
115012
115022
  if (err.code === "PARSE_ERROR") {
@@ -115020,7 +115030,7 @@ async function publishInner(ctx, input) {
115020
115030
  suggestion: "Fix the syntax error and retry publish."
115021
115031
  },
115022
115032
  recovery: recoveryFixConfig({
115023
- problems: [{ file: "percher.toml", message: err.message }],
115033
+ problems: tomlErrorToRecoveryProblems(err),
115024
115034
  reasonCode: "config_invalid"
115025
115035
  }),
115026
115036
  summary: "percher.toml has invalid TOML syntax — fix and retry.",
@@ -115029,10 +115039,7 @@ async function publishInner(ctx, input) {
115029
115039
  };
115030
115040
  }
115031
115041
  if (err.code === "VALIDATION_ERROR") {
115032
- const problems = (err.issues ?? []).map((i) => ({
115033
- file: "percher.toml",
115034
- message: `${i.path.join(".") || "(root)"}: ${i.message}`
115035
- }));
115042
+ const problems = tomlErrorToRecoveryProblems(err);
115036
115043
  const bullets = problems.map((p) => ` - ${p.message}`).join(`
115037
115044
  `);
115038
115045
  const count = problems.length;
@@ -115076,6 +115083,30 @@ ${bullets}`,
115076
115083
  bundle: { fileCount: 0, bytes: 0 }
115077
115084
  };
115078
115085
  }
115086
+ if (preParsedConfig && !input.dryRun && !existsSync15(join24(ctx.cwd, "percher.toml"))) {
115087
+ return {
115088
+ status: "failed",
115089
+ fileCount: 0,
115090
+ bytes: 0,
115091
+ error: {
115092
+ title: "percher.toml missing from the project",
115093
+ explanation: "A pre-parsed config was supplied, but no percher.toml exists in the project directory. The file is packaged into the upload, so it must be present on disk for a real deploy.",
115094
+ suggestion: "Run `percher init` (or write percher.toml) before publishing."
115095
+ },
115096
+ recovery: recoveryFixConfig({
115097
+ problems: [
115098
+ {
115099
+ file: "percher.toml",
115100
+ message: "percher.toml must exist on disk for a real upload (it ships in the tarball)."
115101
+ }
115102
+ ],
115103
+ reasonCode: "config_missing"
115104
+ }),
115105
+ summary: "percher.toml missing — required on disk for a real upload.",
115106
+ configPath: tomlPathFor2(ctx.cwd),
115107
+ bundle: { fileCount: 0, bytes: 0 }
115108
+ };
115109
+ }
115079
115110
  ctx.status("[2/4] Packaging files...");
115080
115111
  const tarball = await createTarball({ cwd: ctx.cwd, config: config2 });
115081
115112
  const packageMs = Date.now() - t0;
@@ -115581,11 +115612,14 @@ async function handleAnonymousPublish(ctx, config2, input, tarball) {
115581
115612
  ]
115582
115613
  };
115583
115614
  }
115584
- async function loadOrGenerate(ctx) {
115615
+ async function loadOrGenerate(ctx, preParsed) {
115616
+ if (preParsed)
115617
+ return structuredClone(preParsed);
115585
115618
  const tomlPath = join24(ctx.cwd, "percher.toml");
115586
115619
  if (!existsSync15(tomlPath)) {
115587
115620
  ctx.status("No percher.toml found — detecting framework...");
115588
- await init(ctx);
115621
+ const generated = await init(ctx);
115622
+ return validate(generated.config);
115589
115623
  }
115590
115624
  return parseFile(tomlPath);
115591
115625
  }
@@ -119014,7 +119048,7 @@ async function renderError(stderr, error48) {
119014
119048
  // package.json
119015
119049
  var package_default = {
119016
119050
  name: "percher",
119017
- version: "0.5.9",
119051
+ version: "0.5.10",
119018
119052
  description: "CLI for the Percher hosting platform — publish apps with one command",
119019
119053
  type: "module",
119020
119054
  bin: {
package/dist/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "percher",
3
- "version": "0.5.9",
4
- "description": "CLI for the Percher hosting platform — publish apps with one command",
3
+ "version": "0.5.10",
4
+ "description": "CLI for the Percher hosting platform \u2014 publish apps with one command",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "percher": "./dist/bin/percher.js"
@@ -1,34 +1,35 @@
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"]
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 2>/dev/null || true) \
25
+ && test -z "$(getcap /usr/bin/caddy)" \
26
+ && apk del libcap \
27
+ && rm -rf /var/cache/apk/*
28
+ COPY Caddyfile /etc/caddy/Caddyfile
29
+ COPY --from=build /app/dist /srv
30
+ # HOME points at the writable tmpfs so non-root Caddy can persist its
31
+ # XDG state (storage-clean lock, config autosave) under Percher's
32
+ # read-only root filesystem.
33
+ ENV HOME=/tmp
34
+ EXPOSE 8080
35
+ CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
@@ -1,5 +1,5 @@
1
1
  ---
2
- const _title = "__APP_NAME__";
2
+ const title = "__APP_NAME__";
3
3
  ---
4
4
 
5
5
  <html lang="en">
@@ -1,34 +1,35 @@
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"]
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 2>/dev/null || true) \
25
+ && test -z "$(getcap /usr/bin/caddy)" \
26
+ && apk del libcap \
27
+ && rm -rf /var/cache/apk/*
28
+ COPY Caddyfile /etc/caddy/Caddyfile
29
+ COPY --from=build /app/_site /srv
30
+ # HOME points at the writable tmpfs so non-root Caddy can persist its
31
+ # XDG state (storage-clean lock, config autosave) under Percher's
32
+ # read-only root filesystem.
33
+ ENV HOME=/tmp
34
+ EXPOSE 8080
35
+ CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
@@ -1,32 +1,33 @@
1
- # Multi-stage build: Hugo compiles the site, then a minimal Caddy
2
- # image serves it. runtime = "docker" because Percher's static runtime
3
- # serves pre-built files only — it does not run Hugo. Edit and own freely.
4
-
5
- # --- Build stage: compile the Hugo site ------------------------------
6
- FROM hugomods/hugo:exts-0.139.4 AS build
7
- WORKDIR /site
8
- COPY . .
9
- RUN hugo --minify
10
-
11
- # --- Serve stage: minimal Caddy image --------------------------------
12
- # caddy:2-alpine ships /usr/bin/caddy with cap_net_bind_service file
13
- # caps. Percher runs every container with --security-opt
14
- # no-new-privileges, which makes execve refuse a cap-bearing binary
15
- # (an "operation not permitted" crash-loop). Copy Caddy onto a plain
16
- # alpine base and strip the caps with setcap -r — Caddy never needs
17
- # the cap here, it listens on :8080 (>= 1024).
18
- FROM caddy:2-alpine AS caddy-source
19
- FROM alpine:3.21
20
- COPY --from=caddy-source /usr/bin/caddy /usr/bin/caddy
21
- RUN apk add --no-cache libcap \
22
- && setcap -r /usr/bin/caddy \
23
- && apk del libcap \
24
- && rm -rf /var/cache/apk/*
25
- COPY Caddyfile /etc/caddy/Caddyfile
26
- COPY --from=build /site/public /srv
27
- # HOME points at the writable tmpfs so non-root Caddy can persist its
28
- # XDG state (storage-clean lock, config autosave) under Percher's
29
- # read-only root filesystem.
30
- ENV HOME=/tmp
31
- EXPOSE 8080
32
- CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
1
+ # Multi-stage build: Hugo compiles the site, then a minimal Caddy
2
+ # image serves it. runtime = "docker" because Percher's static runtime
3
+ # serves pre-built files only — it does not run Hugo. Edit and own freely.
4
+
5
+ # --- Build stage: compile the Hugo site ------------------------------
6
+ FROM hugomods/hugo:exts-0.139.4 AS build
7
+ WORKDIR /site
8
+ COPY . .
9
+ RUN hugo --minify
10
+
11
+ # --- Serve stage: minimal Caddy image --------------------------------
12
+ # caddy:2-alpine ships /usr/bin/caddy with cap_net_bind_service file
13
+ # caps. Percher runs every container with --security-opt
14
+ # no-new-privileges, which makes execve refuse a cap-bearing binary
15
+ # (an "operation not permitted" crash-loop). Copy Caddy onto a plain
16
+ # alpine base and strip the caps with setcap -r — Caddy never needs
17
+ # the cap here, it listens on :8080 (>= 1024).
18
+ FROM caddy:2-alpine AS caddy-source
19
+ FROM alpine:3.21
20
+ COPY --from=caddy-source /usr/bin/caddy /usr/bin/caddy
21
+ RUN apk add --no-cache libcap \
22
+ && (setcap -r /usr/bin/caddy 2>/dev/null || true) \
23
+ && test -z "$(getcap /usr/bin/caddy)" \
24
+ && apk del libcap \
25
+ && rm -rf /var/cache/apk/*
26
+ COPY Caddyfile /etc/caddy/Caddyfile
27
+ COPY --from=build /site/public /srv
28
+ # HOME points at the writable tmpfs so non-root Caddy can persist its
29
+ # XDG state (storage-clean lock, config autosave) under Percher's
30
+ # read-only root filesystem.
31
+ ENV HOME=/tmp
32
+ EXPOSE 8080
33
+ CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
@@ -5,11 +5,15 @@ RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist
5
5
 
6
6
  FROM php:8.3-cli-alpine
7
7
  WORKDIR /app
8
- RUN addgroup -S app && adduser -S app -G app
8
+ # tini as PID 1 forwards SIGTERM to php; without it `php -S` as PID 1 ignores
9
+ # SIGTERM (Linux PID-1 special case) and is SIGKILLed on every stop/redeploy.
10
+ RUN apk add --no-cache tini \
11
+ && addgroup -S app && adduser -S app -G app
9
12
  COPY --from=vendor --chown=app:app /app/vendor ./vendor
10
13
  COPY --chown=app:app . .
11
14
  RUN composer dump-autoload --no-dev --optimize 2>/dev/null || true \
12
15
  && chown -R app:app /app
13
16
  USER app
14
17
  EXPOSE 8000
18
+ ENTRYPOINT ["/sbin/tini", "--"]
15
19
  CMD ["php", "-S", "0.0.0.0:8000", "-t", "public", "public/index.php"]
@@ -0,0 +1,8 @@
1
+ // Plain ESM (not next.config.ts) on purpose: `next start` loads the config at
2
+ // RUNTIME, and a .ts config would require the `typescript` package in the slim
3
+ // runtime image — but typescript is a build-only devDependency (pruned from the
4
+ // runtime stage). A .mjs config loads with no typescript at run time.
5
+ /** @type {import('next').NextConfig} */
6
+ const nextConfig = {};
7
+
8
+ export default nextConfig;
@@ -11,5 +11,11 @@
11
11
  "next": "^15.0.0",
12
12
  "react": "^19.0.0",
13
13
  "react-dom": "^19.0.0"
14
+ },
15
+ "devDependencies": {
16
+ "typescript": "^5.6.0",
17
+ "@types/node": "^22.0.0",
18
+ "@types/react": "^19.0.0",
19
+ "@types/react-dom": "^19.0.0"
14
20
  }
15
21
  }
@@ -1,34 +1,35 @@
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"]
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 2>/dev/null || true) \
25
+ && test -z "$(getcap /usr/bin/caddy)" \
26
+ && apk del libcap \
27
+ && rm -rf /var/cache/apk/*
28
+ COPY Caddyfile /etc/caddy/Caddyfile
29
+ COPY --from=build /app/dist /srv
30
+ # HOME points at the writable tmpfs so non-root Caddy can persist its
31
+ # XDG state (storage-clean lock, config autosave) under Percher's
32
+ # read-only root filesystem.
33
+ ENV HOME=/tmp
34
+ EXPOSE 8080
35
+ CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
@@ -0,0 +1,8 @@
1
+ # Pin the start command for the default nixpacks build path. Without this,
2
+ # nixpacks' Python provider can default to `python main.py`, which imports the
3
+ # FastAPI app and exits without serving — the container exits immediately and
4
+ # the health check fails. `python -m uvicorn` doesn't depend on `uvicorn` being
5
+ # on PATH. Mirrors `[web].command` in percher.toml (used by the Percher-owned
6
+ # python build bypass).
7
+ [start]
8
+ cmd = "python -m uvicorn main:app --host 0.0.0.0 --port 8000"
@@ -6,6 +6,7 @@ framework = "fastapi"
6
6
  [web]
7
7
  port = 8000
8
8
  health = "/health"
9
+ command = "python -m uvicorn main:app --host 0.0.0.0 --port 8000"
9
10
 
10
11
  [data]
11
12
  mode = "none"
@@ -6,9 +6,13 @@ COPY src ./src
6
6
  RUN cargo build --release --bin app
7
7
 
8
8
  FROM alpine:3.20
9
- RUN apk add --no-cache ca-certificates \
9
+ # tini as PID 1 forwards SIGTERM to the app; without it the bare binary as PID 1
10
+ # ignores SIGTERM (Linux PID-1 special case) and the container is SIGKILLed on
11
+ # every stop/redeploy instead of shutting down cleanly.
12
+ RUN apk add --no-cache ca-certificates tini \
10
13
  && addgroup -S app && adduser -S app -G app
11
14
  COPY --from=build --chown=app:app /src/target/release/app /app
12
15
  USER app
13
16
  EXPOSE 8080
17
+ ENTRYPOINT ["/sbin/tini", "--"]
14
18
  CMD ["/app"]
@@ -1,34 +1,35 @@
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"]
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 2>/dev/null || true) \
25
+ && test -z "$(getcap /usr/bin/caddy)" \
26
+ && apk del libcap \
27
+ && rm -rf /var/cache/apk/*
28
+ COPY Caddyfile /etc/caddy/Caddyfile
29
+ COPY --from=build /app/dist /srv
30
+ # HOME points at the writable tmpfs so non-root Caddy can persist its
31
+ # XDG state (storage-clean lock, config autosave) under Percher's
32
+ # read-only root filesystem.
33
+ ENV HOME=/tmp
34
+ EXPOSE 8080
35
+ CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
@@ -1,34 +1,35 @@
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"]
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 2>/dev/null || true) \
25
+ && test -z "$(getcap /usr/bin/caddy)" \
26
+ && apk del libcap \
27
+ && rm -rf /var/cache/apk/*
28
+ COPY Caddyfile /etc/caddy/Caddyfile
29
+ COPY --from=build /app/dist /srv
30
+ # HOME points at the writable tmpfs so non-root Caddy can persist its
31
+ # XDG state (storage-clean lock, config autosave) under Percher's
32
+ # read-only root filesystem.
33
+ ENV HOME=/tmp
34
+ EXPOSE 8080
35
+ CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
@@ -1,34 +1,35 @@
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"]
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 2>/dev/null || true) \
25
+ && test -z "$(getcap /usr/bin/caddy)" \
26
+ && apk del libcap \
27
+ && rm -rf /var/cache/apk/*
28
+ COPY Caddyfile /etc/caddy/Caddyfile
29
+ COPY --from=build /app/dist /srv
30
+ # HOME points at the writable tmpfs so non-root Caddy can persist its
31
+ # XDG state (storage-clean lock, config autosave) under Percher's
32
+ # read-only root filesystem.
33
+ ENV HOME=/tmp
34
+ EXPOSE 8080
35
+ CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "percher",
3
- "version": "0.5.9",
4
- "description": "CLI for the Percher hosting platform — publish apps with one command",
3
+ "version": "0.5.10",
4
+ "description": "CLI for the Percher hosting platform \u2014 publish apps with one command",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "percher": "./dist/bin/percher.js"
@@ -1,5 +0,0 @@
1
- import type { NextConfig } from "next";
2
-
3
- const nextConfig: NextConfig = {};
4
-
5
- export default nextConfig;
@@ -1,12 +0,0 @@
1
- const server = Bun.serve({
2
- port: Number(process.env.PORT ?? 3000),
3
- fetch(req) {
4
- const url = new URL(req.url);
5
- if (url.pathname === "/health") {
6
- return Response.json({ status: "ok" });
7
- }
8
- return new Response("Hello from __APP_NAME__!");
9
- },
10
- });
11
-
12
- console.log(`Listening on http://localhost:${server.port}`);