1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
#!/bin/sh
set -eu
bin=${GITPOCKET_BIN:-/usr/local/bin/gitpocket}
root=${GITPOCKET_ROOT:-/srv/gitpocket}
user=${GITPOCKET_USER:-git}
port=${GITPOCKET_PORT:-3200}
conf=${GITPOCKET_NGINX:-/etc/nginx/conf.d/gitpocket-domains.conf}
cert=${GITPOCKET_CERT:-/etc/gitpocket/origin.pem}
key=${GITPOCKET_KEY:-/etc/gitpocket/origin.key}
cloudflare=${GITPOCKET_CLOUDFLARE:-/etc/gitpocket/cloudflare}
domains=$(runuser -u "$user" -- "$bin" -root "$root" domains | grep -E '^[a-z0-9.-]+$' || true)
render() {
[ -n "$domains" ] || return 0
names=$(printf ' %s' $domains)
cat <<CONF
server {
listen 80;
listen [::]:80;
server_name$names;
include snippets/gitpocket-cloudflare.conf;
location / { return 301 https://\$host\$request_uri; }
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name$names;
ssl_certificate $cert;
ssl_certificate_key $key;
ssl_protocols TLSv1.2 TLSv1.3;
include snippets/gitpocket-cloudflare.conf;
client_max_body_size 1m;
add_header Strict-Transport-Security "max-age=31536000" always;
gzip on;
gzip_proxied any;
gzip_vary on;
gzip_types text/css text/plain text/javascript application/javascript image/svg+xml application/json application/xml;
location / {
limit_except GET HEAD { deny all; }
proxy_pass http://127.0.0.1:$port;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For \$remote_addr;
proxy_set_header Cookie "";
}
}
CONF
}
apply() {
next=$(mktemp)
render > "$next"
if [ -f "$conf" ] && cmp -s "$next" "$conf"; then
rm -f "$next"
return 0
fi
[ -f "$conf" ] && cp "$conf" "$conf.old"
mv "$next" "$conf"
chmod 644 "$conf"
if nginx -t 2>/dev/null; then
systemctl reload nginx
else
echo "nginx rejected $conf, restoring" >&2
if [ -f "$conf.old" ]; then mv "$conf.old" "$conf"; else rm -f "$conf"; fi
exit 1
fi
}
hostnames() {
[ -r "$cloudflare" ] || return 0
python3 - "$cloudflare" $domains <<'PY'
import json, sys, urllib.request
env = dict(l.strip().split("=", 1) for l in open(sys.argv[1]) if "=" in l)
want = set(sys.argv[2:]) - set(env.get("CF_SKIP", "").split())
base = f"https://api.cloudflare.com/client/v4/zones/{env['CF_ZONE']}/custom_hostnames"
def call(method, url, body=None):
req = urllib.request.Request(url, method=method, data=json.dumps(body).encode() if body else None,
headers={"Authorization": "Bearer " + env["CF_TOKEN"], "Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
have, page = {}, 1
while True:
res = call("GET", f"{base}?per_page=50&page={page}")["result"]
have.update({h["hostname"]: h["id"] for h in res})
if len(res) < 50:
break
page += 1
for name in sorted(want - have.keys()):
try:
call("POST", base, {"hostname": name, "ssl": {"method": "http", "type": "dv"}})
except Exception as e:
print(f"custom hostname {name}: {e}", file=sys.stderr)
for name in sorted(have.keys() - want):
call("DELETE", f"{base}/{have[name]}")
PY
}
apply
hostnames
|