diff --git a/.github/workflows/redeploy.yaml b/.github/workflows/redeploy.yaml index faa8882..8740976 100644 --- a/.github/workflows/redeploy.yaml +++ b/.github/workflows/redeploy.yaml @@ -10,7 +10,7 @@ on: jobs: redeploy: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - env: DEPLOY_HOOK: ${{ secrets.DEPLOY_HOOK }} diff --git a/config.yaml b/config.yaml index 169877f..7a39ae8 100644 --- a/config.yaml +++ b/config.yaml @@ -4,10 +4,9 @@ languageCode: "en-us" theme: PaperMod -author: - name: Julian Tölle - params: + author: Julian Tölle + DateFormat: "2006-01-02" keywords: ["cv", "resume", "julian tölle", "personal site"] @@ -28,8 +27,6 @@ params: buttons: - name: "About Me" url: "about-me" - - name: "Favorite Music" - url: listory socialIcons: - name: github @@ -43,17 +40,25 @@ params: - name: mastodon url: "https://hachyderm.io/@apricote" + assets: + # Use hugo native highlighter for Nord theme + disableHLJS: true + menu: main: - identifier: about name: About Me url: about-me + - identifier: posts + name: Blog + url: posts - identifier: work name: Work url: work - identifier: projects name: Projects url: projects - - identifier: listory - name: Favorite Music - url: listory + +markup: + highlight: + style: nord diff --git a/content/listory.md b/content/listory.md deleted file mode 100644 index b9d4cb3..0000000 --- a/content/listory.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: "My Favorite Music" -type: listory ---- - -Last Update: {{< current-day >}} - -The data on this page reflects my listens from Spotify. I track the data using a -self-hosted instance of [Listory](https://github.com/apricote/listory). All data -is coming from the listens of the 180 days prior to the last update time. - -## My favorite genres - -{{< listory-genres >}} - -## My favorite albums - -{{< listory-albums >}} diff --git a/content/posts/hcloud-upload-image-v1/index.md b/content/posts/hcloud-upload-image-v1/index.md new file mode 100644 index 0000000..0901ac6 --- /dev/null +++ b/content/posts/hcloud-upload-image-v1/index.md @@ -0,0 +1,97 @@ +--- +title: "hcloud-upload-image - qcow2 Support, Smaller Snapshots" +date: 2025-05-10T15:28:01+02:00 + +summary: | + New releases of hcloud-upload-image bring major upgrades: support for uploading qcow2 images and significantly smaller snapshot sizes. Plus, container image and docs site! + +ShowToc: true +ShowReadingTime: true +ShowBreadCrumbs: true +--- + +I released versions v1.0.0 and v1.1.0 of [**hcloud-upload-image**](https://github.com/apricote/hcloud-upload-image) in the past week, and they come with two major new features: + +* ✅ Support for uploading images in **qcow2 format** +* ✅ **Smaller snapshot sizes** thanks to improved disk handling + +## 🎉 qcow2 Image Support + +You can now upload disk images in qcow2 format. Internally, I’m using `qemu-img dd` to convert the qcow2 to a raw image and stream it directly to the root disk. + +The main catch is that qemu-img needs a seekable source file. For raw images, that wasn’t an issue because they could just be piped. But for qcow2, I now have to store the image file on the server before writing it to disk. + +The rescue system only has a ramdisk with 960 MiB available by default, so that’s the maximum size for qcow2 images at the moment. The CLI and library try to validate and warn you if your image is too large. + +It would be feasible to increase the size of the ramdisk or mount a volume. But most users have requested this feature for OpenSUSE MicroOS images, which comfortably fit under 960 MiB. + +📌 [Pull Request #69](https://github.com/apricote/hcloud-upload-image/pull/69) +📘 [CLI Reference: Image Size](https://apricote.github.io/hcloud-upload-image/reference/cli/hcloud-upload-image_upload.html#image-size) + +## 📦 Smaller Minimal Snapshot Sizes + +To upload the image, hcloud-upload-image first creates a temporary server in Hetzner Cloud with `ubuntu-24.04`. While Ubuntu is never booted, the root disk still contains all the bytes from the image. + +Previously, the upload would just write the image to disk, leaving behind whatever bytes weren’t overwritten. This is not a problem for the functionality because these bytes are outside the specified ranges in the partition table. + +But Hetzner snapshots don’t look at partition tables — they snapshot the entire raw disk, including any leftover bytes from a previous OS installation. Because of this, the minimum (compressed) size of the uploaded images was ~0.42GiB. + +[Zargony](https://github.com/zargony) suggested zeroing the disk first with `dd if=/dev/zero` first to get rid of these bytes and make the images as small as possible. That worked — but took a full minute for the default 40GiB root disk. Not great UX, and pretty annoying if your image is larger anyway, and you do not even benefit from it. + +Luckily I found `blkdiscard /dev/sda` to have the same effect on the image size, but completes in ~5 seconds. I think it's reasonable to make the process 5 seconds slower for everyone to avoid the additional complexity and heuristics to decide if its worth it to zero out the disk first. + +With these changes, the (compressed) size of an example Talos x86 image was reduced from 0.42GiB to 0.20GiB. + +### Benefits: + +1. **Smaller image size** → slightly lower Hetzner bills. For Talos, that’s about €0.0024/month (excl. VAT) — not life-changing, but hey. + +2. **Faster provisioning**. When you create a new server from a custom image, the image first needs to be copied from the image storage onto the server hosting your VM. In my testing, I found that it takes roughly 2 minutes per GiB. With the smaller image, Talos servers come online ~25 seconds faster. + +## Minor Features + +### 📚 Docs Website + +There’s now a documentation website with the CLI references and links to the official guides from the Talos, Fedora CoreOS, and Flatcar docs: + +👉 [apricote.github.io/hcloud-upload-image](https://apricote.github.io/hcloud-upload-image/) + +### 🐳 Container Image + +There’s now a **container image** published at `ghcr.io/apricote/hcloud-upload-image`, great for local use or CI pipelines: + +```bash +docker run --rm -e HCLOUD_TOKEN="" \ + ghcr.io/apricote/hcloud-upload-image:latest \ + upload +``` + +Thanks to [PRIHLOP](https://github.com/PRIHLOP) for suggesting the feature and implementing the first version. + +This is implemented with [ko](https://ko.build). I really like how simple it is to build a container for Go with it. No need to have a Docker daemon installed; no need to wrangle `Dockerfiles`, cache mounts or multiple stages to have a small and efficient image; no need for complicated cross-compiling setups, buildx and qemu for multi-arch images. + +## 🧪 Testing, Bugs & Shell Pipelines + +There were also a number of bugs that came with the qcow2 support and some other smaller improvements made in v1.0.0. + +That is one of the downsides of not having proper testing. As I work on this on my own time and mostly for my own purposes, testing is limited to what I need for myself. + +I did end up at least [adding some unit tests](https://github.com/apricote/hcloud-upload-image/pull/98/files#diff-b0d3df197c7b8434f621a9b55218097dcaadb57ba673d32f9fad068bb79a22a4R17) for the code that builds the shell pipeline that decompresses and writes the image on the server. + +Concatenating shell commands like `wget ... |`, `xz -cd | `, `dd of=/dev/sda bs=4M` is error-prone. Especially if every single one of them depends on some input flag to support: + +- **file sources**: remote URL vs upload from local file +- **compression**: none, xz, bz2 +- **image format**: raw, qcow2 + +Not sure what the best alternative to this is. I could make a shell script and use `mkfifo` to pass around the data, upload that to the server, and run it. But I have never used mkfifo before, and shell scripts frustrate me. + +Or I could implement all of this in a separate Go binary that is uploaded to the server, making it easier to handle the pipes there. But I would need to figure out the build process and embedding this binary in the library. + +Let's just hope that the current version is bug-free, and all users are happy with the provided feature set, and I never have to touch that part of the code again 🥲 + +--- + +Thanks to everyone who's tried out **hcloud-upload-image** so far and provided feedback. + +If you have any comments or want to leave some feedback, feel free to drop them on the [Mastodon thread](https://hachyderm.io/@apricote/114483565536111800) or on the [GitHub repository](https://github.com/apricote/hcloud-upload-image/issues/new). diff --git a/data/resume.yaml b/data/resume.yaml index e2ef439..761e12d 100644 --- a/data/resume.yaml +++ b/data/resume.yaml @@ -1,7 +1,7 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/jsonresume/resume-schema/v1.0.0/schema.json basics: name: Julian Tölle - label: Infrastructure & Backend + label: Kubernetes & Cloud Infrastructure image: https://www.gravatar.com/avatar/bd55c8a01a6dd038f86c8e9fd60674b5?s=800 email: julian.toelle97@gmail.com location: @@ -29,11 +29,8 @@ skills: - Operators - GitOps - - name: Node.js - level: expert - keywords: - - Typescript - - Nest.js + - name: Go + level: advanced - name: CI/CD level: advanced @@ -42,6 +39,13 @@ skills: - GitLab CI - Jenkins + - name: Cloud + level: advanced + keywords: + - Hetzner Cloud + - AWS + - OpenStack + - name: Infrastructure as Code level: advanced keywords: @@ -53,12 +57,11 @@ skills: keywords: - REST APIs - - name: Cloud + - name: Node.js level: advanced keywords: - - Hetzner Cloud - - AWS - - OpenStack + - Typescript + - Nest.js - name: Databases level: advanced @@ -68,14 +71,9 @@ skills: - MySQL - etcd - - name: Go - level: intermediate - keywords: - - kubebuilder - work: - name: Hetzner Cloud GmbH - position: Software Developer Open Source Integrations + position: Team Lead Open Source Integrations website: https://hetzner.com/cloud startDate: "2022-11" keywords: @@ -315,18 +313,23 @@ education: - LK Physik projects: - - name: Listory - summary: | - Self-hosted app to track all your Spotify listens and show interesting - insights that I develop to fill my own needs. - startDate: 2020-01 - url: https://github.com/apricote/Listory + - name: releaser-pleaser + summary: + Automate versioning and changelog management for GitHub and GitLab projects. + startDate: 2024-07 + url: https://apricote.github.io/releaser-pleaser/ type: application keywords: - - Node.js - - Nest.js - - Helm - - React + - Go + + - name: hcloud-upload-image + summary: + CLI and Go Library to upload OS images to Hetzner Cloud. + startDate: 2024-04 + url: https://apricote.github.io/hcloud-upload-image/ + type: application + keywords: + - Go - name: Streaming on Twitch summary: | @@ -342,6 +345,21 @@ projects: - AV production - Public Talking + + - name: Listory + summary: | + Self-hosted app to track all your Spotify listens and show interesting + insights that I develop to fill my own needs. + startDate: 2020-01 + endDate: 2023-10 + url: https://github.com/apricote/Listory + type: application + keywords: + - Node.js + - Nest.js + - Helm + - React + - name: cluster-api-provider-openstack summary: | I was a reviewer of the project and actively contributed new features. diff --git a/layouts/partials/extend_head.html b/layouts/partials/extend_head.html deleted file mode 100644 index 577e910..0000000 --- a/layouts/partials/extend_head.html +++ /dev/null @@ -1,43 +0,0 @@ - diff --git a/layouts/shortcodes/current-day.html b/layouts/shortcodes/current-day.html deleted file mode 100644 index 739e1f8..0000000 --- a/layouts/shortcodes/current-day.html +++ /dev/null @@ -1,2 +0,0 @@ - -{{ now.Format "2006-01-02T15:04:05Z" }} diff --git a/layouts/shortcodes/listory-albums.html b/layouts/shortcodes/listory-albums.html deleted file mode 100644 index db12896..0000000 --- a/layouts/shortcodes/listory-albums.html +++ /dev/null @@ -1,33 +0,0 @@ -{{ $listoryApi := getenv "HUGO_LISTORY_HOST" }}{{ $listoryToken := getenv -"HUGO_LISTORY_TOKEN" }} {{ $res := getJSON -"https://listory.apricote.de/api/v1/reports/top-albums?timePreset=last_180_days" -(dict "Authorization" (printf "Bearer %s" $listoryToken) ) }} - -
    - {{ range first 5 $res.items }} -
  1. -
    -
    - {{ .album.name | title }} - {{ range .album.artists }}{{.name}}{{ end }} -
    -
    - {{ .count }}Listens -
    -
    -
    -
  2. - {{ end }} -
diff --git a/layouts/shortcodes/listory-genres.html b/layouts/shortcodes/listory-genres.html deleted file mode 100644 index 44ede1d..0000000 --- a/layouts/shortcodes/listory-genres.html +++ /dev/null @@ -1,32 +0,0 @@ -{{ $listoryApi := getenv "HUGO_LISTORY_HOST" }}{{ $listoryToken := getenv -"HUGO_LISTORY_TOKEN" }} {{ $res := getJSON -"https://listory.apricote.de/api/v1/reports/top-genres?timePreset=last_180_days" -(dict "Authorization" (printf "Bearer %s" $listoryToken) ) }} - -
    - {{ range first 5 $res.items }} -
  1. -
    -
    {{ .genre.name | title }}
    -
    - {{ .count }}Listens -
    -
    -
    - {{ range first 3 .artists }}{{.artist.name}} {{ - end }} -
    -
  2. - {{ end }} -
diff --git a/static/listen.svg b/static/listen.svg deleted file mode 100644 index 821b748..0000000 --- a/static/listen.svg +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/themes/PaperMod b/themes/PaperMod index 60984fd..7cf752f 160000 --- a/themes/PaperMod +++ b/themes/PaperMod @@ -1 +1 @@ -Subproject commit 60984fd13658db175e8945467d3dd799f2fdcc32 +Subproject commit 7cf752f8644fea1fc3dc7299352718d492c55182 diff --git a/vercel.json b/vercel.json index c3e0014..1395dee 100644 --- a/vercel.json +++ b/vercel.json @@ -1,7 +1,7 @@ { "build": { "env": { - "HUGO_VERSION": "0.121.1" + "HUGO_VERSION": "0.147.1" } }, "headers": [