diff --git a/Caddyfile b/Caddyfile index 3c7483d..772f55e 100644 --- a/Caddyfile +++ b/Caddyfile @@ -1,49 +1,41 @@ { - # we’re behind cloudflare tunnel; terminate tls there - auto_https off + auto_https off } -:8550 { - # route by host header to each backend +# short domain (http-only on :8550) +http://{$SHORT_DOMAIN}:8550 { + log { + output stdout + format console + } - @paste host {env.PASTE_DOMAIN} - handle @paste { - reverse_proxy privatebin:8080 - } + @shorten path /shorten + handle @shorten { + header { + Access-Control-Allow-Origin https://{$PASTE_DOMAIN} + Access-Control-Allow-Methods GET, OPTIONS + Access-Control-Allow-Headers * + Cache-Control no-store + } + reverse_proxy http://shlink-adapter:3000 + } - @files host {env.FILES_DOMAIN} - handle @files { - reverse_proxy lufi:8081 - } + @shortenPre method OPTIONS path /shorten + respond @shortenPre 204 - @short host {env.SHORT_DOMAIN} - handle @short { - reverse_proxy shlink:8080 - } - - # adapter endpoint on the short domain - @shorten { - host {env.SHORT_DOMAIN} - path /shorten - } - handle @shorten { - header { - Access-Control-Allow-Origin https://{env.PASTE_DOMAIN} - Access-Control-Allow-Methods GET, OPTIONS - Access-Control-Allow-Headers * - Cache-Control no-store - } - reverse_proxy shlink-adapter:3000 - } - - # preflight for /shorten - @shortenPre { - host {env.SHORT_DOMAIN} - method OPTIONS - path /shorten - } - respond @shortenPre 204 - - respond "unauthorized domain" 404 + # everything else → shlink ui/api + handle { + reverse_proxy http://shlink:8080 + } +} + +# paste domain (http-only) +http://{$PASTE_DOMAIN}:8550 { + reverse_proxy http://privatebin:8080 +} + +# files domain (http-only) +http://{$FILES_DOMAIN}:8550 { + reverse_proxy http://lufi:8081 } diff --git a/docker-compose.yml b/docker-compose.yml index 48aedcf..0cf3bda 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -95,9 +95,18 @@ services: shlink-db: condition: service_healthy + shlink-adapter: + build: ./shlink-adapter + restart: unless-stopped + environment: + SHLINK_BASE: "http://shlink:8080" + SHLINK_API_KEY: "${SHLINK_API_KEY}" + networks: + - proxy + networks: proxy: - external: false + driver: bridge volumes: privatebin-data: diff --git a/privatebin.conf.php b/privatebin.conf.php index 367d760..25c1d3a 100644 --- a/privatebin.conf.php +++ b/privatebin.conf.php @@ -73,7 +73,7 @@ urlshortener = "https://{env.SHORT_DOMAIN}/shorten?link=" ; (optional) Whether to shorten the URL by default when a new document is created. ; If set to true, the "Shorten URL" functionality will be automatically called. ; This only works if the "urlshortener" option is set. -; shortenbydefault = false +shortenbydefault = true ; (optional) Let users create a QR code for sharing the document URL with one click. ; It works both when a new document is created and when you view a document. @@ -184,4 +184,4 @@ class = Filesystem ; Either form below works. Pick ONE (uncommented). Here we use the conventional form: dir = PATH "data" ; Alternative explicit absolute path (also valid): -; dir = "/srv/data" \ No newline at end of file +; dir = "/srv/data" diff --git a/shlink-adapter/Dockerfile b/shlink-adapter/Dockerfile new file mode 100644 index 0000000..deb65fb --- /dev/null +++ b/shlink-adapter/Dockerfile @@ -0,0 +1,10 @@ +FROM oven/bun + +WORKDIR /app + +RUN ls /app > /app/temp.txt +COPY server.js . + +EXPOSE 3000 + +CMD ["bun", "run", "server.js"] diff --git a/shlink-adapter/server.js b/shlink-adapter/server.js new file mode 100644 index 0000000..56bce65 --- /dev/null +++ b/shlink-adapter/server.js @@ -0,0 +1,52 @@ +import http from "http"; + +const base = process.env.SHLINK_BASE ?? ""; +const key = process.env.SHLINK_API_KEY ?? ""; + +const server = http.createServer(async (req, res) => { + try { + const u = new URL(req.url ?? "/", "http://local/"); + if (req.method === "OPTIONS" && u.pathname === "/shorten") { + res.writeHead(204); res.end(); return; + } + + if (u.pathname === "/" || u.pathname === "") { + res.writeHead(200); res.end("Ok"); return; + } + + if (req.method !== "GET" || u.pathname !== "/shorten") { + res.writeHead(404); res.end("not found"); return; + } + + const longUrl = + u.searchParams.get("link") ?? + u.searchParams.get("url") ?? + u.searchParams.get("longUrl"); + + if (!longUrl) { res.writeHead(400); res.end("missing url"); return; } + + // shlink v3 rest api: create short url + const r = await fetch(`${base}/rest/v3/short-urls`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + "X-Api-Key": key + }, + body: JSON.stringify({ longUrl }) + }); + + if (!r.ok) { + const txt = await r.text(); + res.writeHead(502); res.end(`shlink error: ${txt}`); return; + } + const data = await r.json(); + res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" }); + res.end((data.shortUrl ?? "") + "\n"); + } catch { + res.writeHead(500); res.end("internal error"); + } +}); + +server.listen(3000); +