22
Dockerfile
|
@ -1,22 +1,24 @@
|
||||||
FROM node:lts as build
|
FROM node:lts as web
|
||||||
# Reduce npm log spam and colour during install within Docker
|
# Reduce npm log spam and colour during install within Docker
|
||||||
ENV NPM_CONFIG_LOGLEVEL=warn
|
ENV NPM_CONFIG_LOGLEVEL=warn
|
||||||
ENV NPM_CONFIG_COLOR=false
|
ENV NPM_CONFIG_COLOR=false
|
||||||
|
|
||||||
WORKDIR /home/node/app
|
WORKDIR /home/node/app
|
||||||
|
|
||||||
COPY --chown=node:node . /home/node/app/
|
COPY --chown=node:node web /home/node/app/
|
||||||
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
RUN npm run typecheck
|
RUN npm run typecheck
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM nginx:stable as production
|
FROM denoland/deno:1.42.3 as build-server
|
||||||
WORKDIR /home/node/app
|
|
||||||
# Copy nginx config
|
|
||||||
COPY --chown=nobody:nogroup nginx.conf /etc/nginx/conf.d/default.conf
|
|
||||||
# Copy what we've installed/built from production
|
|
||||||
COPY --chown=nobody:nogroup --from=build /home/node/app/build /usr/share/nginx/html/
|
|
||||||
|
|
||||||
# Caching directory
|
WORKDIR /app
|
||||||
RUN mkdir -p /var/run/nginx-cache && chown nobody:nogroup /var/run/nginx-cache
|
|
||||||
|
COPY server /app
|
||||||
|
COPY --from=web /home/node/app/build /app/web
|
||||||
|
|
||||||
|
RUN deno cache server.ts
|
||||||
|
|
||||||
|
USER deno
|
||||||
|
CMD ["run", "--unstable-kv", "--allow-net", "--allow-read", "server.ts"]
|
||||||
|
|
5
justfile
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
build:
|
||||||
|
docker build -t code.tjo.space/tjo-space/tjo.space:latest .
|
||||||
|
|
||||||
|
run:
|
||||||
|
docker run -p 8080:8080 code.tjo.space/tjo-space/tjo.space:latest
|
55
nginx.conf
|
@ -1,55 +0,0 @@
|
||||||
proxy_cache_path /var/run/nginx-cache/jscache levels=1:2 keys_zone=jscache:100m inactive=30d use_temp_path=off max_size=100m;
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80 default_server;
|
|
||||||
server_name _;
|
|
||||||
resolver 9.9.9.9 1.1.1.1 8.8.8.8;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html index.htm;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /.well-known/ {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
default_type application/json;
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Plausible Analytics
|
|
||||||
# https://plausible.io/docs/proxy/guides/nginx
|
|
||||||
set $plausible_script_url https://plausible.io/js/script.js; # Change this if you use a different variant of the script
|
|
||||||
set $plausible_event_url https://plausible.io/api/event;
|
|
||||||
location = /js/script.js {
|
|
||||||
proxy_pass $plausible_script_url;
|
|
||||||
proxy_set_header Host plausible.io;
|
|
||||||
|
|
||||||
# Tiny, negligible performance improvement. Very optional.
|
|
||||||
proxy_buffering on;
|
|
||||||
|
|
||||||
# Cache the script for 6 hours, as long as plausible.io returns a valid response
|
|
||||||
proxy_cache jscache;
|
|
||||||
proxy_cache_valid 200 6h;
|
|
||||||
proxy_cache_use_stale updating error timeout invalid_header http_500;
|
|
||||||
|
|
||||||
# Optional. Adds a header to tell if you got a cache hit or miss
|
|
||||||
add_header X-Cache $upstream_cache_status;
|
|
||||||
}
|
|
||||||
location = /api/event {
|
|
||||||
proxy_pass $plausible_event_url;
|
|
||||||
proxy_set_header Host plausible.io;
|
|
||||||
proxy_buffering on;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
}
|
|
||||||
|
|
||||||
# redirect server error pages to the static page /50x.html
|
|
||||||
#
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
|
||||||
location = /50x.html {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
}
|
|
||||||
}
|
|
92
server/server.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import { Hono } from "https://deno.land/x/hono@v4.2.5/mod.ts";
|
||||||
|
import { serveStatic } from "https://deno.land/x/hono@v4.2.5/middleware.ts";
|
||||||
|
|
||||||
|
const cache = await Deno.openKv();
|
||||||
|
const app = new Hono();
|
||||||
|
|
||||||
|
// .well-known for matrix
|
||||||
|
app.get("/.well-known/matrix/client", (c) =>
|
||||||
|
c.json({
|
||||||
|
"m.homeserver": {
|
||||||
|
base_url: "https://matrix.chat.tjo.space",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
app.get("/.well-known/matrix/server", (c) =>
|
||||||
|
c.json({
|
||||||
|
"m.server": "matrix.chat.tjo.space:443",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
app.get("/.well-known/matrix/support", (c) =>
|
||||||
|
c.json({
|
||||||
|
contacts: [
|
||||||
|
{
|
||||||
|
matrix_id: "@tine:tjo.space",
|
||||||
|
email_address: "tine@tjo.space",
|
||||||
|
role: "m.role.admin",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// .well-known for webfinger - tailscale
|
||||||
|
app.get("/.well-known/webfinger", (c) => {
|
||||||
|
console.log(c.req.query);
|
||||||
|
|
||||||
|
const resource = c.req.query("resource");
|
||||||
|
if (!resource) {
|
||||||
|
c.status(400);
|
||||||
|
return c.json({ error: "Missing resource query parameter" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const [scheme] = resource.split(":");
|
||||||
|
if (scheme !== "acct") {
|
||||||
|
c.status(400);
|
||||||
|
return c.json({ error: "Only acct scheme is supported" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
subject: resource,
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
rel: "http://schemas.openid.net/specs/connect/1.0/issuer",
|
||||||
|
href: `https://id.tjo.space/application/o/tailscalecom/`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Serve plausible analytics
|
||||||
|
app.get("/js/script.js", async (c) => {
|
||||||
|
c.header("Content-Type", "text/javascript");
|
||||||
|
|
||||||
|
// Check if we have the script cached
|
||||||
|
const cachedScript = await cache.get<string>(["plausible", "script"]);
|
||||||
|
if (cachedScript.value) {
|
||||||
|
return c.body(cachedScript.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch("https://plausible.io/js/plausible.js");
|
||||||
|
const script = await response.text();
|
||||||
|
|
||||||
|
// Cache for 24 hours if we got the script
|
||||||
|
if (response.status === 200) {
|
||||||
|
await cache.set(["plausible", "script"], script, {
|
||||||
|
expireIn: 1000 * 60 * 60 * 24,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.body(script);
|
||||||
|
});
|
||||||
|
app.get("/api/event", (c) => {
|
||||||
|
const request = new Request(c.req.raw);
|
||||||
|
request.headers.delete("Cookie");
|
||||||
|
request.headers.set("X-Forwarded-For", c.req.header("host") || "");
|
||||||
|
return fetch("https://plausible.io/api/event", request);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Serve website
|
||||||
|
app.use("*", serveStatic({ root: "./web" }));
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
Deno.serve({ port: 8080, hostname: "0.0.0.0" }, app.fetch);
|
0
.gitignore → web/.gitignore
vendored
0
package-lock.json → web/package-lock.json
generated
|
@ -12,8 +12,7 @@
|
||||||
"serve": "docusaurus serve",
|
"serve": "docusaurus serve",
|
||||||
"write-translations": "docusaurus write-translations",
|
"write-translations": "docusaurus write-translations",
|
||||||
"write-heading-ids": "docusaurus write-heading-ids",
|
"write-heading-ids": "docusaurus write-heading-ids",
|
||||||
"typecheck": "tsc",
|
"typecheck": "tsc"
|
||||||
"build:docker": "docker build -t tjo-space ."
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "2.4.1",
|
"@docusaurus/core": "2.4.1",
|
0
web/static/.nojekyll
Normal file
Before (image error) Size: 5.6 KiB After (image error) Size: 5.6 KiB |
Before (image error) Size: 6.8 KiB After (image error) Size: 6.8 KiB |
Before (image error) Size: 6.8 KiB After (image error) Size: 6.8 KiB |
Before (image error) Size: 10 KiB After (image error) Size: 10 KiB |
Before (image error) Size: 8.8 KiB After (image error) Size: 8.8 KiB |
Before (image error) Size: 10 KiB After (image error) Size: 10 KiB |
Before (image error) Size: 25 KiB After (image error) Size: 25 KiB |
Before (image error) Size: 12 KiB After (image error) Size: 12 KiB |
Before (image error) Size: 11 KiB After (image error) Size: 11 KiB |
Before (image error) Size: 12 KiB After (image error) Size: 12 KiB |
Before (image error) Size: 14 KiB After (image error) Size: 14 KiB |
Before (image error) Size: 5.3 KiB After (image error) Size: 5.3 KiB |
Before (image error) Size: 19 KiB After (image error) Size: 19 KiB |
Before (image error) Size: 22 KiB After (image error) Size: 22 KiB |
Before (image error) Size: 8.4 KiB After (image error) Size: 8.4 KiB |
Before (image error) Size: 12 KiB After (image error) Size: 12 KiB |