Read-only mirror — the public repositories of the Forgejo instance, refreshed every 10 minutes. The live instance (fg.dieschoenewolke.de) is reachable only over the VPN; clone here with https://git.dieschoenewolke.de/git/<owner>/<repo>.git.
summaryrefslogtreecommitdiff
path: root/deploy
diff options
context:
space:
mode:
Diffstat (limited to 'deploy')
-rw-r--r--deploy/config.json88
-rw-r--r--deploy/docker-compose.yml28
-rw-r--r--deploy/nginx-edge-img.conf68
3 files changed, 184 insertions, 0 deletions
diff --git a/deploy/config.json b/deploy/config.json
new file mode 100644
index 0000000..642046a
--- /dev/null
+++ b/deploy/config.json
@@ -0,0 +1,88 @@
+{
+ "distSpecVersion": "1.1.1",
+ "storage": {
+ "rootDirectory": "/var/lib/registry",
+ "gc": true,
+ "gcDelay": "1h",
+ "dedupe": true
+ },
+ "http": {
+ "address": "0.0.0.0",
+ "port": "5000",
+ "realm": "distroless-mirror",
+ "accessControl": {
+ "repositories": {
+ "**": {
+ "anonymousPolicy": [
+ "read"
+ ]
+ }
+ }
+ }
+ },
+ "log": {
+ "level": "info",
+ "output": "/dev/stdout"
+ },
+ "extensions": {
+ "sync": {
+ "enable": true,
+ "downloadDir": "/var/lib/registry/.sync",
+ "registries": [
+ {
+ "urls": [
+ "https://gcr.io"
+ ],
+ "tlsVerify": true,
+ "onDemand": true,
+ "manifestCheckInterval": "30m",
+ "maxRetries": 3,
+ "retryDelay": "5m",
+ "maxRetryDelay": "30m",
+ "content": [
+ {
+ "prefix": "distroless/static",
+ "tags": {
+ "regex": "^(latest|nonroot|debug|debug-nonroot)$"
+ },
+ "platforms": [
+ "linux/amd64",
+ "linux/arm64"
+ ]
+ },
+ {
+ "prefix": "distroless/base",
+ "tags": {
+ "regex": "^(latest|nonroot|debug|debug-nonroot)$"
+ },
+ "platforms": [
+ "linux/amd64",
+ "linux/arm64"
+ ]
+ },
+ {
+ "prefix": "distroless/cc",
+ "tags": {
+ "regex": "^(latest|nonroot|debug|debug-nonroot)$"
+ },
+ "platforms": [
+ "linux/amd64",
+ "linux/arm64"
+ ]
+ },
+ {
+ "prefix": "distroless/python3",
+ "tags": {
+ "regex": "^(latest|nonroot|debug|debug-nonroot)$"
+ },
+ "platforms": [
+ "linux/amd64",
+ "linux/arm64"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml
new file mode 100644
index 0000000..e486a2f
--- /dev/null
+++ b/deploy/docker-compose.yml
@@ -0,0 +1,28 @@
+# Public read-only OCI mirror of the four upstream Google distroless images.
+#
+# Serving path: public :443 -> edge-nginx (host network, TLS + CrowdSec) ->
+# 127.0.0.1:5000 -> this container. The port publish is loopback-only on
+# purpose: nothing on any host interface can reach zot directly, so the edge
+# vhost is the only entry point.
+#
+# There is no `auth` block and anonymous users only get `read`, so the registry
+# has no push surface at all - not from the internet and not from the LAN.
+# Content arrives exclusively through the built-in sync extension pulling from
+# gcr.io, which is also why the mirror keeps working while the home Forgejo
+# instance (and its runner) is down.
+services:
+ zot:
+ image: ghcr.io/project-zot/zot:v2.1.23
+ container_name: zot-mirror
+ command: ["serve", "/etc/zot/config.json"]
+ restart: unless-stopped
+ ports:
+ - "127.0.0.1:5000:5000"
+ volumes:
+ - ./data:/var/lib/registry
+ - ./config.json:/etc/zot/config.json:ro
+ logging:
+ driver: json-file
+ options:
+ max-size: "10m"
+ max-file: "3"
diff --git a/deploy/nginx-edge-img.conf b/deploy/nginx-edge-img.conf
new file mode 100644
index 0000000..3d6ac16
--- /dev/null
+++ b/deploy/nginx-edge-img.conf
@@ -0,0 +1,68 @@
+# --- img.dieschoenewolke.de: public read-only OCI mirror ---------------------
+# Merge this block into /root/Compose/Webserver/NginxEdge/nginx/default.conf
+# (write in place: `cat new > default.conf`) and force-recreate edge-nginx.
+#
+# docker/containerd/skopeo/crane all speak plain GET+HEAD with a Go or Docker
+# user agent. CRS treats those as scanners, so AppSec is off for this host - the
+# CrowdSec IP bouncer still applies and the registry itself is read-only.
+# `limit_except GET` is the second lock: even if zot were reconfigured, the edge
+# refuses every write verb.
+#
+# A path-based /v2/ on git.dieschoenewolke.de is NOT an option: OCI clients
+# build the repository name from <host>/<path>, so a shared host would force
+# names like git.dieschoenewolke.de/distroless/static and the /v2/ base path
+# would have to be stripped by the edge. docker's ping is
+# GET /v2/ (not /git/v2/), so the mirror gets its own name. The wildcard
+# *.dieschoenewolke.de certificate already covers it - no cert change.
+server {
+ listen 443 ssl;
+ listen [::]:443 ssl;
+ listen 443 quic;
+ listen [::]:443 quic;
+ http2 on;
+ server_name img.dieschoenewolke.de;
+ access_log /dev/stdout edge;
+
+ set $crowdsec_disable_appsec 1;
+
+ add_header Alt-Svc 'h3=":443"; ma=86400' always;
+
+ ssl_certificate /certs/edge.crt;
+ ssl_certificate_key /certs/edge.key;
+ ssl_protocols TLSv1.2 TLSv1.3;
+ 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;
+ ssl_prefer_server_ciphers off;
+ ssl_ecdh_curve X25519:prime256v1:secp384r1;
+ ssl_session_cache shared:SSL:10m;
+ ssl_session_timeout 1d;
+ ssl_session_tickets off;
+
+ more_set_headers
+ "Strict-Transport-Security: max-age=31536000; includeSubDomains"
+ "X-Content-Type-Options: nosniff"
+ "Referrer-Policy: strict-origin-when-cross-origin";
+
+ # a manifest/index is a few hundred KB; layers are streamed, never buffered
+ client_max_body_size 4m;
+
+ location / {
+ limit_except GET {
+ deny all;
+ }
+
+ proxy_pass http://127.0.0.1:5000;
+ proxy_http_version 1.1;
+ proxy_set_header Connection "";
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto https;
+ # stream blobs instead of spooling them to the edge disk (28 GB root)
+ proxy_buffering off;
+ proxy_max_temp_file_size 0;
+ proxy_request_buffering off;
+ proxy_read_timeout 900s;
+ proxy_send_timeout 900s;
+ proxy_cache off;
+ }
+}