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.
Files changed (174) hide show
  1. package/dist/bin/percher.js +111049 -18287
  2. package/dist/package.json +13 -4
  3. package/dist/src/cli-utils.d.ts +9 -0
  4. package/dist/src/cli-utils.d.ts.map +1 -1
  5. package/dist/src/cli-utils.js +18 -0
  6. package/dist/src/cli-utils.js.map +1 -1
  7. package/dist/src/commands/account.d.ts.map +1 -1
  8. package/dist/src/commands/account.js +63 -7
  9. package/dist/src/commands/account.js.map +1 -1
  10. package/dist/src/commands/admin.d.ts +17 -0
  11. package/dist/src/commands/admin.d.ts.map +1 -0
  12. package/dist/src/commands/admin.js +92 -0
  13. package/dist/src/commands/admin.js.map +1 -0
  14. package/dist/src/commands/apps.d.ts.map +1 -1
  15. package/dist/src/commands/apps.js +60 -8
  16. package/dist/src/commands/apps.js.map +1 -1
  17. package/dist/src/commands/deploys.d.ts.map +1 -1
  18. package/dist/src/commands/deploys.js +12 -2
  19. package/dist/src/commands/deploys.js.map +1 -1
  20. package/dist/src/commands/migrate.d.ts +4 -0
  21. package/dist/src/commands/migrate.d.ts.map +1 -0
  22. package/dist/src/commands/migrate.js +142 -0
  23. package/dist/src/commands/migrate.js.map +1 -0
  24. package/dist/src/commands/sharing.d.ts +23 -0
  25. package/dist/src/commands/sharing.d.ts.map +1 -0
  26. package/dist/src/commands/sharing.js +162 -0
  27. package/dist/src/commands/sharing.js.map +1 -0
  28. package/dist/src/commands/transfers.d.ts +20 -0
  29. package/dist/src/commands/transfers.d.ts.map +1 -0
  30. package/dist/src/commands/transfers.js +117 -0
  31. package/dist/src/commands/transfers.js.map +1 -0
  32. package/dist/src/index.d.ts.map +1 -1
  33. package/dist/src/index.js +42 -4
  34. package/dist/src/index.js.map +1 -1
  35. package/dist/templates/astro/Caddyfile +6 -0
  36. package/dist/templates/astro/Dockerfile +34 -0
  37. package/dist/templates/astro/astro.config.mjs +3 -0
  38. package/dist/templates/astro/package.json +14 -0
  39. package/dist/templates/astro/percher.toml +11 -0
  40. package/dist/templates/astro/src/pages/index.astro +15 -0
  41. package/dist/templates/bun/index.ts +23 -0
  42. package/dist/templates/bun/package.json +8 -0
  43. package/dist/templates/bun/percher.toml +10 -0
  44. package/dist/templates/django/Procfile +1 -0
  45. package/dist/templates/django/app/__init__.py +0 -0
  46. package/dist/templates/django/app/settings.py +44 -0
  47. package/dist/templates/django/app/urls.py +21 -0
  48. package/dist/templates/django/app/wsgi.py +5 -0
  49. package/dist/templates/django/manage.py +19 -0
  50. package/dist/templates/django/percher.toml +11 -0
  51. package/dist/templates/django/requirements.txt +2 -0
  52. package/dist/templates/dotnet/Dockerfile +18 -0
  53. package/dist/templates/dotnet/Program.cs +23 -0
  54. package/dist/templates/dotnet/app.csproj +9 -0
  55. package/dist/templates/dotnet/percher.toml +11 -0
  56. package/dist/templates/eleventy/.eleventy.js +12 -0
  57. package/dist/templates/eleventy/Caddyfile +6 -0
  58. package/dist/templates/eleventy/Dockerfile +34 -0
  59. package/dist/templates/eleventy/package.json +12 -0
  60. package/dist/templates/eleventy/percher.toml +11 -0
  61. package/dist/templates/eleventy/src/_includes/base.njk +12 -0
  62. package/dist/templates/eleventy/src/assets/style.css +12 -0
  63. package/dist/templates/eleventy/src/index.md +10 -0
  64. package/dist/templates/elysia/index.ts +8 -0
  65. package/dist/templates/elysia/package.json +12 -0
  66. package/dist/templates/elysia/percher.toml +11 -0
  67. package/dist/templates/express/index.ts +16 -0
  68. package/dist/templates/express/package.json +11 -0
  69. package/dist/templates/express/percher.toml +11 -0
  70. package/dist/templates/fastify/index.ts +13 -0
  71. package/dist/templates/fastify/package.json +11 -0
  72. package/dist/templates/fastify/percher.toml +11 -0
  73. package/dist/templates/flask/main.py +23 -0
  74. package/dist/templates/flask/percher.toml +11 -0
  75. package/dist/templates/flask/requirements.txt +1 -0
  76. package/dist/templates/go/Dockerfile +11 -0
  77. package/dist/templates/go/go.mod +3 -0
  78. package/dist/templates/go/main.go +53 -0
  79. package/dist/templates/go/percher.toml +11 -0
  80. package/dist/templates/hono/index.ts +12 -0
  81. package/dist/templates/hono/package.json +11 -0
  82. package/dist/templates/hono/percher.toml +11 -0
  83. package/dist/templates/htmx/index.ts +32 -0
  84. package/dist/templates/htmx/package.json +15 -0
  85. package/dist/templates/htmx/percher.toml +11 -0
  86. package/dist/templates/hugo/Caddyfile +6 -0
  87. package/dist/templates/hugo/Dockerfile +32 -0
  88. package/dist/templates/hugo/content/_index.md +7 -0
  89. package/dist/templates/hugo/hugo.toml +11 -0
  90. package/dist/templates/hugo/layouts/index.html +22 -0
  91. package/dist/templates/hugo/percher.toml +11 -0
  92. package/dist/templates/java/Dockerfile +15 -0
  93. package/dist/templates/java/Main.java +77 -0
  94. package/dist/templates/java/percher.toml +11 -0
  95. package/dist/templates/laravel/Dockerfile +15 -0
  96. package/dist/templates/laravel/composer.json +15 -0
  97. package/dist/templates/laravel/percher.toml +11 -0
  98. package/dist/templates/laravel/public/index.php +37 -0
  99. package/dist/templates/nextjs/app/api/health/route.ts +3 -0
  100. package/dist/templates/nextjs/app/layout.tsx +11 -0
  101. package/dist/templates/nextjs/app/page.tsx +10 -0
  102. package/dist/templates/nextjs/next.config.ts +5 -0
  103. package/dist/templates/nextjs/package.json +15 -0
  104. package/dist/templates/nextjs/percher.toml +15 -0
  105. package/dist/templates/node/index.js +26 -0
  106. package/dist/templates/node/index.ts +12 -0
  107. package/dist/templates/node/package.json +8 -0
  108. package/dist/templates/node/percher.toml +10 -0
  109. package/dist/templates/nuxt/app.vue +6 -0
  110. package/dist/templates/nuxt/nuxt.config.ts +4 -0
  111. package/dist/templates/nuxt/package.json +15 -0
  112. package/dist/templates/nuxt/percher.toml +15 -0
  113. package/dist/templates/nuxt/server/api/health.ts +1 -0
  114. package/dist/templates/phoenix/Dockerfile +15 -0
  115. package/dist/templates/phoenix/lib/application.ex +15 -0
  116. package/dist/templates/phoenix/lib/router.ex +39 -0
  117. package/dist/templates/phoenix/mix.exs +30 -0
  118. package/dist/templates/phoenix/percher.toml +11 -0
  119. package/dist/templates/preact/Caddyfile +7 -0
  120. package/dist/templates/preact/Dockerfile +34 -0
  121. package/dist/templates/preact/index.html +12 -0
  122. package/dist/templates/preact/package.json +19 -0
  123. package/dist/templates/preact/percher.toml +11 -0
  124. package/dist/templates/preact/src/main.tsx +13 -0
  125. package/dist/templates/preact/vite.config.ts +6 -0
  126. package/dist/templates/python/main.py +13 -0
  127. package/dist/templates/python/percher.toml +11 -0
  128. package/dist/templates/python/requirements.txt +2 -0
  129. package/dist/templates/rails/Dockerfile +11 -0
  130. package/dist/templates/rails/Gemfile +12 -0
  131. package/dist/templates/rails/config.ru +32 -0
  132. package/dist/templates/rails/percher.toml +11 -0
  133. package/dist/templates/remix/app/root.tsx +19 -0
  134. package/dist/templates/remix/app/routes/_index.tsx +10 -0
  135. package/dist/templates/remix/app/routes/health.tsx +3 -0
  136. package/dist/templates/remix/package.json +30 -0
  137. package/dist/templates/remix/percher.toml +15 -0
  138. package/dist/templates/remix/vite.config.ts +7 -0
  139. package/dist/templates/rust/Cargo.toml +19 -0
  140. package/dist/templates/rust/Dockerfile +14 -0
  141. package/dist/templates/rust/percher.toml +11 -0
  142. package/dist/templates/rust/src/main.rs +45 -0
  143. package/dist/templates/solid/Caddyfile +7 -0
  144. package/dist/templates/solid/Dockerfile +34 -0
  145. package/dist/templates/solid/index.html +12 -0
  146. package/dist/templates/solid/package.json +19 -0
  147. package/dist/templates/solid/percher.toml +11 -0
  148. package/dist/templates/solid/src/main.tsx +13 -0
  149. package/dist/templates/solid/vite.config.ts +6 -0
  150. package/dist/templates/static/index.html +15 -0
  151. package/dist/templates/static/percher.toml +6 -0
  152. package/dist/templates/static/style.css +32 -0
  153. package/dist/templates/sveltekit/package.json +19 -0
  154. package/dist/templates/sveltekit/percher.toml +15 -0
  155. package/dist/templates/sveltekit/src/app.html +12 -0
  156. package/dist/templates/sveltekit/src/routes/+page.svelte +2 -0
  157. package/dist/templates/sveltekit/src/routes/health/+server.ts +5 -0
  158. package/dist/templates/sveltekit/svelte.config.js +8 -0
  159. package/dist/templates/sveltekit/vite.config.js +6 -0
  160. package/dist/templates/vite-react/Caddyfile +7 -0
  161. package/dist/templates/vite-react/Dockerfile +34 -0
  162. package/dist/templates/vite-react/index.html +12 -0
  163. package/dist/templates/vite-react/package.json +22 -0
  164. package/dist/templates/vite-react/percher.toml +11 -0
  165. package/dist/templates/vite-react/src/main.tsx +20 -0
  166. package/dist/templates/vite-react/vite.config.ts +6 -0
  167. package/dist/templates/vite-vue/Caddyfile +7 -0
  168. package/dist/templates/vite-vue/Dockerfile +34 -0
  169. package/dist/templates/vite-vue/index.html +12 -0
  170. package/dist/templates/vite-vue/package.json +19 -0
  171. package/dist/templates/vite-vue/percher.toml +11 -0
  172. package/dist/templates/vite-vue/src/main.ts +12 -0
  173. package/dist/templates/vite-vue/vite.config.ts +6 -0
  174. package/package.json +13 -4
@@ -0,0 +1,12 @@
1
+ body {
2
+ font-family: system-ui, -apple-system, sans-serif;
3
+ max-width: 640px;
4
+ margin: 48px auto;
5
+ padding: 0 24px;
6
+ line-height: 1.6;
7
+ color: #222;
8
+ }
9
+ h1 {
10
+ font-weight: 600;
11
+ letter-spacing: -0.5px;
12
+ }
@@ -0,0 +1,10 @@
1
+ ---
2
+ layout: base.njk
3
+ title: __APP_NAME__
4
+ ---
5
+
6
+ # __APP_NAME__
7
+
8
+ Welcome. This is an Eleventy static site deployed on Percher.
9
+
10
+ Edit `src/index.md` and push — Percher rebuilds and serves the `_site` output.
@@ -0,0 +1,8 @@
1
+ import { Elysia } from "elysia";
2
+
3
+ const app = new Elysia()
4
+ .get("/health", () => ({ status: "ok" }))
5
+ .get("/", () => "Hello from __APP_NAME__!")
6
+ .listen(Number(process.env.PORT ?? 3000));
7
+
8
+ console.log(`__APP_NAME__ running on :${app.server?.port}`);
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "bun run index.ts"
8
+ },
9
+ "dependencies": {
10
+ "elysia": "^1.1.0"
11
+ }
12
+ }
@@ -0,0 +1,11 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "bun"
4
+ framework = "elysia"
5
+
6
+ [web]
7
+ port = 3000
8
+ health = "/health"
9
+
10
+ [data]
11
+ mode = "none"
@@ -0,0 +1,16 @@
1
+ import express from "express";
2
+
3
+ const app = express();
4
+ const port = Number(process.env.PORT ?? 3000);
5
+
6
+ app.get("/health", (_req, res) => {
7
+ res.json({ status: "ok" });
8
+ });
9
+
10
+ app.get("/", (_req, res) => {
11
+ res.send("Hello from __APP_NAME__!");
12
+ });
13
+
14
+ app.listen(port, () => {
15
+ console.log(`Listening on http://localhost:${port}`);
16
+ });
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "start": "bun run index.ts"
7
+ },
8
+ "dependencies": {
9
+ "express": "^5.0.0"
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "node"
4
+ framework = "express"
5
+
6
+ [web]
7
+ port = 3000
8
+ health = "/health"
9
+
10
+ [data]
11
+ mode = "none"
@@ -0,0 +1,13 @@
1
+ import Fastify from "fastify";
2
+
3
+ const app = Fastify({ logger: true });
4
+
5
+ app.get("/health", async () => ({ status: "ok" }));
6
+
7
+ app.get("/", async () => ({ hello: "__APP_NAME__" }));
8
+
9
+ const port = Number(process.env.PORT ?? 3000);
10
+ app.listen({ port, host: "0.0.0.0" }).catch((err) => {
11
+ app.log.error(err);
12
+ process.exit(1);
13
+ });
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "start": "bun run index.ts"
7
+ },
8
+ "dependencies": {
9
+ "fastify": "^5.0.0"
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "node"
4
+ framework = "fastify"
5
+
6
+ [web]
7
+ port = 3000
8
+ health = "/health"
9
+
10
+ [data]
11
+ mode = "none"
@@ -0,0 +1,23 @@
1
+ import os
2
+ from flask import Flask, jsonify
3
+
4
+ app = Flask(__name__)
5
+
6
+
7
+ @app.route("/health")
8
+ def health():
9
+ return jsonify({"status": "ok"})
10
+
11
+
12
+ @app.route("/")
13
+ def index():
14
+ return f"Hello from __APP_NAME__!"
15
+
16
+
17
+ if __name__ == "__main__":
18
+ port = int(os.environ.get("PORT", 5000))
19
+ # 0.0.0.0 is required so Flask binds inside the Percher container and
20
+ # Caddy can route to it. Public exposure is gated by Caddy + the
21
+ # platform's egress rules, not by the bind address.
22
+ # nosemgrep: python.flask.security.audit.app-run-param-config.avoid_app_run_with_bad_host
23
+ app.run(host="0.0.0.0", port=port)
@@ -0,0 +1,11 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "python"
4
+ framework = "flask"
5
+
6
+ [web]
7
+ port = 5000
8
+ health = "/health"
9
+
10
+ [data]
11
+ mode = "none"
@@ -0,0 +1 @@
1
+ flask>=3.0.0
@@ -0,0 +1,11 @@
1
+ FROM golang:1.23-alpine AS build
2
+ WORKDIR /src
3
+ COPY go.mod ./
4
+ COPY main.go ./
5
+ RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app .
6
+
7
+ FROM gcr.io/distroless/static-debian12:nonroot
8
+ COPY --from=build /app /app
9
+ EXPOSE 8080
10
+ USER nonroot
11
+ ENTRYPOINT ["/app"]
@@ -0,0 +1,3 @@
1
+ module __APP_NAME__
2
+
3
+ go 1.23
@@ -0,0 +1,53 @@
1
+ package main
2
+
3
+ import (
4
+ "fmt"
5
+ "log"
6
+ "net/http"
7
+ "os"
8
+ )
9
+
10
+ func main() {
11
+ port := os.Getenv("PORT")
12
+ if port == "" {
13
+ port = "8080"
14
+ }
15
+
16
+ http.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
17
+ w.Header().Set("Content-Type", "application/json")
18
+ _, _ = fmt.Fprint(w, `{"ok":true}`)
19
+ })
20
+
21
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
22
+ if r.URL.Path != "/" {
23
+ http.NotFound(w, r)
24
+ return
25
+ }
26
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
27
+ _, _ = fmt.Fprint(w, `<!doctype html>
28
+ <html lang="en">
29
+ <head>
30
+ <meta charset="utf-8" />
31
+ <title>__APP_NAME__</title>
32
+ <style>
33
+ body { font-family: system-ui, sans-serif; max-width: 640px; margin: 48px auto; padding: 0 24px; line-height: 1.6; }
34
+ h1 { letter-spacing: -0.5px; }
35
+ </style>
36
+ </head>
37
+ <body>
38
+ <h1>__APP_NAME__</h1>
39
+ <p>A Go app deployed on Percher.</p>
40
+ <p>Add handlers in <code>main.go</code> and push to redeploy.</p>
41
+ </body>
42
+ </html>`)
43
+ })
44
+
45
+ log.Printf("listening on :%s", port)
46
+ // Plain HTTP is correct here: Percher's Caddy ingress terminates
47
+ // TLS in front of the container. The app speaks HTTP on its
48
+ // internal port and Caddy proxies HTTPS traffic to it.
49
+ // nosemgrep: go.lang.security.audit.net.use-tls.use-tls
50
+ if err := http.ListenAndServe(":"+port, nil); err != nil {
51
+ log.Fatal(err)
52
+ }
53
+ }
@@ -0,0 +1,11 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "docker"
4
+ framework = "go"
5
+
6
+ [web]
7
+ port = 8080
8
+ health = "/health"
9
+
10
+ [data]
11
+ mode = "none"
@@ -0,0 +1,12 @@
1
+ import { Hono } from "hono";
2
+
3
+ const app = new Hono();
4
+
5
+ app.get("/health", (c) => c.json({ status: "ok" }));
6
+
7
+ app.get("/", (c) => c.text("Hello from __APP_NAME__!"));
8
+
9
+ export default {
10
+ port: Number(process.env.PORT ?? 3000),
11
+ fetch: app.fetch,
12
+ };
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "start": "bun run index.ts"
7
+ },
8
+ "dependencies": {
9
+ "hono": "^4.0.0"
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "node"
4
+ framework = "hono"
5
+
6
+ [web]
7
+ port = 3000
8
+ health = "/health"
9
+
10
+ [data]
11
+ mode = "none"
@@ -0,0 +1,32 @@
1
+ import express from "express";
2
+
3
+ const app = express();
4
+ const port = Number(process.env.PORT ?? 3000);
5
+
6
+ app.get("/health", (_req, res) => res.json({ status: "ok" }));
7
+
8
+ app.get("/", (_req, res) => {
9
+ res.type("html").send(`<!doctype html>
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="UTF-8" />
13
+ <title>__APP_NAME__</title>
14
+ <script src="https://unpkg.com/htmx.org@2.0.2"></script>
15
+ </head>
16
+ <body style="font-family: system-ui; padding: 40px;">
17
+ <h1>Hello from __APP_NAME__</h1>
18
+ <p>Express + HTMX on Percher. No build step, just server-rendered HTML fragments.</p>
19
+ <button hx-get="/click" hx-swap="outerHTML" style="font-size: 16px; padding: 10px 20px;">
20
+ Click me
21
+ </button>
22
+ </body>
23
+ </html>`);
24
+ });
25
+
26
+ app.get("/click", (_req, res) => {
27
+ res
28
+ .type("html")
29
+ .send(`<p><strong>Hello back.</strong> Swapped at ${new Date().toISOString()}.</p>`);
30
+ });
31
+
32
+ app.listen(port, () => console.log(`__APP_NAME__ listening on :${port}`));
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "bun run index.ts"
8
+ },
9
+ "dependencies": {
10
+ "express": "^4.19.0"
11
+ },
12
+ "devDependencies": {
13
+ "@types/express": "^4.17.0"
14
+ }
15
+ }
@@ -0,0 +1,11 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "node"
4
+ framework = "express"
5
+
6
+ [web]
7
+ port = 3000
8
+ health = "/health"
9
+
10
+ [data]
11
+ mode = "none"
@@ -0,0 +1,6 @@
1
+ :8080 {
2
+ bind 0.0.0.0
3
+ root * /srv
4
+ encode gzip zstd
5
+ file_server
6
+ }
@@ -0,0 +1,32 @@
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"]
@@ -0,0 +1,7 @@
1
+ ---
2
+ title: "__APP_NAME__"
3
+ ---
4
+
5
+ # __APP_NAME__
6
+
7
+ A Hugo site deployed on Percher. Edit `content/_index.md` and push.
@@ -0,0 +1,11 @@
1
+ baseURL = "/"
2
+ title = "__APP_NAME__"
3
+ languageCode = "en-us"
4
+
5
+ [params]
6
+ description = "__APP_NAME__ on Percher"
7
+
8
+ [markup]
9
+ [markup.goldmark]
10
+ [markup.goldmark.renderer]
11
+ unsafe = true
@@ -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" />
6
+ <title>{{ .Site.Title }}</title>
7
+ <style>
8
+ body {
9
+ font-family: system-ui, -apple-system, sans-serif;
10
+ max-width: 640px;
11
+ margin: 48px auto;
12
+ padding: 0 24px;
13
+ line-height: 1.6;
14
+ color: #222;
15
+ }
16
+ h1 { font-weight: 600; letter-spacing: -0.5px; }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <main>{{ .Content }}</main>
21
+ </body>
22
+ </html>
@@ -0,0 +1,11 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "docker"
4
+ framework = "hugo"
5
+
6
+ [web]
7
+ port = 8080
8
+ health = "/"
9
+
10
+ [data]
11
+ mode = "none"
@@ -0,0 +1,15 @@
1
+ FROM eclipse-temurin:21-jdk-alpine AS build
2
+ WORKDIR /src
3
+ COPY Main.java ./
4
+ # -d emits class files under /build so the runtime stage can copy the
5
+ # whole compiled tree in one COPY (picks up Main.class plus the
6
+ # Main$HealthHandler / Main$RootHandler inner-class files).
7
+ RUN mkdir -p /build && javac -d /build Main.java
8
+
9
+ FROM eclipse-temurin:21-jre-alpine
10
+ RUN addgroup -S app && adduser -S app -G app
11
+ WORKDIR /app
12
+ COPY --from=build --chown=app:app /build/ ./
13
+ USER app
14
+ EXPOSE 8080
15
+ ENTRYPOINT ["java", "Main"]
@@ -0,0 +1,77 @@
1
+ import com.sun.net.httpserver.HttpExchange;
2
+ import com.sun.net.httpserver.HttpHandler;
3
+ import com.sun.net.httpserver.HttpServer;
4
+
5
+ import java.io.IOException;
6
+ import java.io.OutputStream;
7
+ import java.net.InetSocketAddress;
8
+ import java.nio.charset.StandardCharsets;
9
+
10
+ public class Main {
11
+ public static void main(String[] args) throws IOException {
12
+ String portEnv = System.getenv("PORT");
13
+ int port = 8080;
14
+ if (portEnv != null && !portEnv.isEmpty()) {
15
+ try {
16
+ port = Integer.parseInt(portEnv);
17
+ } catch (NumberFormatException ignored) {
18
+ // fall back to 8080
19
+ }
20
+ }
21
+
22
+ HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
23
+ server.createContext("/health", new HealthHandler());
24
+ server.createContext("/", new RootHandler());
25
+ server.setExecutor(null);
26
+ System.out.println("listening on :" + port);
27
+ server.start();
28
+ }
29
+
30
+ static class HealthHandler implements HttpHandler {
31
+ @Override
32
+ public void handle(HttpExchange exchange) throws IOException {
33
+ byte[] body = "{\"ok\":true}".getBytes(StandardCharsets.UTF_8);
34
+ exchange.getResponseHeaders().set("Content-Type", "application/json");
35
+ exchange.sendResponseHeaders(200, body.length);
36
+ try (OutputStream os = exchange.getResponseBody()) {
37
+ os.write(body);
38
+ }
39
+ }
40
+ }
41
+
42
+ static class RootHandler implements HttpHandler {
43
+ @Override
44
+ public void handle(HttpExchange exchange) throws IOException {
45
+ if (!"/".equals(exchange.getRequestURI().getPath())) {
46
+ byte[] notFound = "Not Found".getBytes(StandardCharsets.UTF_8);
47
+ exchange.sendResponseHeaders(404, notFound.length);
48
+ try (OutputStream os = exchange.getResponseBody()) {
49
+ os.write(notFound);
50
+ }
51
+ return;
52
+ }
53
+ String html = "<!doctype html>\n"
54
+ + "<html lang=\"en\">\n"
55
+ + " <head>\n"
56
+ + " <meta charset=\"utf-8\" />\n"
57
+ + " <title>__APP_NAME__</title>\n"
58
+ + " <style>\n"
59
+ + " body { font-family: system-ui, sans-serif; max-width: 640px; margin: 48px auto; padding: 0 24px; line-height: 1.6; }\n"
60
+ + " h1 { letter-spacing: -0.5px; }\n"
61
+ + " </style>\n"
62
+ + " </head>\n"
63
+ + " <body>\n"
64
+ + " <h1>__APP_NAME__</h1>\n"
65
+ + " <p>A Java app deployed on Percher.</p>\n"
66
+ + " <p>Add handlers in <code>Main.java</code> and push to redeploy.</p>\n"
67
+ + " </body>\n"
68
+ + "</html>";
69
+ byte[] body = html.getBytes(StandardCharsets.UTF_8);
70
+ exchange.getResponseHeaders().set("Content-Type", "text/html; charset=utf-8");
71
+ exchange.sendResponseHeaders(200, body.length);
72
+ try (OutputStream os = exchange.getResponseBody()) {
73
+ os.write(body);
74
+ }
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,11 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "docker"
4
+ framework = "java"
5
+
6
+ [web]
7
+ port = 8080
8
+ health = "/health"
9
+
10
+ [data]
11
+ mode = "none"
@@ -0,0 +1,15 @@
1
+ FROM composer:2 AS vendor
2
+ WORKDIR /app
3
+ COPY composer.json ./
4
+ RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist
5
+
6
+ FROM php:8.3-cli-alpine
7
+ WORKDIR /app
8
+ RUN addgroup -S app && adduser -S app -G app
9
+ COPY --from=vendor --chown=app:app /app/vendor ./vendor
10
+ COPY --chown=app:app . .
11
+ RUN composer dump-autoload --no-dev --optimize 2>/dev/null || true \
12
+ && chown -R app:app /app
13
+ USER app
14
+ EXPOSE 8000
15
+ CMD ["php", "-S", "0.0.0.0:8000", "-t", "public", "public/index.php"]
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "percher/__APP_NAME__",
3
+ "description": "Minimal PHP starter, Laravel-ready",
4
+ "type": "project",
5
+ "require": {
6
+ "php": "^8.2"
7
+ },
8
+ "autoload": {
9
+ "psr-4": {
10
+ "App\\": "app/"
11
+ }
12
+ },
13
+ "minimum-stability": "stable",
14
+ "prefer-stable": true
15
+ }
@@ -0,0 +1,11 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "docker"
4
+ framework = "laravel"
5
+
6
+ [web]
7
+ port = 8000
8
+ health = "/health"
9
+
10
+ [data]
11
+ mode = "none"
@@ -0,0 +1,37 @@
1
+ <?php
2
+ // Minimal router — swap in Laravel's bootstrap/app.php once you run
3
+ // `composer create-project laravel/laravel .` to upgrade to the full framework.
4
+
5
+ $uri = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
6
+
7
+ if ($uri === "/health") {
8
+ header("Content-Type: application/json");
9
+ echo json_encode(["ok" => true]);
10
+ exit;
11
+ }
12
+
13
+ if ($uri === "/") {
14
+ header("Content-Type: text/html; charset=utf-8");
15
+ echo <<<HTML
16
+ <!doctype html>
17
+ <html lang="en">
18
+ <head>
19
+ <meta charset="utf-8" />
20
+ <title>__APP_NAME__</title>
21
+ <style>
22
+ body { font-family: system-ui, sans-serif; max-width: 640px; margin: 48px auto; padding: 0 24px; line-height: 1.6; }
23
+ h1 { letter-spacing: -0.5px; }
24
+ </style>
25
+ </head>
26
+ <body>
27
+ <h1>__APP_NAME__</h1>
28
+ <p>A PHP app deployed on Percher.</p>
29
+ <p>To upgrade to full Laravel: <code>composer create-project laravel/laravel .</code></p>
30
+ </body>
31
+ </html>
32
+ HTML;
33
+ exit;
34
+ }
35
+
36
+ http_response_code(404);
37
+ echo "Not found";
@@ -0,0 +1,3 @@
1
+ export async function GET() {
2
+ return Response.json({ status: "ok" });
3
+ }