commit 2a55706a8869b35e34e10d028df875fb2bdea5bc Author: karim gabriele varano Date: Mon Jun 29 23:47:33 2026 +0200 fundbureau: single-page site + Docker/Proxmox deploy diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..426bf3c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +public/ +resources/ +.git/ +.github/ +node_modules/ +*.log diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ce3603 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/public/ +/resources/ +.hugo_build.lock +*.log +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d5cc25f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +# ── build the static site with Hugo (extended) ─────────────────────────── +FROM hugomods/hugo:exts AS build +WORKDIR /src +COPY . . +RUN hugo --gc --minify + +# ── serve the static output with nginx ──────────────────────────────────── +FROM nginx:alpine +COPY --from=build /src/public /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 diff --git a/README.md b/README.md new file mode 100644 index 0000000..76d21ab --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# fundbureau.xyz + +Single-page Hugo site for **FUND BUREAU** — a collective of architects based in +Lucerne. Static, no trackers, no third-party services. + +- Build: `hugo` (extended) +- Local dev: `hugo server -D` +- Content & config: `hugo.toml` (wordmark, intro, address, image, footer credit) +- Image: `static/img/hero.webp` (1:1) +- Type: Manrope reference; current build uses Neue Haas Grotesk with a Helvetica + fallback — drop the woff2 into `static/fonts/` to enable it. + +## Deploy + +Docker (nginx) in an LXC on Proxmox, Caddy in front for TLS. See [`deploy/`](deploy/). + +Built with openbureau. diff --git a/assets/css/main.css b/assets/css/main.css new file mode 100644 index 0000000..9c3ea70 --- /dev/null +++ b/assets/css/main.css @@ -0,0 +1,126 @@ +/* --------------------------------------------------------------- + FUND BUREAU — minimal frontpage + typography: Neue Haas Grotesk (lizenzpflichtig). lege deine + woff2-dateien in static/fonts/ ab, dann greifen die @font-face + regeln unten. bis dahin: klassischer Helvetica-fallback. + --------------------------------------------------------------- */ + +@font-face { + font-family: "Neue Haas Grotesk"; + src: url("/fonts/neue-haas-grotesk-display-55-roman.woff2") format("woff2"); + font-weight: 400; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: "Neue Haas Grotesk"; + src: url("/fonts/neue-haas-grotesk-display-65-medium.woff2") format("woff2"); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +:root { + --ink: #111; + --paper: #fff; + --gap: clamp(1.25rem, 4vw, 2.5rem); + /* gemeinsame spaltenbreite: titel, bild und adresse fluchten links */ + --media: min(100%, 56vh, 560px); + --grotesk: "Neue Haas Grotesk", "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +*, *::before, *::after { box-sizing: border-box; } + +html { -webkit-text-size-adjust: 100%; } + +body { + margin: 0; + min-height: 100vh; + padding: var(--gap); + display: grid; + grid-template-rows: auto 1fr auto; + gap: var(--gap); + background: var(--paper); + color: var(--ink); + font-family: var(--grotesk); + font-weight: 400; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + -webkit-user-select: none; + user-select: none; +} + +img { -webkit-user-drag: none; user-drag: none; pointer-events: none; } + +a { color: inherit; text-decoration: none; } +a:hover { text-decoration: underline; text-underline-offset: 0.15em; } + +/* wordmark, oben — linke kante auf der bildkante ------------------------- */ +.wordmark { + width: var(--media); + margin-inline: auto; +} +.wordmark a { + display: inline-block; + font-weight: 500; + font-size: clamp(1.5rem, 4.5vw, 3rem); + letter-spacing: -0.02em; + line-height: 1; + text-transform: uppercase; +} +.wordmark a:hover { text-decoration: none; } + +/* mitte: quadratisches bild + adresse ------------------------------------ */ +.stage { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--gap); +} + +.hero { + margin: 0; + width: var(--media); + aspect-ratio: 1 / 1; /* quadratische box erzwingen */ +} +.hero img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; /* bild füllt die box, wird beschnitten */ + background: #ececec; +} + +/* intro + adresse, unter dem bild ---------------------------------------- */ +.colophon { + width: var(--media); + display: flex; + flex-wrap: wrap; + gap: 0.4em var(--gap); + justify-content: space-between; + font-size: clamp(0.85rem, 1.6vw, 1rem); + line-height: 1.4; +} +.colophon .intro { margin: 0; max-width: 22ch; } + +.colophon address { + font-style: normal; + display: flex; + flex-direction: column; +} +.colophon address span, +.colophon address a { display: block; } + +/* fuss ------------------------------------------------------------------- */ +.credit { + font-size: 0.75rem; + letter-spacing: 0.02em; + opacity: 0.6; + text-transform: lowercase; +} +.credit p { margin: 0; } + +@media (max-width: 600px) { + .colophon { flex-direction: column; } +} diff --git a/deploy/Caddyfile b/deploy/Caddyfile new file mode 100644 index 0000000..ec2993f --- /dev/null +++ b/deploy/Caddyfile @@ -0,0 +1,9 @@ +# Caddy site block for fundbureau. Replace the upstream IP with the container's +# IP, reload caddy (`caddy reload` / `systemctl reload caddy`), verify, then +# retire any old container. Keeping the domain here makes a cutover a one-line +# upstream change. + +fundbureau.xyz, www.fundbureau.xyz { + encode zstd gzip + reverse_proxy CHANGE_ME_CONTAINER_IP:8080 +} diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..90e4b11 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,26 @@ +# Deploy + +Static Hugo build served by nginx in one container +(`docker compose up -d --build`). Hosting: an LXC on Proxmox, with Caddy in +front terminating TLS and reverse-proxying `fundbureau.xyz`. + +## Provision + +On the Proxmox node, with a free CTID and a free IP: + +``` +CTID=142 IP=10.0.0.51/24 GW=10.0.0.1 BRIDGE=vmbr0 STORAGE=local-lvm \ + REPO=https://github.com//fundbureau.git \ + ./provision-lxc.sh +``` + +Then: +1. Test it: `curl -I http://10.0.0.51:8080` +2. Point Caddy's upstream at the IP (`deploy/Caddyfile`), reload Caddy. +3. Open `https://fundbureau.xyz`, verify. + +## Update later + +``` +pct exec -- bash -lc 'cd /opt/fundbureau && git pull && docker compose up -d --build' +``` diff --git a/deploy/provision-lxc.sh b/deploy/provision-lxc.sh new file mode 100755 index 0000000..7e902ed --- /dev/null +++ b/deploy/provision-lxc.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Provision an LXC for the fundbureau website (Docker + compose). RUN ON THE PROXMOX NODE. +# +# Create this container on a FREE CTID and a free IP, test it, then point Caddy's +# upstream at it. Override any value via env, e.g.: +# CTID=142 IP=10.0.0.51/24 GW=10.0.0.1 BRIDGE=vmbr0 STORAGE=local-lvm ./provision-lxc.sh +set -euo pipefail + +CTID="${CTID:-142}" # must be free: pct status $CTID → should error +HOSTNAME_="${HOSTNAME_:-fundbureau}" +IP="${IP:-CHANGE_ME/24}" # free ip in your bridge subnet +GW="${GW:-CHANGE_ME}" +BRIDGE="${BRIDGE:-vmbr0}" +STORAGE="${STORAGE:-local-lvm}" +TEMPLATE="${TEMPLATE:-local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst}" +REPO="${REPO:-https://github.com/CHANGE_ME/fundbureau.git}" +DISK="${DISK:-4}"; CORES="${CORES:-1}"; RAM="${RAM:-512}" + +echo "Creating LXC $CTID ($HOSTNAME_) on $IP ..." +pct create "$CTID" "$TEMPLATE" \ + --hostname "$HOSTNAME_" --cores "$CORES" --memory "$RAM" \ + --rootfs "${STORAGE}:${DISK}" \ + --net0 "name=eth0,bridge=${BRIDGE},ip=${IP},gw=${GW}" \ + --features nesting=1 --unprivileged 1 --onboot 1 +pct start "$CTID" +sleep 6 + +pct exec "$CTID" -- bash -lc ' + set -e + export DEBIAN_FRONTEND=noninteractive + apt-get update && apt-get install -y ca-certificates curl git + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc + chmod a+r /etc/apt/keyrings/docker.asc + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release; echo $VERSION_CODENAME) stable" \ + > /etc/apt/sources.list.d/docker.list + apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin + rm -rf /opt/fundbureau && git clone '"$REPO"' /opt/fundbureau + cd /opt/fundbureau && docker compose up -d --build +' + +echo +echo "done. test the new container: curl -I http://${IP%/*}:8080" +echo "then point Caddy at ${IP%/*}:8080 (see deploy/Caddyfile), reload caddy," +echo "verify the domain." diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..099c316 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +services: + web: + build: . + image: fundbureau + container_name: fundbureau + ports: + - "8080:80" + restart: unless-stopped diff --git a/hugo.toml b/hugo.toml new file mode 100644 index 0000000..0b8143e --- /dev/null +++ b/hugo.toml @@ -0,0 +1,29 @@ +baseURL = "https://fundbureau.xyz/" +languageCode = "de-CH" +title = "FUND BUREAU" +enableEmoji = true +enableRobotsTXT = true + +[params] + # wordmark, oben im schwarzen feld + name = "FUND BUREAU" + # grosser einleitungssatz + intro = "a collective of architects
based in lucerne." + email = "hi@fundbureau.xyz" + instagram = "https://instagram.com/fundbureau" + + # adresse + [params.address] + legal = "FUND BUREAU KlG" + street = "Fluhmühlerain 1" + city = "6015 Luzern" + + # quadratisches bild (1:1, schon zugeschnitten) + [params.hero] + image = "/img/hero.webp" + alt = "a woman and a child, from afar" + + # fuss-credit, links unten + [params.credit] + text = "built with openbureau" + url = "https://kgva.ch" diff --git a/layouts/404.html b/layouts/404.html new file mode 100644 index 0000000..998178d --- /dev/null +++ b/layouts/404.html @@ -0,0 +1,8 @@ +{{ define "bodyclass" }}notfound{{ end }} +{{ define "main" }} +
{{ .Site.Params.name }}
+
+

404 — not found

+

← back

+
+{{ end }} diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html new file mode 100644 index 0000000..bb2621f --- /dev/null +++ b/layouts/_default/baseof.html @@ -0,0 +1,13 @@ + + +{{ partial "head.html" . }} + + {{ block "main" . }}{{ end }} + + + diff --git a/layouts/index.html b/layouts/index.html new file mode 100644 index 0000000..48f8c18 --- /dev/null +++ b/layouts/index.html @@ -0,0 +1,32 @@ +{{ define "bodyclass" }}home{{ end }} +{{ define "main" }} +{{ $a := .Site.Params.address }} +
+ {{ .Site.Params.name }} +
+ +
+ {{ with .Site.Params.hero.image }} +
+ {{ $.Site.Params.hero.alt }} +
+ {{ end }} + +
+ {{ with .Site.Params.intro }}

{{ . | safeHTML }}

{{ end }} +
+ {{ with $a.legal }}{{ . }}{{ end }} + {{ with $a.street }}{{ . }}{{ end }} + {{ with $a.city }}{{ . }}{{ end }} + {{ with .Site.Params.email }}{{ . }}{{ end }} + {{ with .Site.Params.instagram }}instagram{{ end }} +
+
+
+ +{{ with .Site.Params.credit }} + +{{ end }} +{{ end }} diff --git a/layouts/partials/head.html b/layouts/partials/head.html new file mode 100644 index 0000000..2dc1cde --- /dev/null +++ b/layouts/partials/head.html @@ -0,0 +1,15 @@ + + + + {{ .Site.Title }} + + + + + + {{ with .Site.Params.hero.image }}{{ end }} + + + {{ $css := resources.Get "css/main.css" | minify | fingerprint }} + + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..62f0450 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,18 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ =404; + } + + error_page 404 /404.html; + + # long cache for fingerprinted/static assets + location ~* \.(css|js|woff2|webp|svg|png|jpe?g|ico)$ { + expires 7d; + add_header Cache-Control "public, max-age=604800"; + } +} diff --git a/static/fonts/Manrope-VariableFont_wght.woff2 b/static/fonts/Manrope-VariableFont_wght.woff2 new file mode 100644 index 0000000..44f81dc Binary files /dev/null and b/static/fonts/Manrope-VariableFont_wght.woff2 differ diff --git a/static/img/hero.webp b/static/img/hero.webp new file mode 100644 index 0000000..348d302 Binary files /dev/null and b/static/img/hero.webp differ