diff --git a/README.md b/README.md index a3ae986..b443611 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,6 @@ The image serves HTTP only. Its `scratch` filesystem contains a static musl bina and CA certificates for outgoing HTTPS requests, with no shell or package manager. It runs as an unprivileged user. -The maintainer's [VPS deployment files](deploy/README.md) provide a systemd -unit for Podman, an nginx configuration and a plain HTML usage page. - ## Calendar subscription and privacy **Self-hosting is recommended. Do not blindly trust any hosted instance, diff --git a/deploy/README.md b/deploy/README.md deleted file mode 100644 index 32da8f5..0000000 --- a/deploy/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# VPS deployment - -These files configure the maintainer's Debian 12 server with Podman 4.3, -systemd and nginx. The nginx configuration uses the existing wildcard -certificate at `/etc/letsencrypt/live/popov.link/` and the server's TLS settings. - -| Source | Installed path | -| --- | --- | -| `vacationplanner.service` | `/etc/systemd/system/vacationplanner.service` | -| `vacationplanner.conf` | `/etc/nginx/sites-available/vacationplanner.conf` | -| `index.html` | `/var/www/vacationplanner/index.html` | - -Enable the nginx site with a symlink in `/etc/nginx/sites-enabled/`. Validate -the unit with `systemd-analyze verify`, run `systemctl daemon-reload`, then -`systemctl enable --now vacationplanner.service`. Run `nginx -t` before -`systemctl reload nginx`. Back up existing configuration before replacing it. - -The container listens on `127.0.0.1:8080` using host networking. It runs as -`65532:65532`, with a read-only filesystem, no capabilities or additional -privileges, and limits of 128 MiB without swap, 0.5 CPU and 64 processes. -systemd restarts it after an exit and checks `/healthz` before marking the -start successful. Graceful shutdown allows 65 seconds. - -Only `/calendar.ics` is proxied. Other valid GET/HEAD paths serve the same -HTML 4.01 Strict page; unsupported methods return `405`. Calendar limits are -6 requests/minute per IP with a burst of 4, 2 requests/second overall with a -burst of 16, and 4 concurrent requests per IP or 16 overall. Rejections return -`429` and `Retry-After: 60`. Client addresses come from nginx's existing -trusted proxy configuration. Both virtual hosts disable access and error -logging; calendar responses are not cached or buffered to disk. - -## Updates and recovery - -The existing `podman-auto-update.timer` checks the registry daily. The -`io.containers.autoupdate=registry` label and `PODMAN_SYSTEMD_UNIT` connect -the container to its unit. Podman's default rollback restores the previous -image if restarting the updated service fails, including its HTTP readiness -check. Ordinary restarts use the saved image with `--pull=missing`. - -```sh -sudo systemctl status vacationplanner.service podman-auto-update.timer -curl --fail http://127.0.0.1:8080/healthz -sudo podman auto-update --dry-run -``` - -Before an update, retain a known-good image under a separate local tag and -record its digest. This also protects it from the existing timer's image -pruning. To recover manually, tag that saved image as -`code.popov.link/valentineus/vacationplanner2ics:latest` and restart -`vacationplanner.service`. If the registry image is still faulty, temporarily -remove the container's auto-update label from this unit before restarting; -restore it when a fixed image is available. Restore nginx files from the -backup, validate them and reload nginx if the proxy change must be reverted. - -HTML validation uses OpenSP with the W3C HTML 4.01 Strict DTD, HTML Tidy and -Lynx in Docker. Deployment checks use a local mock API to exercise query -forwarding, routes, headers, rate limits and concurrent request limits. -Use fake credentials for these checks and verify logging before making a -real calendar request. diff --git a/deploy/index.html b/deploy/index.html deleted file mode 100644 index 4be3ccc..0000000 --- a/deploy/index.html +++ /dev/null @@ -1,50 +0,0 @@ - - -
- -This service turns Vacationplanner vacations into an iCalendar - subscription for Apple Calendar and other calendar applications.
- -Add a calendar subscription using the following URL. Replace
- YOUR_API_TOKEN with your Vacationplanner API token and
- URL-encode its value.
https://vacationplanner.popov.link/calendar.ics?token=YOUR_API_TOKEN-
tokenyearsTo select years explicitly:
-https://vacationplanner.popov.link/calendar.ics?token=YOUR_API_TOKEN&years=2026,2027,2028-
The calendar application controls the refresh interval. Occasional - requests from several devices are supported; frequent requests may be - rate limited. Keep the subscription URL private.
- -Self-hosting is recommended. Do not blindly trust - any hosted instance, including this one. The subscription URL contains - your API token. Service operators and providers that terminate HTTPS - can read it. HTTPS does not hide the token from those operators.
-The service does not store calendars or log requests. Query-string - logging is disabled on this server, but this does not guarantee privacy - across the infrastructure. Prefer a server you control and replace - the host in the example URLs with your own.
-The repositories below contain source code, ready-made container - image links and self-hosting instructions.
- -Repository locations: - canonical source - · github - · read-only mirror
- - diff --git a/deploy/vacationplanner.conf b/deploy/vacationplanner.conf deleted file mode 100644 index 50e3c19..0000000 --- a/deploy/vacationplanner.conf +++ /dev/null @@ -1,84 +0,0 @@ -limit_req_zone $binary_remote_addr zone=vacationplanner_ip_rate:1m rate=6r/m; -limit_req_zone $server_name zone=vacationplanner_total_rate:1m rate=2r/s; -limit_conn_zone $binary_remote_addr zone=vacationplanner_ip_conn:1m; -limit_conn_zone $server_name zone=vacationplanner_total_conn:1m; - -map $status $vacationplanner_retry_after { - default ""; - 429 60; -} - -map $status $vacationplanner_allow { - default ""; - 405 "GET, HEAD"; -} - -server { - listen 80; - listen [::]:80; - server_name vacationplanner.popov.link; - - access_log off; - error_log /dev/null; - add_header Referrer-Policy "no-referrer" always; - - return 301 https://vacationplanner.popov.link$request_uri; -} - -server { - listen 443 ssl; - listen [::]:443 ssl; - server_name vacationplanner.popov.link; - - ssl_certificate /etc/letsencrypt/live/popov.link/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/popov.link/privkey.pem; - ssl_trusted_certificate /etc/letsencrypt/live/popov.link/chain.pem; - - root /var/www/vacationplanner; - access_log off; - error_log /dev/null; - - # Defining a header here replaces the inherited add_header directives. - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-XSS-Protection "1; mode=block" always; - add_header Referrer-Policy "no-referrer" always; - add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; - add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; - add_header Retry-After $vacationplanner_retry_after always; - add_header Allow $vacationplanner_allow always; - - if ($request_method !~ ^(GET|HEAD)$) { - return 405; - } - - location = /calendar.ics { - limit_req zone=vacationplanner_ip_rate burst=4 nodelay; - limit_req zone=vacationplanner_total_rate burst=16 nodelay; - limit_req_status 429; - limit_conn vacationplanner_ip_conn 4; - limit_conn vacationplanner_total_conn 16; - limit_conn_status 429; - - proxy_pass http://127.0.0.1:8080; - 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 $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass_request_body off; - proxy_set_header Content-Length ""; - proxy_hide_header Referrer-Policy; - proxy_hide_header X-Content-Type-Options; - proxy_read_timeout 65s; - proxy_buffering off; - proxy_cache off; - proxy_max_temp_file_size 0; - proxy_intercept_errors off; - } - - location / { - try_files /index.html =404; - } -} diff --git a/deploy/vacationplanner.service b/deploy/vacationplanner.service deleted file mode 100644 index 349e39f..0000000 --- a/deploy/vacationplanner.service +++ /dev/null @@ -1,36 +0,0 @@ -[Unit] -Description=Vacationplanner calendar subscriptions -Wants=network-online.target -After=network-online.target -RequiresMountsFor=/var/lib/containers/storage -StartLimitIntervalSec=0 - -[Service] -Type=notify -NotifyAccess=all -Environment=PODMAN_SYSTEMD_UNIT=%n -RuntimeDirectory=vacationplanner -RuntimeDirectoryMode=0700 -Restart=always -RestartSec=5 -TimeoutStartSec=180 -TimeoutStopSec=80 -ExecStart=/usr/bin/podman run --detach --rm --replace \ - --name vacationplanner --cidfile=/run/vacationplanner/container.cid \ - --cgroups=no-conmon --sdnotify=conmon --pull=missing \ - --label=io.containers.autoupdate=registry \ - --network=host --env=BIND_ADDR=127.0.0.1:8080 \ - --user=65532:65532 --read-only --read-only-tmpfs=false \ - --cap-drop=ALL --security-opt=no-new-privileges \ - --memory=128m --memory-swap=128m --cpus=0.5 --pids-limit=64 \ - --stop-timeout=65 --log-driver=none \ - code.popov.link/valentineus/vacationplanner2ics:latest -ExecStartPost=/usr/bin/curl --fail --silent --show-error \ - --retry 20 --retry-all-errors --retry-delay 1 --retry-max-time 45 \ - --connect-timeout 1 --max-time 2 --output /dev/null \ - http://127.0.0.1:8080/healthz -ExecStop=-/usr/bin/podman stop --ignore --time=65 --cidfile=/run/vacationplanner/container.cid -ExecStopPost=-/usr/bin/podman rm --force --ignore --cidfile=/run/vacationplanner/container.cid - -[Install] -WantedBy=multi-user.target