aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuqian Yang <crupest@crupest.life>2026-03-17 22:20:41 +0800
committerYuqian Yang <crupest@crupest.life>2026-03-17 22:20:41 +0800
commit7d6bd360eef8d630ff419297cc5043a11f8b1074 (patch)
treea7608f9d5a458b93bcec844d2643a440b673daf0
parent8dabb7870ebc4bec146ebf7ec2f948cd2e1b228a (diff)
downloadcrupest-deno.tar.gz
crupest-deno.tar.bz2
crupest-deno.zip
HALF WORKdeno
-rw-r--r--deno/base/cron.ts2
-rw-r--r--deno/deno.json3
-rw-r--r--deno/deno.lock17
-rw-r--r--deno/gateway/deno.json13
-rw-r--r--deno/gateway/main.ts220
-rw-r--r--services/docker/gateway/Dockerfile16
-rw-r--r--services/docker/gateway/mail-robots.txt (renamed from services/docker/nginx/mail-robots.txt)0
-rw-r--r--services/docker/nginx/Dockerfile10
-rwxr-xr-xservices/docker/nginx/certbot.bash12
-rwxr-xr-xservices/docker/nginx/nginx-wrapper.bash12
-rw-r--r--services/templates/docker-compose.yaml.template7
-rw-r--r--services/templates/envs/gateway.env.template2
-rw-r--r--services/templates/nginx/common/acme-challenge3
-rw-r--r--services/templates/nginx/common/http-listen2
-rw-r--r--services/templates/nginx/common/https-listen3
-rw-r--r--services/templates/nginx/common/https-redirect3
-rw-r--r--services/templates/nginx/common/reverse-proxy7
-rw-r--r--services/templates/nginx/default.conf9
-rw-r--r--services/templates/nginx/mail.conf.template29
-rw-r--r--services/templates/nginx/root.conf.template40
-rw-r--r--services/templates/nginx/ssl.conf.template17
-rw-r--r--services/templates/nginx/timeline.conf.template6
-rw-r--r--services/templates/nginx/websocket.conf4
23 files changed, 271 insertions, 166 deletions
diff --git a/deno/base/cron.ts b/deno/base/cron.ts
index bf0a0be..1b74361 100644
--- a/deno/base/cron.ts
+++ b/deno/base/cron.ts
@@ -1,4 +1,4 @@
-export type CronCallback = (task: CronTask) => Promise<void>;
+export type CronCallback = (task: CronTask) => Promise<void> | void;
export interface CronTaskConfig {
readonly name: string;
diff --git a/deno/deno.json b/deno/deno.json
index 9efebf7..888bdab 100644
--- a/deno/deno.json
+++ b/deno/deno.json
@@ -1,6 +1,7 @@
{
- "workspace": ["./base", "./mail", "./tools"],
+ "workspace": ["./base", "./gateway", "./mail", "./tools"],
"tasks": {
+ "compile:gateway": "deno task --cwd=gateway compile",
"compile:mail": "deno task --cwd=mail compile",
"compile:tools": "deno task --cwd=tools compile"
},
diff --git a/deno/deno.lock b/deno/deno.lock
index bdc8c3f..cf20d47 100644
--- a/deno/deno.lock
+++ b/deno/deno.lock
@@ -3,6 +3,7 @@
"specifiers": {
"jsr:@db/sqlite@0.12": "0.12.0",
"jsr:@denosaurs/plug@1": "1.1.0",
+ "jsr:@hono/hono@^4.12.8": "4.12.8",
"jsr:@std/assert@0.217": "0.217.0",
"jsr:@std/assert@^1.0.13": "1.0.13",
"jsr:@std/async@^1.0.13": "1.0.13",
@@ -14,11 +15,11 @@
"jsr:@std/encoding@^1.0.10": "1.0.10",
"jsr:@std/expect@^1.0.16": "1.0.16",
"jsr:@std/fmt@1": "1.0.8",
- "jsr:@std/fs@1": "1.0.17",
- "jsr:@std/fs@^1.0.17": "1.0.17",
+ "jsr:@std/fs@1": "1.0.18",
+ "jsr:@std/fs@^1.0.17": "1.0.18",
"jsr:@std/fs@^1.0.18": "1.0.18",
- "jsr:@std/internal@^1.0.6": "1.0.7",
- "jsr:@std/internal@^1.0.7": "1.0.7",
+ "jsr:@std/internal@^1.0.6": "1.0.8",
+ "jsr:@std/internal@^1.0.7": "1.0.8",
"jsr:@std/internal@^1.0.8": "1.0.8",
"jsr:@std/io@~0.225.2": "0.225.2",
"jsr:@std/path@0.217": "0.217.0",
@@ -60,6 +61,9 @@
"jsr:@std/path@1"
]
},
+ "@hono/hono@4.12.8": {
+ "integrity": "1997fde8abf28e84d821c9c867229be020e268d4749397aec071305bf54db297"
+ },
"@std/assert@0.217.0": {
"integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642"
},
@@ -1306,6 +1310,11 @@
"npm:yargs@18"
],
"members": {
+ "gateway": {
+ "dependencies": [
+ "jsr:@hono/hono@^4.12.8"
+ ]
+ },
"mail": {
"dependencies": [
"jsr:@db/sqlite@0.12",
diff --git a/deno/gateway/deno.json b/deno/gateway/deno.json
new file mode 100644
index 0000000..db2fab2
--- /dev/null
+++ b/deno/gateway/deno.json
@@ -0,0 +1,13 @@
+{
+ "imports": {
+ "hono": "jsr:@hono/hono@^4.12.8"
+ },
+ "tasks": {
+ "start": "deno run --allow-net main.ts",
+ "compile": "deno compile -o out/crupest-gateway -A main.ts"
+ },
+ "compilerOptions": {
+ "jsx": "precompile",
+ "jsxImportSource": "hono/jsx"
+ }
+}
diff --git a/deno/gateway/main.ts b/deno/gateway/main.ts
new file mode 100644
index 0000000..c82f1a6
--- /dev/null
+++ b/deno/gateway/main.ts
@@ -0,0 +1,220 @@
+import { Context, Hono } from "hono";
+import { serveStatic } from "hono/deno";
+import { logger } from "hono/logger";
+
+import { ConfigDefinition, ConfigProvider } from "@crupest/base/config";
+import { CronTask } from "@crupest/base/cron";
+
+const PREFIX = "crupest";
+const CONFIG_DEFINITION: ConfigDefinition = {
+ domain: {
+ description: "the root domain",
+ },
+ github: {
+ description: "site owner's github url",
+ },
+ v2rayPath: {
+ description: "the path for v2ray websocket",
+ },
+ mailServerAwsInboundPath: {
+ description: "the path for mail server aws inbound webhook",
+ },
+} as const satisfies ConfigDefinition;
+
+const configProvider = new ConfigProvider(PREFIX, CONFIG_DEFINITION);
+type Config = typeof configProvider;
+
+interface Bindings {
+ remoteAddr: string;
+}
+
+interface Env {
+ Bindings: Bindings;
+}
+
+function createReverseProxyHandler({ originServer }: { originServer: string }) {
+ return async (c: Context<Env>) => {
+ const url = new URL(c.req.url);
+ const proto = url.protocol === "https:" ? "https" : "http";
+
+ let forwardedFor = c.req.header("x-forwarded-for");
+ if (forwardedFor) forwardedFor += `, ${c.env.remoteAddr}`;
+ else forwardedFor = c.env.remoteAddr;
+
+ const connection = c.req.header("upgrade") ? "upgrade" : "close";
+
+ return await fetch(`http://${originServer}${url.pathname}${url.search}`, {
+ method: c.req.method,
+ headers: {
+ ...c.req.header(),
+ "Connection": connection,
+ "X-Forwarded-For": forwardedFor,
+ "X-Forwarded-Proto": proto,
+ "X-Real-IP": c.env.remoteAddr,
+ },
+ body: c.req.method !== "GET" && c.req.method !== "HEAD"
+ ? await c.req.arrayBuffer()
+ : undefined,
+ });
+ };
+}
+
+function createHttpHono() {
+ const app = new Hono();
+ app.use(logger());
+
+ // Serve static files for ACME challenge
+ app.get(
+ "/.well-known/acme-challenge/*",
+ serveStatic({
+ root: "/var/www/certbot",
+ }),
+ );
+
+ // Redirect all other requests to the HTTPS version of the site
+ app.all("*", (c) => {
+ return c.redirect(c.req.url.replace("http://", "https://"), 301);
+ });
+
+ return app;
+}
+
+function createRootHono(
+ { basePath, config }: { basePath: string; config: Config },
+) {
+ const app = new Hono();
+
+ app.get("/github", (c) => {
+ const githubUrl = config.get("github");
+ return c.redirect(githubUrl, 302);
+ });
+
+ app.all(
+ "/git/*",
+ createReverseProxyHandler({ originServer: "git-server:3636" }),
+ );
+
+ app.all(
+ `/${config.get("v2rayPath")}`,
+ createReverseProxyHandler({ originServer: "v2ray:10000" }),
+ );
+
+ app.get(
+ "*",
+ serveStatic({
+ root: "/srv/www",
+ rewriteRequestPath: (path) => path.replace(basePath, ""),
+ }),
+ );
+
+ return app;
+}
+
+function createMailHono(
+ { basePath, config }: { basePath: string; config: Config },
+) {
+ const app = new Hono();
+
+ app.get(
+ "/robots.txt",
+ serveStatic({
+ root: "/srv/mail",
+ rewriteRequestPath: (path) => path.replace(basePath, ""),
+ }),
+ );
+
+ app.all(
+ `/${config.get("mailServerAwsInboundPath")}`,
+ createReverseProxyHandler({ originServer: "mail-server:2345" }),
+ );
+
+ app.all(
+ "*",
+ createReverseProxyHandler({ originServer: "roundcubemail:80" }),
+ );
+
+ return app;
+}
+
+function createHttpsHono({ config }: { config: Config }) {
+ const app = new Hono({
+ getPath: (req) => req.url.replace(/^https?:\/([^?]+).*$/, "$1"),
+ });
+ app.use(logger());
+
+ const rootBasePath = `/${config.get("domain")}`;
+ app.route(
+ rootBasePath,
+ createRootHono({ basePath: rootBasePath, config }),
+ );
+
+ const mailBasePath = `/mail.${config.get("domain")}`;
+ app.route(
+ mailBasePath,
+ createMailHono({ basePath: mailBasePath, config }),
+ );
+
+ return app;
+}
+
+let servers: {
+ httpServer: Deno.HttpServer<Deno.NetAddr>;
+ httpsServer: Deno.HttpServer<Deno.NetAddr>;
+} | null = null;
+
+async function restartServer() {
+ if (servers) {
+ await Promise.all([
+ servers.httpServer.shutdown(),
+ servers.httpsServer.shutdown(),
+ ]);
+ }
+
+ const httpApp = createHttpHono();
+ const httpsApp = createHttpsHono({ config: configProvider });
+
+ const httpServer = Deno.serve({
+ port: 80,
+ }, (req, info) => {
+ return httpApp.fetch(req, info);
+ });
+
+ const httpsServer = Deno.serve({
+ port: 443,
+ cert: await Deno.readTextFile(
+ `/etc/letsencrypt/live/${configProvider.get("domain")}/fullchain.pem`,
+ ),
+ key: await Deno.readTextFile(
+ `/etc/letsencrypt/live/${configProvider.get("domain")}/privkey.pem`,
+ ),
+ }, (req, info) => {
+ return httpsApp.fetch(req, info);
+ });
+
+ servers = { httpServer, httpsServer };
+ return servers;
+}
+
+async function certbotRenew() {
+ const command = new Deno.Command("certbot", {
+ args: ["renew", "--webroot", "-w", "/var/www/certbot"],
+ });
+ const process = command.spawn();
+ await process.status;
+ await restartServer();
+}
+
+function main() {
+ restartServer();
+
+ setTimeout(() => {
+ new CronTask({
+ name: "certbot-renewal",
+ interval: 1000 * 60 * 60 * 12,
+ callback: certbotRenew,
+ startNow: true,
+ });
+ }, 5000);
+}
+
+main();
diff --git a/services/docker/gateway/Dockerfile b/services/docker/gateway/Dockerfile
new file mode 100644
index 0000000..68ec5ca
--- /dev/null
+++ b/services/docker/gateway/Dockerfile
@@ -0,0 +1,16 @@
+FROM ghcr.io/gohugoio/hugo AS build-www
+COPY --from=www . /project/
+RUN ls && hugo
+
+FROM denoland/deno AS deno-build
+COPY --from=deno . /workdir/
+WORKDIR /workdir
+RUN deno install
+RUN deno task compile:gateway
+
+FROM debian
+RUN apt update && apt-get install -y tini certbot && rm -rf /var/lib/apt/lists/*
+ADD mail-robots.txt /srv/mail/robots.txt
+COPY --from=build-www /project/public /srv/www
+COPY --from=deno-build /workdir/gateway/out/crupest-gateway /app/
+CMD ["/usr/bin/tini", "--", "/app/crupest-gateway"]
diff --git a/services/docker/nginx/mail-robots.txt b/services/docker/gateway/mail-robots.txt
index 1f53798..1f53798 100644
--- a/services/docker/nginx/mail-robots.txt
+++ b/services/docker/gateway/mail-robots.txt
diff --git a/services/docker/nginx/Dockerfile b/services/docker/nginx/Dockerfile
deleted file mode 100644
index 3169e00..0000000
--- a/services/docker/nginx/Dockerfile
+++ /dev/null
@@ -1,10 +0,0 @@
-FROM ghcr.io/gohugoio/hugo AS build-www
-COPY --from=www . /project/
-RUN ls && hugo
-
-FROM nginx:mainline
-RUN apt update && apt-get install -y tini certbot && rm -rf /var/lib/apt/lists/*
-ADD mail-robots.txt /srv/mail/robots.txt
-ADD certbot.bash nginx-wrapper.bash /app/
-COPY --from=build-www /project/public /srv/www
-CMD ["/usr/bin/tini", "--", "/app/nginx-wrapper.bash"]
diff --git a/services/docker/nginx/certbot.bash b/services/docker/nginx/certbot.bash
deleted file mode 100755
index cb5c636..0000000
--- a/services/docker/nginx/certbot.bash
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/bash
-
-set -e
-
-echo "Sleep 5 seconds waiting for nginx to start."
-sleep 5s
-
-while true; do
- certbot renew --webroot -w /var/www/certbot --deploy-hook "nginx -s reload"
- echo "Sleep one day before next certbot renew."
- sleep 1d
-done
diff --git a/services/docker/nginx/nginx-wrapper.bash b/services/docker/nginx/nginx-wrapper.bash
deleted file mode 100755
index a4a19ec..0000000
--- a/services/docker/nginx/nginx-wrapper.bash
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/bash
-
-set -e -o pipefail
-
-die() {
- echo "$@" >&2
- exit 1
-}
-
-/app/certbot.bash &
-
-/docker-entrypoint.sh nginx "-g" "daemon off;"
diff --git a/services/templates/docker-compose.yaml.template b/services/templates/docker-compose.yaml.template
index e96e8eb..01ab477 100644
--- a/services/templates/docker-compose.yaml.template
+++ b/services/templates/docker-compose.yaml.template
@@ -1,20 +1,21 @@
services:
- nginx:
+ gateway:
build:
- context: "./@@CRUPEST_DOCKER_DIR@@/nginx"
+ context: "./@@CRUPEST_DOCKER_DIR@@/gateway"
additional_contexts:
- "www=./www"
+ - "deno=./deno"
dockerfile: Dockerfile
ports:
- "80:80"
- "443:443"
- "443:443/udp"
env_file:
+ - "./@@CRUPEST_GENERATED_DIR@@/envs/gateway.env"
- "./@@CRUPEST_GENERATED_DIR@@/envs/v2ray-common.env"
- "./@@CRUPEST_GENERATED_DIR@@/envs/mail-server-common.env"
volumes:
- - "./@@CRUPEST_GENERATED_DIR@@/nginx:/etc/nginx/conf.d"
- "./@@CRUPEST_DATA_CERTBOT_DIR@@/certs:/etc/letsencrypt"
- "./@@CRUPEST_DATA_CERTBOT_DIR@@/data:/var/lib/letsencrypt"
- "./@@CRUPEST_DATA_CERTBOT_DIR@@/webroot:/var/www/certbot"
diff --git a/services/templates/envs/gateway.env.template b/services/templates/envs/gateway.env.template
new file mode 100644
index 0000000..9bde5f7
--- /dev/null
+++ b/services/templates/envs/gateway.env.template
@@ -0,0 +1,2 @@
+CRUPEST_DOMAIN=@@CRUPEST_DOMAIN@@
+CRUPEST_GITHUB=@@CRUPEST_GITHUB@@ \ No newline at end of file
diff --git a/services/templates/nginx/common/acme-challenge b/services/templates/nginx/common/acme-challenge
deleted file mode 100644
index 8280cd8..0000000
--- a/services/templates/nginx/common/acme-challenge
+++ /dev/null
@@ -1,3 +0,0 @@
-location /.well-known/acme-challenge {
- root /var/www/certbot;
-}
diff --git a/services/templates/nginx/common/http-listen b/services/templates/nginx/common/http-listen
deleted file mode 100644
index 76cb18d..0000000
--- a/services/templates/nginx/common/http-listen
+++ /dev/null
@@ -1,2 +0,0 @@
-listen 80;
-listen [::]:80;
diff --git a/services/templates/nginx/common/https-listen b/services/templates/nginx/common/https-listen
deleted file mode 100644
index db2f68e..0000000
--- a/services/templates/nginx/common/https-listen
+++ /dev/null
@@ -1,3 +0,0 @@
-listen 443 ssl;
-listen [::]:443 ssl;
-http2 on;
diff --git a/services/templates/nginx/common/https-redirect b/services/templates/nginx/common/https-redirect
deleted file mode 100644
index 56d095d..0000000
--- a/services/templates/nginx/common/https-redirect
+++ /dev/null
@@ -1,3 +0,0 @@
-location / {
- return 301 https://$host$request_uri;
-}
diff --git a/services/templates/nginx/common/reverse-proxy b/services/templates/nginx/common/reverse-proxy
deleted file mode 100644
index 4193548..0000000
--- a/services/templates/nginx/common/reverse-proxy
+++ /dev/null
@@ -1,7 +0,0 @@
-proxy_http_version 1.1;
-proxy_set_header Upgrade $http_upgrade;
-proxy_set_header Connection $connection_upgrade;
-proxy_set_header Host $host;
-proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-proxy_set_header X-Forwarded-Proto $scheme;
-proxy_set_header X-Real-IP $remote_addr;
diff --git a/services/templates/nginx/default.conf b/services/templates/nginx/default.conf
deleted file mode 100644
index 515942b..0000000
--- a/services/templates/nginx/default.conf
+++ /dev/null
@@ -1,9 +0,0 @@
-server {
- listen 80 default_server;
- listen [::]:80 default_server;
- listen 443 ssl default_server;
- listen [::]:443 ssl default_server;
- http2 on;
-
- return 444;
-}
diff --git a/services/templates/nginx/mail.conf.template b/services/templates/nginx/mail.conf.template
deleted file mode 100644
index 1c2a2ca..0000000
--- a/services/templates/nginx/mail.conf.template
+++ /dev/null
@@ -1,29 +0,0 @@
-server {
- server_name mail.@@CRUPEST_DOMAIN@@;
- include conf.d/common/https-listen;
-
- location = /robots.txt {
- root /srv/mail;
- }
-
- location = /@@CRUPEST_MAIL_SERVER_AWS_INBOUND_PATH@@ {
- include conf.d/common/reverse-proxy;
- proxy_pass http://mail-server:2345/@@CRUPEST_MAIL_SERVER_AWS_INBOUND_PATH@@;
- }
-
- location / {
- include conf.d/common/reverse-proxy;
- proxy_pass http://roundcubemail:80/;
- }
-
- client_max_body_size 5G;
-}
-
-
-server {
- server_name mail.@@CRUPEST_DOMAIN@@;
- include conf.d/common/http-listen;
-
- include conf.d/common/https-redirect;
- include conf.d/common/acme-challenge;
-}
diff --git a/services/templates/nginx/root.conf.template b/services/templates/nginx/root.conf.template
deleted file mode 100644
index db28f00..0000000
--- a/services/templates/nginx/root.conf.template
+++ /dev/null
@@ -1,40 +0,0 @@
-server {
- server_name @@CRUPEST_DOMAIN@@;
- include conf.d/common/https-listen;
-
- location / {
- root /srv/www;
- }
-
- location /git/ {
- include conf.d/common/reverse-proxy;
- client_max_body_size 5G;
- proxy_pass http://git-server:3636;
- }
-
- location = /github {
- return 301 @@CRUPEST_GITHUB@@;
- }
-
- location = /github/ {
- return 301 @@CRUPEST_GITHUB@@;
- }
-
- location /_@@CRUPEST_V2RAY_PATH@@ {
- if ($http_upgrade != "websocket") {
- return 404;
- }
-
- proxy_redirect off;
- include conf.d/common/reverse-proxy;
- proxy_pass http://v2ray:10000;
- }
-}
-
-server {
- server_name @@CRUPEST_DOMAIN@@;
- include conf.d/common/http-listen;
-
- include conf.d/common/https-redirect;
- include conf.d/common/acme-challenge;
-}
diff --git a/services/templates/nginx/ssl.conf.template b/services/templates/nginx/ssl.conf.template
deleted file mode 100644
index 181a1af..0000000
--- a/services/templates/nginx/ssl.conf.template
+++ /dev/null
@@ -1,17 +0,0 @@
-# This file contains important security parameters. If you modify this file
-# manually, Certbot will be unable to automatically provide future security
-# updates. Instead, Certbot will print and log an error message with a path to
-# the up-to-date file that you will need to refer to when manually updating
-# this file. Contents are based on https://ssl-config.mozilla.org
-
-ssl_certificate /etc/letsencrypt/live/@@CRUPEST_DOMAIN@@/fullchain.pem;
-ssl_certificate_key /etc/letsencrypt/live/@@CRUPEST_DOMAIN@@/privkey.pem;
-
-ssl_session_cache shared:le_nginx_SSL:10m;
-ssl_session_timeout 1440m;
-ssl_session_tickets off;
-
-ssl_protocols TLSv1.2 TLSv1.3;
-ssl_prefer_server_ciphers off;
-
-ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
diff --git a/services/templates/nginx/timeline.conf.template b/services/templates/nginx/timeline.conf.template
deleted file mode 100644
index 3414510..0000000
--- a/services/templates/nginx/timeline.conf.template
+++ /dev/null
@@ -1,6 +0,0 @@
-server {
- server_name timeline.@@CRUPEST_DOMAIN@@;
- include conf.d/common/http-listen;
-
- include conf.d/common/acme-challenge;
-}
diff --git a/services/templates/nginx/websocket.conf b/services/templates/nginx/websocket.conf
deleted file mode 100644
index 32af4c3..0000000
--- a/services/templates/nginx/websocket.conf
+++ /dev/null
@@ -1,4 +0,0 @@
-map $http_upgrade $connection_upgrade {
- default upgrade;
- '' close;
-}