No description
  • Astro 46.1%
  • TypeScript 32.6%
  • CSS 10%
  • Shell 5.8%
  • JavaScript 5.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
webmatix 4ef351d846
All checks were successful
ci/woodpecker/push/check Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
add more services
2026-08-17 01:30:29 +02:00
.woodpecker tests 2026-08-14 04:10:58 +02:00
ci prepare ci 2026-08-14 04:01:24 +02:00
public add more services 2026-08-17 01:30:29 +02:00
src add more services 2026-08-17 01:30:29 +02:00
.gitignore init astrco from template BracoZS/astro-starter-portfolio 2026-08-13 08:11:47 +02:00
.prettierignore prepare ci 2026-08-14 04:01:24 +02:00
.prettierrc.json init astrco from template BracoZS/astro-starter-portfolio 2026-08-13 08:11:47 +02:00
AGENTS.md init astrco from template BracoZS/astro-starter-portfolio 2026-08-13 08:11:47 +02:00
astro.config.mjs prepare ci 2026-08-14 04:01:24 +02:00
CLAUDE.md init astrco from template BracoZS/astro-starter-portfolio 2026-08-13 08:11:47 +02:00
LICENSE init astrco from template BracoZS/astro-starter-portfolio 2026-08-13 08:11:47 +02:00
package-lock.json init astrco from template BracoZS/astro-starter-portfolio 2026-08-13 08:11:47 +02:00
package.json mvp 2026-08-14 02:35:13 +02:00
pnpm-lock.yaml init astrco from template BracoZS/astro-starter-portfolio 2026-08-13 08:11:47 +02:00
pnpm-workspace.yaml init astrco from template BracoZS/astro-starter-portfolio 2026-08-13 08:11:47 +02:00
README.md ci badges 2026-08-14 04:18:45 +02:00
skills-lock.json init astrco from template BracoZS/astro-starter-portfolio 2026-08-13 08:11:47 +02:00
tsconfig.json prepare ci 2026-08-14 04:01:24 +02:00

webhofer.dev

status-badgestatus-badgestatus-badge

Personal site of Matthias Webhofer. Astro, static output, no client-side framework.

Currently a single landing page: name, role, one line of context, and a link to git.webmatix.dev.

Development

npm install
npm run dev

Serves on http://localhost:4321.

Command Action
npm run dev Local dev server
npm run build Type-check, then build to ./dist/
npm run preview Serve the production build locally
npm run check astro check only
npm run format Prettier

Structure

.woodpecker/
  check.yaml            prettier gate, every branch
  deploy.yaml           build + deploy, one bucket per branch
ci/
  deploy.sh             rclone upload + prune, takes the bucket as its argument
public/                 favicons, og-image.png
src/
  components/           BaseHead, Button, Footer, ThemeToggle
  layouts/
    BaseLayout.astro    <head>, SEO, fonts, theme script
  pages/
    index.astro         the landing page
    404.astro
    robots.txt.ts       robots.txt, disallow-all off production
  styles/
    global.css          design tokens + Tailwind import
  site.config.ts        name, role, context line, git URL, production host
astro.config.mjs        site URL, sitemap, fonts

Copy lives in src/site.config.ts. Colours are the six custom properties at the top of src/styles/global.css, in a light and a dark block. Everything reads from those tokens, nothing hardcodes a hex value.

Branches (jj bookmarks)

  • main: live state, deploys to webhofer.dev
  • dev: work in progress, deploys to dev.webhofer.dev
  • template: unmodified base from BracoZS/astro-starter-portfolio, kept for reference

template still holds the full template: work content collection, about page, header nav, project pages. Those were removed on main and can be picked back from template when needed.

Deployment

Woodpecker CI, two workflows in .woodpecker/:

Workflow Runs on Does
check.yaml every branch, every push/PR npm run format:check
deploy.yaml push to main or dev npm run build, then rclone the dist/

Targets are Garage buckets:

Branch Bucket Site
main portfolio-website webhofer.dev
dev portfolio-website-dev dev.webhofer.dev

No other branch deploys.

Setup

Two repository secrets in Woodpecker: garage_access_key and garage_secret_key. The endpoint and region are literal in .woodpecker/deploy.yaml and marked # EDIT. The region must match Garage's s3_region.

Per bucket, in Garage:

garage bucket create portfolio-website
garage bucket allow --read --write --key <key-id> portfolio-website
garage bucket website --allow portfolio-website

Read and write for the CI key, plus the bucket-level website grant for anonymous read. Garage does not implement object ACLs, so nothing is set per object.

How the upload works

ci/deploy.sh <bucket>, called once per branch from deploy.yaml. It is plain rclone against a remote named garage, so it also runs by hand against a local rclone.conf:

npm run build && ./ci/deploy.sh portfolio-website-dev

rclone sync deletes by default, so files dropped from dist/ do not accumulate in the bucket. dist/ is therefore the complete truth for the bucket: anything put there by hand is removed on the next deploy.

It runs in two passes because one rclone invocation can only set one Cache-Control. Pass one copies the content-hashed _astro/ assets with max-age=31536000, immutable, pass two syncs everything else with a short TTL and does the deleting. --checksum makes pass two skip the already-uploaded assets, so they keep their long header. A test -f dist/index.html guard stops a broken build from emptying the bucket.

The build needs egress to the npm registry, Docker Hub, and fonts.googleapis.com / fonts.gstatic.com (Astro's Fonts API self-hosts the fonts at build time).

Environments

SITE_URL overrides the site origin, which feeds the canonical, Open Graph and sitemap URLs. It defaults to https://webhofer.dev, so a plain npm run build is a production build. Anything other than the production host also gets noindex, nofollow and a disallow-all robots.txt, so the preview never competes with the live site.

SITE_URL=https://dev.webhofer.dev npm run build

The production host is PRODUCTION_HOST in src/site.config.ts, mirrored as PRODUCTION_URL in astro.config.mjs. Both need changing together.

Known gaps

  • public/og-image.png and the favicons are still template artwork.
  • pnpm-lock.yaml and pnpm-workspace.yaml are stale template leftovers. The pipeline uses npm ci, so they are inert, but they should go.

License

MIT, see LICENSE. Template by BracoZS, same licence.