Add landing page, Hetzner labels, and Codeberg link

- Add default landing page (roles/caddy/templates/index.html.j2) deployed
  to empty caddy sites; adapted from YC/coming-soon by Steven Tang (MIT),
  with site domain and powered-by footer linking to codeberg.org/opennomad/linderhof
- Apply hcloud_labels to all Hetzner cloud and DNS resources; default to {}
  in role defaults for stacks without the variable defined
- Fix setup.sh: export stack_name so envsubst substitutes it in config.yml
- Add Codeberg repo link to README

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthias Johnson 2026-02-28 20:49:22 -07:00
parent bd90a7e16f
commit e4fdcdc279
8 changed files with 112 additions and 1 deletions

View file

@ -2,6 +2,8 @@
> *Linderhof* — the smallest and most intimate of Ludwig II's Bavarian palaces, the only one he lived to see completed; built entirely to his own vision as a private retreat. ([Wikipedia](https://en.wikipedia.org/wiki/Linderhof_Palace))
**[codeberg.org/opennomad/linderhof](https://codeberg.org/opennomad/linderhof)**
a self-hosting stack based on ansible and docker compose that comes with
- email

View file

@ -38,6 +38,16 @@
mode: "0775" # also allow members of the docker group to write
loop: "{{ caddy_sites }}"
- name: Deploy default landing page for empty sites
template:
src: index.html.j2
dest: "/srv/caddy/sites/{{ item }}/index.html"
owner: root
group: docker
mode: "0644"
force: false # never overwrite real content
loop: "{{ caddy_sites }}"
- name: Install Caddyfile
template:
src: Caddyfile.j2

View file

@ -0,0 +1,86 @@
{#
Adapted from "Coming Soon" by Steven Tang (github.com/YC/coming-soon)
MIT License — https://github.com/YC/coming-soon/blob/master/LICENSE
#}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ item }}</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #0f1117;
color: #e8e8e8;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
animation: fadein 2s ease forwards;
}
main {
padding: 2rem;
}
h1 {
font-size: clamp(1.8rem, 5vw, 3rem);
font-weight: 600;
margin: 0 0 1rem;
letter-spacing: -0.02em;
color: #ffffff;
}
p {
font-size: clamp(1rem, 2.5vw, 1.25rem);
font-weight: 300;
color: #a0a0a0;
margin: 0;
}
.dot {
color: #00b98b;
}
footer {
position: fixed;
bottom: 1.5rem;
width: 100%;
font-size: 0.8rem;
color: #444;
}
footer a {
color: #555;
text-decoration: none;
}
footer a:hover {
color: #00b98b;
}
@keyframes fadein {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<main>
<h1>{{ item }}<span class="dot">.</span></h1>
<p>Something's on its way.</p>
</main>
<footer>
powered by <a href="https://codeberg.org/opennomad/linderhof" target="_blank" rel="noopener">linderhof</a>
</footer>
</body>
</html>

View file

@ -1,2 +1,3 @@
---
dns_zones: []
hcloud_labels: {} # override in config.yml — see config.yml.setup for recommended labels

View file

@ -7,6 +7,7 @@
ttl: 300
records:
- value: "{{ server_ip }}"
labels: "{{ hcloud_labels }}"
api_token: "{{ hcloud_token }}"
state: present
@ -18,6 +19,7 @@
ttl: 300
records:
- value: "10 {{ mail_hostname }}."
labels: "{{ hcloud_labels }}"
api_token: "{{ hcloud_token }}"
state: present
@ -29,6 +31,7 @@
ttl: 300
records:
- value: "{{ 'v=spf1 mx -all' | hetzner.hcloud.txt_record }}"
labels: "{{ hcloud_labels }}"
api_token: "{{ hcloud_token }}"
state: present
@ -40,6 +43,7 @@
ttl: 300
records:
- value: "{{ ('v=DMARC1; p=none; rua=mailto:dmarc@' + extra_domain) | hetzner.hcloud.txt_record }}"
labels: "{{ hcloud_labels }}"
api_token: "{{ hcloud_token }}"
state: present
@ -51,6 +55,7 @@
ttl: 300
records:
- value: "{{ dkim_keys[extra_domain] | hetzner.hcloud.txt_record }}"
labels: "{{ hcloud_labels }}"
api_token: "{{ hcloud_token }}"
state: present
when: dkim_keys is defined and extra_domain in dkim_keys

View file

@ -3,6 +3,7 @@
hetzner.hcloud.zone:
name: "{{ item.zone }}"
mode: primary
labels: "{{ hcloud_labels }}"
api_token: "{{ hcloud_token }}"
state: present
loop: "{{ dns_zones }}"
@ -17,6 +18,7 @@
type: "{{ item.1.type }}"
ttl: "{{ item.1.ttl | default(300) }}"
records: "{{ item.1.records }}"
labels: "{{ hcloud_labels }}"
api_token: "{{ hcloud_token }}"
state: present
loop: "{{ dns_zones | subelements('records') }}"
@ -29,6 +31,7 @@
hetzner.hcloud.zone:
name: "{{ item }}"
mode: primary
labels: "{{ hcloud_labels }}"
api_token: "{{ hcloud_token }}"
state: present
loop: "{{ mail_domains | difference([domain]) }}"
@ -51,6 +54,7 @@
ttl: 300
records:
- value: "{{ item.value | hetzner.hcloud.txt_record }}"
labels: "{{ hcloud_labels }}"
api_token: "{{ hcloud_token }}"
state: present
loop: "{{ dkim_keys | default({}) | dict2items }}"

View file

@ -3,3 +3,4 @@ cloud_provider: hetzner
hcloud_server_type: cx23
hcloud_image: ubuntu-24.04
hcloud_location: fsn1
hcloud_labels: {} # override in config.yml — see config.yml.setup for recommended labels

View file

@ -45,6 +45,8 @@ ok "collections installed"
echo
info "stack setup"
prompt stack_name "Stack name" "home"
export stack_name
LINDERHOF_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/linderhof"
STACK_DIR="$LINDERHOF_CONFIG_DIR/$stack_name"
@ -174,7 +176,7 @@ if [[ -f "$CONFIG" ]]; then
warn "config.yml already exists — skipping (not overwriting)"
else
info "writing config.yml..."
envsubst '$admin_user $server_name $server_ip $domain $ssh_key_pub' \
envsubst '$admin_user $server_name $server_ip $domain $ssh_key_pub $stack_name' \
< "$TEMPLATES/config.yml.setup" > "$CONFIG"
ok "config.yml created"
fi