feat(website): download page for the ISO, homepage with download CTA and 3-step how-it-works

- /download/ and /de/download/: big download button, file name, size and
  release date, requirements (64-bit PC, UEFI or BIOS, >=16 GB disk that
  gets erased, USB stick, network cable with internet, second device),
  five install steps (Etcher / Rufus DD mode / dd, boot menu, Secure Boot
  off, proksi.local:5000, wizard, pull stick), checksum + verify commands
  when the release has one, alpha notice, link to all releases
- the release data comes from the Forgejo API at build time
  (partials/release.html): newest release with an .iso asset, falling back
  to params.version if the lookup fails; getresource cache disabled so a
  build never pins an old release
- homepage: status chip follows the latest release, 'Download ISO' primary
  CTA + 'Source code' ghost CTA, numbered how-it-works cards, download band
  before the closer
- releases now upload furtka-<ver>.iso.sha256, and release.yml rebuilds the
  site after publishing so the download page follows each release

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wq1hzUp4ZmCKHDuLKCnu73
This commit is contained in:
Daniel Maksymilian Syrnicki 2026-09-28 14:52:59 +02:00
parent 4c5bfe3aef
commit 0cad2e05c8
12 changed files with 496 additions and 8 deletions

View file

@ -57,3 +57,13 @@ jobs:
env:
FORGEJO_TOKEN: ${{ secrets.FORGEJO_RELEASE_TOKEN }}
run: ./scripts/publish-release.sh "${GITHUB_REF_NAME}"
- name: Refresh furtka.org download page
# The download page reads the latest release (ISO, size, date,
# checksum) from the Forgejo API at build time, so rebuild the site
# now that the release exists. Same runner + bind mounts as
# deploy-site.yml. A failure here doesn't touch the release.
continue-on-error: true
run: |
apk add --no-cache hugo rsync
./website/deploy-ci.sh

View file

@ -45,7 +45,7 @@ Tag per meaningful milestone, not on a calendar. A milestone is: ISO boots, a wi
git push origin 26.1-alpha
```
5. **The release workflow does the rest.** `.forgejo/workflows/release.yml` fires on the tag push and runs on the self-hosted runner: `scripts/build-release-tarball.sh` builds the self-update payload (tarball + sha256 + release.json under `dist/`), `iso/build.sh` builds the live-installer ISO, `scripts/publish-release.sh` uploads tarball + sha256 + release.json + ISO to the Forgejo release page. Pre-release is flagged automatically based on the suffix (`-alpha`/`-beta`/`-rc`). ISO build is `continue-on-error`: a flaky ISO step doesn't block the core tarball (the thing boxes need for self-update).
5. **The release workflow does the rest.** `.forgejo/workflows/release.yml` fires on the tag push and runs on the self-hosted runner: `scripts/build-release-tarball.sh` builds the self-update payload (tarball + sha256 + release.json under `dist/`), `iso/build.sh` builds the live-installer ISO, `scripts/publish-release.sh` uploads tarball + sha256 + release.json + ISO to the Forgejo release page. Pre-release is flagged automatically based on the suffix (`-alpha`/`-beta`/`-rc`). The ISO gets a `furtka-<ver>.iso.sha256` sidecar, and a last step rebuilds furtka.org so the download page (`/download/`) picks up the new ISO, size, date and checksum from the Forgejo API. ISO build is `continue-on-error`: a flaky ISO step doesn't block the core tarball (the thing boxes need for self-update).
The release workflow needs one secret set at repo **Settings → Secrets → Actions**:
- `FORGEJO_RELEASE_TOKEN` — a PAT with `write:repository` scope.

View file

@ -113,6 +113,15 @@ if [ -f "$ISO" ]; then
if ! upload_asset "$ISO"; then
echo "warning: ISO upload failed — release published without ISO asset" >&2
fi
# Checksum sidecar for the download page on furtka.org (and anyone
# verifying by hand). Uploaded even when the ISO upload reported a
# failure — a 504 there often still lands the file. Written from inside
# dist/ so the line carries the bare filename `sha256sum -c` expects.
ISO_SHA="$ISO.sha256"
(cd "$DIST_DIR" && sha256sum "$(basename "$ISO")" > "$(basename "$ISO_SHA")")
if ! upload_asset "$ISO_SHA"; then
echo "warning: ISO checksum upload failed" >&2
fi
fi
echo "Release $VERSION published: https://$HOST/$REPO/releases/tag/$VERSION"

View file

@ -356,6 +356,214 @@ main.container {
.cta--primary { animation: none; }
}
.cta-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.9rem 1.1rem;
}
.cta--ghost {
color: var(--fg);
border: 1px solid var(--border);
background: var(--card-bg);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
}
.cta--ghost:hover {
border-color: var(--accent);
color: var(--accent);
transform: translateY(-2px);
}
/* ── How it works (home) ─────────────────────────────────── */
.how-steps {
list-style: none;
counter-reset: how;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
gap: 1rem;
}
.how-step {
counter-increment: how;
position: relative;
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 1rem;
padding: 1.4rem 1.5rem 1.5rem;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
}
.how-step::before {
content: counter(how, decimal-leading-zero);
display: block;
font-family: var(--font-mono);
font-weight: 600;
font-size: 2.4rem;
line-height: 1;
letter-spacing: -0.04em;
margin-bottom: 0.9rem;
background-image: linear-gradient(135deg, var(--accent), var(--fg-muted));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.how-step h3 { margin: 0 0 0.35rem; font-size: 1.15rem; letter-spacing: -0.01em; }
.how-step p { margin: 0; color: var(--fg-muted); font-size: 0.98rem; line-height: 1.5; }
.how-step strong { color: var(--fg); font-weight: 600; }
/* ── Download band (home, before the closer) ─────────────── */
.dl-band {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 1.25rem 2rem;
margin-block: 4.5rem 0;
padding: 2rem;
border-radius: 1.25rem;
border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border));
background:
radial-gradient(120% 140% at 0% 0%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 60%),
var(--card-bg);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
}
.dl-band h2 { margin: 0 0 0.3rem; font-size: 1.5rem; letter-spacing: -0.02em; }
.dl-band p { margin: 0; color: var(--fg-muted); max-width: 26rem; }
/* ── Download page ───────────────────────────────────────── */
.dl-hero { padding-block: 1rem 1rem; }
.dl-hero h1 {
font-size: clamp(2.4rem, 7vw, 4rem);
line-height: 1;
letter-spacing: -0.035em;
margin: 0 0 1rem;
}
.dl-hero .lede {
font-size: clamp(1.05rem, 1.6vw, 1.2rem);
color: var(--fg-muted);
max-width: 36rem;
margin: 0 0 2rem;
}
.dl-card {
padding: 1.75rem;
border-radius: 1.25rem;
border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border));
background:
radial-gradient(120% 140% at 100% 0%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 60%),
var(--card-bg);
}
.dl-card .cta svg { flex: none; }
.dl-meta {
display: flex;
flex-wrap: wrap;
gap: 0.75rem 2rem;
margin: 1.5rem 0 0;
}
.dl-meta div { min-width: 0; }
.dl-meta dt {
font-size: 0.7rem;
font-weight: 500;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--fg-muted);
}
.dl-meta dd { margin: 0.15rem 0 0; overflow-wrap: anywhere; }
.mono { font-family: var(--font-mono); font-size: 0.92em; }
.dl-host { margin: 1.25rem 0 0; font-size: 0.85rem; color: var(--fg-muted); }
.dl-alpha {
margin: 1.5rem 0 0;
padding: 0.9rem 1.1rem;
border-left: 3px solid var(--accent);
background: var(--bg-subtle);
border-radius: 0 0.6rem 0.6rem 0;
font-size: 0.95rem;
max-width: 40rem;
}
.dl-section { margin-top: 3.5rem; }
.dl-needs { margin: 0; padding-left: 1.2rem; max-width: 40rem; }
.dl-needs li { margin-bottom: 0.55rem; }
.dl-needs li::marker { color: var(--accent); }
.dl-steps {
list-style: none;
counter-reset: dl;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 1rem;
}
.step-card {
counter-increment: dl;
display: grid;
grid-template-columns: 2.6rem 1fr;
gap: 0 1rem;
padding: 1.3rem 1.4rem;
border: 1px solid var(--card-border);
border-radius: 1rem;
background: var(--card-bg);
}
.step-card::before {
content: counter(dl);
grid-row: span 2;
display: grid;
place-items: center;
width: 2.6rem;
height: 2.6rem;
border-radius: 999px;
background: linear-gradient(135deg, var(--accent), var(--accent-hover));
color: #fff;
font-weight: 700;
}
.step-card h2 { margin: 0.35rem 0 0.4rem; font-size: 1.15rem; letter-spacing: -0.01em; }
.step-body { min-width: 0; color: var(--fg-muted); }
.step-body p { margin: 0 0 0.7rem; }
.step-body p:last-child { margin: 0; }
.step-body strong { color: var(--fg); font-weight: 600; }
.step-body a, .dl-older a, .dl-alpha a { color: var(--accent); }
kbd {
font-family: var(--font-mono);
font-size: 0.85em;
padding: 0.05rem 0.4rem;
border: 1px solid var(--border);
border-bottom-width: 2px;
border-radius: 0.3rem;
background: var(--bg);
color: var(--fg);
}
.download pre {
margin: 0.4rem 0 0;
padding: 0.85rem 1rem;
border-radius: 0.6rem;
background: var(--bg-subtle);
border: 1px solid var(--border);
overflow-x: auto;
font-size: 0.85rem;
line-height: 1.55;
color: var(--fg);
}
.download code { font-family: var(--font-mono); }
.download :not(pre) > code {
padding: 0.05rem 0.3rem;
border-radius: 0.3rem;
background: var(--bg-subtle);
font-size: 0.9em;
}
.dl-hash code { overflow-wrap: anywhere; white-space: pre-wrap; }
.dl-cmds .c { color: var(--fg-muted); }
.dl-muted { color: var(--fg-muted); }
.dl-older { margin-top: 3rem; }
@media (max-width: 30rem) {
.dl-card, .dl-band { padding: 1.35rem; }
.step-card { grid-template-columns: 1fr; }
.step-card::before { grid-row: auto; margin-bottom: 0.6rem; }
}
/* ── Intro paragraph (home, between hero and feature grids) ─ */
.intro {

View file

@ -1,7 +1,9 @@
---
title: "Furtka"
description: "Offenes Heimserver-Betriebssystem — einfach genug für alle."
status: "<span class=\"mono\">26.20-alpha</span> — in Arbeit"
status_text: "in Arbeit"
cta_download: "ISO herunterladen"
cta_source: "Quellcode"
poll_link: "/de/logo/"
poll_text: "Wir suchen ein Logo — hilf uns beim Aussuchen"
# features_today / features_next müssen index-parallel zu content/_index.md bleiben.
@ -12,6 +14,16 @@ intro: |
Apps, eigenem Namen im Netz, eigenen Daten.
Das Ziel ist einfach: **dein Vater soll das einrichten können.**
steps_label: "So funktioniert's"
steps:
- title: "Herunterladen"
body: "Installer holen und auf einen USB-Stick schreiben."
- title: "Installieren"
body: "Computer vom Stick starten und dem Assistenten folgen — im Browser auf Handy oder Laptop."
- title: "Loslegen"
body: "**http://furtka.local** öffnen und Apps mit einem Klick installieren."
band_title: "Lust, es auszuprobieren?"
band_text: "Kostenlos und quelloffen (AGPL-3.0). Läuft auf 64-Bit-PCs, auch auf alten."
features_today_label: "Was heute schon geht"
features_today:
- "Vom USB-Stick booten und Furtka auf die Festplatte einrichten"

View file

@ -1,7 +1,9 @@
---
title: "Furtka"
description: "Open-source home server OS — simple enough for everyone."
status: "<span class=\"mono\">26.20-alpha</span> — work in progress"
status_text: "work in progress"
cta_download: "Download ISO"
cta_source: "Source code"
poll_link: "/logo/"
poll_text: "We're picking a logo — help us choose"
# Keep features_today / features_next index-aligned with content/_index.de.md.
@ -12,6 +14,16 @@ intro: |
your own name on the network, your own data.
The goal is simple: **your dad should be able to set this up.**
steps_label: "How it works"
steps:
- title: "Download"
body: "Get the installer and write it to a USB stick."
- title: "Install"
body: "Start the computer from the stick and follow the wizard — in the browser on your phone or laptop."
- title: "Use"
body: "Open **http://furtka.local** and install apps with one click."
band_title: "Ready to try it?"
band_text: "Free and open source (AGPL-3.0). Runs on 64-bit PCs, old ones included."
features_today_label: "What works today"
features_today:
- "Boot from USB stick and install Furtka onto the hard drive"

View file

@ -0,0 +1,40 @@
---
title: "Furtka herunterladen"
description: "Der Furtka-Installer als ISO-Datei: auf einen USB-Stick schreiben, den Computer davon starten und den Heimserver im Browser einrichten."
translationKey: "download"
url: /de/download/
type: download
button: "ISO herunterladen"
released: "Veröffentlicht"
size_label: "Größe"
alpha: "Furtka ist **Alpha-Software**: Dinge ändern sich und manches geht kaputt. Installiere es auf einem Computer, dessen Festplatte du nicht brauchst, und sichere alles Wichtige."
host_note: "Die Datei kommt von unserem Forgejo-Server (forgejo.sourcegate.online)."
need_label: "Was du brauchst"
needs:
- "Einen **64-Bit-PC** (x86-64) — ein alter Laptop oder Desktop reicht. UEFI- und ältere BIOS-Rechner funktionieren beide."
- "Eine Festplatte mit **mindestens 16 GB** für Furtka. **Sie wird komplett gelöscht.**"
- "Einen **USB-Stick** mit mindestens 2 GB (wird ebenfalls gelöscht)."
- "Ein **Netzwerkkabel** mit Internetzugang — der Installer lädt Pakete herunter, und WLAN wird nicht automatisch eingerichtet."
- "Ein **zweites Gerät mit Browser** (Handy oder Laptop) im selben Netzwerk."
steps_label: "So installierst du Furtka"
steps:
- title: "ISO auf einen USB-Stick schreiben"
body: |
Nimm [balenaEtcher](https://etcher.balena.io/) (Windows, macOS, Linux) oder [Rufus](https://rufus.ie/) unter Windows — wenn Rufus fragt, wähle den **DD-Abbild-Modus**. Unter Linux geht auch `dd` (ersetze `/dev/sdX` durch deinen Stick — lieber zweimal prüfen):
```
sudo dd if={iso} of=/dev/sdX bs=4M status=progress oflag=sync
```
- title: "Computer vom Stick starten"
body: "Stick und Netzwerkkabel einstecken, Computer einschalten und die Taste fürs Bootmenü drücken (oft <kbd>F11</kbd>, <kbd>F12</kbd> oder <kbd>Esc</kbd> — sie wird kurz am Bildschirm angezeigt). Den USB-Stick auswählen. Startet der Computer nicht davon, schalte **Secure Boot** in den Firmware-Einstellungen aus."
- title: "Installer von einem anderen Gerät öffnen"
body: "Nach kurzer Zeit zeigt der Bildschirm eine Adresse an — meist **http://proksi.local:5000**, dazu die IP-Adresse als Ausweichweg. Öffne sie im Browser auf Handy oder Laptop."
- title: "Durch den Assistenten klicken"
body: "Drei Seiten: ein Name für deinen Server mit Benutzername und Passwort, die Festplatte und eine letzte Kontrolle. Danach lädt der Installer alles herunter und richtet es ein."
- title: "Stick raus, neu starten, fertig"
body: "Wenn der Assistent es sagt, den USB-Stick abziehen und neu starten. Dein Server ist dann unter **http://&lt;name&gt;.local** erreichbar — zum Beispiel http://furtka.local."
verify_label: "Download prüfen (optional)"
verify_intro: "Vergleiche die Prüfsumme der heruntergeladenen Datei mit dieser:"
verify_missing: "Für die ISO dieses Releases ist noch keine Prüfsumme veröffentlicht — künftige Releases bringen eine mit."
older: "Alle Releases und Änderungen"
---

View file

@ -0,0 +1,40 @@
---
title: "Download Furtka"
description: "The Furtka installer as an ISO file: write it to a USB stick, start your computer from it, and set up your home server in the browser."
translationKey: "download"
url: /download/
type: download
button: "Download ISO"
released: "Released"
size_label: "Size"
alpha: "Furtka is **alpha software**: things change and some will break. Install it on a computer whose disk you don't need, and keep a backup of anything important."
host_note: "The file is served from our Forgejo server (forgejo.sourcegate.online)."
need_label: "What you need"
needs:
- "A **64-bit PC** (x86-64) — an old laptop or desktop is fine. Both UEFI and older BIOS computers work."
- "A disk of **at least 16 GB** for Furtka. **It will be erased completely.**"
- "A **USB stick** with at least 2 GB (also erased)."
- "A **network cable** with internet access — the installer downloads packages, and Wi-Fi isn't set up automatically."
- "A **second device with a browser** (phone or laptop) on the same network."
steps_label: "How to install"
steps:
- title: "Write the ISO to a USB stick"
body: |
Use [balenaEtcher](https://etcher.balena.io/) (Windows, macOS, Linux) or [Rufus](https://rufus.ie/) on Windows — if Rufus asks, pick **DD image mode**. On Linux you can also use `dd` (replace `/dev/sdX` with your stick — double-check it):
```
sudo dd if={iso} of=/dev/sdX bs=4M status=progress oflag=sync
```
- title: "Start the computer from the stick"
body: "Plug in the stick and the network cable, switch the computer on and press the one-time boot menu key (often <kbd>F11</kbd>, <kbd>F12</kbd> or <kbd>Esc</kbd> — it flashes briefly on screen). Pick the USB stick. If the computer refuses to start from it, switch off **Secure Boot** in the firmware settings."
- title: "Open the installer from another device"
body: "After a short while the screen shows an address — usually **http://proksi.local:5000**, plus the IP address as a fallback. Open it in a browser on your phone or laptop."
- title: "Click through the wizard"
body: "Three pages: a name for your server with your username and password, the disk to install on, and a last check. Then the installer downloads and sets everything up."
- title: "Pull the stick, restart, done"
body: "When the wizard says so, remove the USB stick and restart. Your server comes up at **http://&lt;name&gt;.local** — for example http://furtka.local."
verify_label: "Check the download (optional)"
verify_intro: "Compare the checksum of the file you downloaded with this one:"
verify_missing: "This release has no published checksum for the ISO yet — future releases include one."
older: "All releases and changelogs"
---

View file

@ -8,6 +8,11 @@ enableRobotsTXT = true
description = "Open-source home server OS — simple enough for everyone."
version = "26.20-alpha"
contactEmail = "hallo@furtka.org"
# Forgejo repo the download page reads its latest release (ISO asset,
# size, date, checksum) from at build time. `version` above is only the
# fallback when that lookup fails.
forgejoURL = "https://forgejo.sourcegate.online"
repoPath = "daniel/furtka"
[markup.goldmark.renderer]
unsafe = true
@ -32,5 +37,12 @@ enableRobotsTXT = true
[build]
writeStats = true
# The release lookup must be fresh on every build — Hugo's default keeps
# remote resources cached forever, which would pin the download page to
# whatever release existed on the first build.
[caches]
[caches.getresource]
maxAge = 0
[minify]
minifyOutput = true

View file

@ -0,0 +1,63 @@
{{ define "main" }}
{{- $r := partialCached "release.html" . "release" -}}
{{- $file := path.Base $r.url -}}
<article class="download">
<header class="dl-hero">
<p class="status-chip"><span class="mono">{{ $r.version }}</span></p>
<h1>{{ .Title }}</h1>
<p class="lede">{{ .Description }}</p>
<div class="dl-card">
<a class="cta cta--primary" href="{{ $r.url }}" download>
<svg aria-hidden="true" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="m7 10 5 5 5-5"/><path d="M5 21h14"/></svg>
{{ .Params.button }}
</a>
<dl class="dl-meta">
<div><dt>ISO</dt><dd class="mono">{{ $file }}</dd></div>
{{ with $r.size }}<div><dt>{{ $.Params.size_label }}</dt><dd>{{ lang.FormatNumber 1 (div (float .) 1e9) }} GB</dd></div>{{ end }}
{{ with $r.date }}<div><dt>{{ $.Params.released }}</dt><dd>{{ time.Format ":date_long" . }}</dd></div>{{ end }}
</dl>
<p class="dl-host">{{ .Params.host_note }}</p>
</div>
<p class="dl-alpha">{{ .Params.alpha | markdownify }}</p>
</header>
<section class="dl-section">
<p class="section-eyebrow">{{ .Params.need_label }}</p>
<ul class="dl-needs">
{{ range .Params.needs }}<li>{{ . | markdownify }}</li>{{ end }}
</ul>
</section>
<section class="dl-section">
<p class="section-eyebrow">{{ .Params.steps_label }}</p>
<ol class="dl-steps">
{{ range .Params.steps }}
<li class="step-card">
<h2>{{ .title }}</h2>
<div class="step-body">{{ replace .body "{iso}" $file | markdownify }}</div>
</li>
{{ end }}
</ol>
</section>
<section class="dl-section">
<p class="section-eyebrow">{{ .Params.verify_label }}</p>
{{ with $r.sha256 }}
<p>{{ $.Params.verify_intro }}</p>
<pre class="dl-hash"><code>{{ . }}</code></pre>
<pre class="dl-cmds"><code><span class="c"># Linux</span>
sha256sum {{ $file }}
<span class="c"># macOS</span>
shasum -a 256 {{ $file }}
<span class="c"># Windows (PowerShell)</span>
Get-FileHash {{ $file }}</code></pre>
{{ else }}
<p class="dl-muted">{{ $.Params.verify_missing }}</p>
{{ end }}
</section>
<p class="dl-older"><a href="{{ $r.all }}">{{ .Params.older }} →</a></p>
</article>
{{ end }}

View file

@ -1,16 +1,17 @@
{{ define "main" }}
{{- $r := partialCached "release.html" . "release" -}}
{{- $dl := "download/" | relLangURL -}}
<article class="home">
<header class="hero">
{{ with .Params.status }}
<p class="status-chip reveal">{{ . | safeHTML }}</p>
{{ end }}
<p class="status-chip reveal"><span class="mono">{{ $r.version }}</span> — {{ .Params.status_text }}</p>
<h1 class="reveal">{{ .Title }}</h1>
{{ with site.Params.description }}<p class="lede reveal">{{ . }}</p>{{ end }}
<p class="cta-row reveal">
<a class="cta cta--primary" href="https://forgejo.sourcegate.online/daniel/furtka/releases">
{{ if eq site.Language.Lang "de" }}Neuestes Release{{ else }}Latest release{{ end }}
<a class="cta cta--primary" href="{{ $dl }}">
{{ .Params.cta_download }}
<span aria-hidden="true">→</span>
</a>
<a class="cta cta--ghost" href="{{ site.Params.forgejoURL }}/{{ site.Params.repoPath }}">{{ .Params.cta_source }}</a>
</p>
{{ with .Params.poll_link }}
<p class="reveal"><a class="home-poll" href="{{ . }}">{{ partial "mark-m.html" (dict "class" "" "id" "hm") }}{{ partial "mark-r.html" (dict "class" "" "id" "hr") }}<span>{{ $.Params.poll_text }} →</span></a></p>
@ -21,6 +22,20 @@
<section class="intro">{{ . | markdownify }}</section>
{{ end }}
{{ with .Params.steps }}
<section class="feature-section">
{{ with $.Params.steps_label }}<p class="section-eyebrow">{{ . }}</p>{{ end }}
<ol class="how-steps">
{{ range . }}
<li class="how-step" data-gsap="card">
<h3>{{ .title }}</h3>
<p>{{ .body | markdownify }}</p>
</li>
{{ end }}
</ol>
</section>
{{ end }}
{{ if .Params.features_today }}
<section class="feature-section">
{{ with .Params.features_today_label }}<p class="section-eyebrow">{{ . }}</p>{{ end }}
@ -43,6 +58,16 @@
</section>
{{ end }}
{{ with .Params.band_title }}
<section class="dl-band" data-gsap="card">
<div>
<h2>{{ . }}</h2>
<p>{{ $.Params.band_text }}</p>
</div>
<a class="cta cta--primary" href="{{ $dl }}">{{ $.Params.cta_download }} <span aria-hidden="true">→</span></a>
</section>
{{ end }}
{{ with .Content }}
<section class="prose closer">{{ . }}</section>
{{ end }}

View file

@ -0,0 +1,57 @@
{{- /*
Latest Furtka release that has an ISO attached, looked up on Forgejo at
build time. Returns a dict: version, url (ISO), size (bytes, 0 = unknown),
date (time or ""), sha256 ("" when the release has no .iso.sha256), page
(release page URL), live (false when the lookup failed and we fell back
to site.Params.version). Use via partialCached "release.html" . "release".
*/ -}}
{{- $repo := printf "%s/%s" site.Params.forgejoURL site.Params.repoPath -}}
{{- $v := site.Params.version -}}
{{- $out := dict
"version" $v
"url" (printf "%s/releases/download/%s/furtka-%s.iso" $repo $v $v)
"size" 0
"date" ""
"sha256" ""
"page" (printf "%s/releases/tag/%s" $repo $v)
"all" (printf "%s/releases" $repo)
"live" false
-}}
{{- $api := printf "%s/api/v1/repos/%s/releases?limit=10" site.Params.forgejoURL site.Params.repoPath -}}
{{- with try (resources.GetRemote $api) -}}
{{- with .Err -}}
{{- warnf "release.html: release lookup failed, using site.Params.version: %s" . -}}
{{- else with .Value -}}
{{- $done := false -}}
{{- range (. | transform.Unmarshal) -}}
{{- if and (not $done) (not .draft) -}}
{{- $rel := . -}}
{{- $iso := "" -}}
{{- $sum := "" -}}
{{- range .assets -}}
{{- if strings.HasSuffix .name ".iso" }}{{ $iso = . }}{{ end -}}
{{- if strings.HasSuffix .name ".iso.sha256" }}{{ $sum = . }}{{ end -}}
{{- end -}}
{{- with $iso -}}
{{- $done = true -}}
{{- $out = merge $out (dict
"version" $rel.tag_name
"url" .browser_download_url
"size" .size
"date" (time.AsTime $rel.published_at)
"page" $rel.html_url
"live" true) -}}
{{- with $sum -}}
{{- with try (resources.GetRemote .browser_download_url) -}}
{{- with .Value -}}
{{- $hash := index (split (trim .Content " \n") " ") 0 -}}
{{- if eq (len $hash) 64 }}{{ $out = merge $out (dict "sha256" $hash) }}{{ end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- return $out -}}