chore: remove VPS deployment files
CI / Test and publish (push) Successful in 26s

This commit is contained in:
2026-09-10 21:26:33 +04:00
parent a2485f16a6
commit f06b959c89
5 changed files with 0 additions and 232 deletions
-3
View File
@@ -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,
-59
View File
@@ -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.
-50
View File
@@ -1,50 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
<title>Vacationplanner calendar subscriptions</title>
</head>
<body>
<h1>Vacationplanner calendar subscriptions</h1>
<p>This service turns Vacationplanner vacations into an iCalendar
subscription for Apple Calendar and other calendar applications.</p>
<h2>Usage</h2>
<p>Add a calendar subscription using the following URL. Replace
<code>YOUR_API_TOKEN</code> with your Vacationplanner API token and
URL-encode its value.</p>
<pre>https://vacationplanner.popov.link/calendar.ics?token=YOUR_API_TOKEN</pre>
<dl>
<dt><code>token</code></dt>
<dd>Required: your Vacationplanner API token.</dd>
<dt><code>years</code></dt>
<dd>Optional: comma-separated years. The default is the current UTC
year and the next year. Up to 10 distinct years from 1 to 9998 are
accepted; duplicate years are removed.</dd>
</dl>
<p>To select years explicitly:</p>
<pre>https://vacationplanner.popov.link/calendar.ics?token=YOUR_API_TOKEN&amp;years=2026,2027,2028</pre>
<p>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.</p>
<h2>Privacy and self-hosting</h2>
<p><strong>Self-hosting is recommended.</strong> 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.</p>
<p>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.</p>
<p>The repositories below contain source code, ready-made container
image links and self-hosting instructions.</p>
<hr>
<p>Repository locations:
<a href="https://code.popov.link/valentineus/vacationplanner2ics">canonical source</a>
&middot; <a href="https://github.com/valentineus/vacationplanner2ics">github</a>
&middot; <a href="https://git.popov.link/popov.link/vacationplanner2ics/">read-only mirror</a></p>
</body>
</html>
-84
View File
@@ -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;
}
}
-36
View File
@@ -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