commit 80c820bdff59410f47b519df14ac217c21f56e5f Author: Asger Geel Weirsøe Date: Thu Jun 11 19:13:47 2026 +0200 site: full Astro source + blog posts, Gitea Actions deploy to CT148 Replaces the placeholder repo with the asger.weirsoe.dk site source. Adds 6 blog posts (Proxmox, Authentik/OIDC, NPMplus migration, JS injection, ah-fail) and .gitea/workflows/deploy.yml (bun build + rsync over SSH). Co-Authored-By: Claude Opus 4.8 (1M context) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..46a51f5 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,38 @@ +name: Build & Deploy + +on: + push: + branches: [master, main] + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Build site + run: bun run build + + - name: Deploy dist/ to CT148 over SSH + env: + DEPLOY_HOST: 192.168.10.148 + DEPLOY_USER: root + DEPLOY_PATH: /var/www/asger + run: | + set -euo pipefail + mkdir -p ~/.ssh + echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -t ed25519 "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null || true + rsync -az --delete \ + -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new" \ + dist/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02da9ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.astro/ +.DS_Store +bun.lock* diff --git a/README.md b/README.md new file mode 100644 index 0000000..400839a --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# asger.weirsoe.dk + +Personlig side (CV + blog) bygget med Astro + Bun + Tailwind. Statisk output, hostet på Proxmox (CT148, nginx), deployet via Gitea Actions. + +## Udvikling +``` +bun install +bun run dev # http://localhost:4321 +bun run build # -> dist/ +``` + +## Nyt blogindlæg +Læg en `.md`-fil i `src/content/blog/` med frontmatter (title, date, tags, description, draft). Push til main → bygges og deployes automatisk. diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 0000000..82f606c --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,20 @@ +import { defineConfig } from 'astro/config'; +import sitemap from '@astrojs/sitemap'; +import tailwindcss from '@tailwindcss/vite'; + +export default defineConfig({ + site: 'https://asger.xn--weirse-fya.dk', + trailingSlash: 'ignore', + integrations: [ + sitemap({ + changefreq: 'weekly', + priority: 0.7, + serialize(item) { + if (item.url === 'https://asger.xn--weirse-fya.dk/') item.priority = 1.0; + return item; + }, + }), + ], + prefetch: { prefetchAll: true, defaultStrategy: 'viewport' }, + vite: { plugins: [tailwindcss()] }, +}); diff --git a/og.svg b/og.svg new file mode 100644 index 0000000..dfbbe85 --- /dev/null +++ b/og.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + Asger Weirsøe + Technical Solution Architect · Datalog + Proxmox · AI · Selvhosting · Softwarearkitektur + + asger.weirsøe.dk + diff --git a/package.json b/package.json new file mode 100644 index 0000000..b087301 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "asger-weirsoe-dk", + "type": "module", + "private": true, + "scripts": { + "dev": "astro dev --host", + "build": "astro build", + "preview": "astro preview" + }, + "dependencies": { + "astro": "^5.7.0", + "@astrojs/rss": "^4.0.11", + "@astrojs/sitemap": "^3.3.0", + "@tailwindcss/vite": "^4.1.5", + "tailwindcss": "^4.1.5", + "@fontsource-variable/inter": "^5.2.5" + } +} diff --git a/public/asger.png b/public/asger.png new file mode 100644 index 0000000..47f3306 Binary files /dev/null and b/public/asger.png differ diff --git a/public/b68b31655cb9c1a662d53905dd0b1b06.txt b/public/b68b31655cb9c1a662d53905dd0b1b06.txt new file mode 100644 index 0000000..9b12e70 --- /dev/null +++ b/public/b68b31655cb9c1a662d53905dd0b1b06.txt @@ -0,0 +1 @@ +b68b31655cb9c1a662d53905dd0b1b06 diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..9866400 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/public/llms.txt b/public/llms.txt new file mode 100644 index 0000000..0c342f7 --- /dev/null +++ b/public/llms.txt @@ -0,0 +1,33 @@ +# Asger Weirsøe + +> Datalog (cand.scient.) fra Aalborg Universitet og Technical Solution Architect hos Digisense A/S. Selvhosting, Proxmox, AI og fuld-stack udvikling. + +## Om +- Fulde navn: Asger Geel Weirsøe +- Nuværende rolle: Technical Solution Architect hos Digisense A/S (fra maj 2026) +- Tidligere: Teknisk Lead / Senior Konsulent hos CGI (2019–2026), Fullstack-udvikler hos BilligVVS +- Uddannelse: cand.scient. i Datalogi, Aalborg Universitet; læser HD 1. del i Erhvervsøkonomi (2025–2027) +- Ekspertise: Proxmox, selvhosting/homelab, Linux, Docker, Kubernetes, fuld-stack (Java, Go, Python, TypeScript, Angular), AI/LLM-workflows, OIDC/SSO (Authentik) +- Bopæl: Nørresundby, Danmark + +## Fagområder / ekspertise +Asger arbejder med **elektronisk fakturering (e-faktura / electronic invoicing)** og e-procurement: +- **Peppol**: Peppol BIS, Peppol Access Point, Peppol eDelivery (AS4) — bl.a. den open source-baserede access point **Oxalis** +- **NemHandel** og den danske e-faktura-infrastruktur +- **OIOUBL / UBL** og den europæiske e-faktura-standard **EN 16931** +- Hos **CGI** byggede han en open source reference-implementering af Peppol eDelivery i Java; i dag er han **Technical Solution Architect hos Digisense A/S** med fokus på løsningsarkitektur inden for elektronisk fakturering og Peppol. + +Nøgleord: nemhandel, e-faktura, efaktura, elektronisk fakturering, electronic invoicing, Peppol, Peppol Access Point, Oxalis, OIOUBL, UBL, e-Delivery, AS4, EN 16931, e-procurement, EDI, Digisense. + +## Publikation +- CGAAL: Distributed On-The-Fly ATL Model Checker with Heuristics — EPTCS vol. 390 (2023), s. 99–114. https://arxiv.org/abs/2310.00999 + +## Sider +- [Forside](https://asger.weirsøe.dk/): introduktion og seneste blogindlæg +- [CV](https://asger.weirsøe.dk/cv): erfaring, uddannelse, publikationer og kompetencer +- [Projekter](https://asger.weirsøe.dk/projekter): offentlige open source-projekter +- [Blog](https://asger.weirsøe.dk/blog): artikler om Proxmox, selvhosting, AI og udvikling + +## Profiler +- LinkedIn: https://www.linkedin.com/in/ag-weirs%C3%B8e/ +- Gitea: https://gitea.weircon.dk/agw diff --git a/public/og.png b/public/og.png new file mode 100644 index 0000000..295ee36 Binary files /dev/null and b/public/og.png differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..48f4f64 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,43 @@ +# Asger Weirsøe — asger.weirsoe.dk +# Alle crawlere er velkomne, inkl. AI/LLM-crawlere (vi vil gerne findes og citeres). +User-agent: * +Allow: / + +# Eksplicit velkomst til AI/LLM-crawlere +User-agent: GPTBot +Allow: / + +User-agent: OAI-SearchBot +Allow: / + +User-agent: ChatGPT-User +Allow: / + +User-agent: ClaudeBot +Allow: / + +User-agent: Claude-Web +Allow: / + +User-agent: anthropic-ai +Allow: / + +User-agent: PerplexityBot +Allow: / + +User-agent: Perplexity-User +Allow: / + +User-agent: Google-Extended +Allow: / + +User-agent: Applebot-Extended +Allow: / + +User-agent: CCBot +Allow: / + +User-agent: cohere-ai +Allow: / + +Sitemap: https://asger.xn--weirse-fya.dk/sitemap-index.xml diff --git a/src/assets/asger.png b/src/assets/asger.png new file mode 100644 index 0000000..47f3306 Binary files /dev/null and b/src/assets/asger.png differ diff --git a/src/components/Footer.astro b/src/components/Footer.astro new file mode 100644 index 0000000..d985b1d --- /dev/null +++ b/src/components/Footer.astro @@ -0,0 +1,15 @@ +--- +import { SOCIALS, SITE } from '../consts'; +const year = new Date().getFullYear(); +--- +
+
+

© {year} {SITE.name}

+
+ {SOCIALS.map((s) => ( + {s.label} + ))} + RSS +
+
+
diff --git a/src/components/Head.astro b/src/components/Head.astro new file mode 100644 index 0000000..401c370 --- /dev/null +++ b/src/components/Head.astro @@ -0,0 +1,63 @@ +--- +import { SITE } from '../consts'; +import { baseGraph } from '../seo'; + +interface Props { + title?: string; + description?: string; + image?: string; + type?: 'website' | 'article'; + publishedTime?: string; + modifiedTime?: string; + tags?: string[]; + jsonLd?: unknown[]; +} +const { + title, description, image, type = 'website', + publishedTime, modifiedTime, tags = [], jsonLd = [], +} = Astro.props; + +const pageTitle = title ? `${title} · ${SITE.name}` : `${SITE.title} — Technical Solution Architect & Datalog`; +const desc = description ?? SITE.description; +const canonical = new URL(Astro.url.pathname, SITE.url).href; +const ogImage = new URL(image ?? '/og.png', SITE.url).href; +const graphs = [baseGraph, ...jsonLd]; +--- + + +{pageTitle} + + + + + + + + + + + + + + + + + +{type === 'article' && publishedTime && } +{type === 'article' && modifiedTime && } +{type === 'article' && } +{type === 'article' && tags.map((t) => )} + + + + + + + + + + + + + +{graphs.map((g) => '; +sub_filter_once on; +sub_filter_types text/html; +``` + +I NPMplus lægger du det enten på den enkelte proxy host under **Advanced > Custom +Nginx Configuration**, eller globalt i `custom_nginx/server_proxy.conf`, så det +gælder *alle* hosts på én gang. Det sidste er det jeg gør. + +## Fælde 1: gzip gør sub_filter usynlig + +Det her er den klassiske aha-fejl. Din upstream-app svarer som regel **gzippet**, +og `sub_filter` arbejder på den rå svar-body. Får den komprimerede bytes, finder +den aldrig ``, og der sker .. ingenting. Ingen fejl, bare tavshed. + +Løsningen er linjen `proxy_set_header Accept-Encoding "";`. Den fortæller upstream +at du ikke vil have komprimering, så nginx får ren HTML at arbejde på. Husk den, +ellers leder du efter spøgelser. + +`sub_filter_types text/html;` sikrer samtidig, at vi kun rører HTML og ikke +forsøger at rode i JSON, billeder eller dine JavaScript-filer. + +## Fælde 2: CSP blokerer dit tredjepartsscript + +Mange apps sender en **Content-Security-Policy**, der kun tillader scripts fra +deres eget origin. Injicerer du et `" +} + +sub_filter '' '$inject_script'; +sub_filter_once on; +sub_filter_types text/html; +``` + +`sub_filter` kan godt indsætte en nginx-variabel, så `$inject_script` bliver til +enten et script-tag eller ingenting, afhængigt af hvilken host der svarer. + +## Bonus: en first-party cookie på tværs af subdomæner + +Vil du følge den samme besøgende på tværs af `app1.example.dk`, +`app2.example.dk` osv., så kan du sætte en **first-party cookie på todomænet** med +`header_filter_by_lua_block`. Pointen er at sætte den på `.example.dk`, så den +deles, og kun på rigtige sidevisninger (ikke baggrundskald), så du undgår en +Set-Cookie-løkke: + +```nginx +header_filter_by_lua_block { + local cookie = ngx.var.http_cookie or "" + local vid = cookie:match("VID=([^;]+)") + local is_new = false + + if not vid or vid == "" then + vid = ngx.var.request_id + is_new = true + end + + -- Sæt kun cookien på spritny besøgende eller ægte HTML-sideload + local accept = ngx.var.http_accept or "" + local is_html = string.find(accept, "text/html") + + if is_new or is_html then + local secure = (ngx.var.scheme == "https") and "; Secure" or "" + local sc = "VID=" .. vid .. + "; Domain=.example.dk; Path=/; Max-Age=157680000; HttpOnly; SameSite=Lax" .. + secure + local existing = ngx.header["Set-Cookie"] + if existing == nil then + ngx.header["Set-Cookie"] = sc + elseif type(existing) == "table" then + table.insert(existing, sc) + ngx.header["Set-Cookie"] = existing + else + ngx.header["Set-Cookie"] = { existing, sc } + end + end +} +``` + +`Max-Age=157680000` er fem år. Bemærk `HttpOnly`.. cookien er kun til server-side +sammenkædning, ikke til JavaScript. + +## Et par advarsler + +- **Hold dig til dine egne sider.** At injicere scripts i HTML, du ikke ejer, er + præcis den slags man bør lade være med. Det her er til *mine* selvhostede apps. +- **`sub_filter_once on`** sikrer, at vi kun rammer det første ``. Slå det + fra, hvis du bevidst vil ramme flere forekomster, men det er sjældent det du vil. +- **Test med `curl -H "Accept-Encoding: identity"`** og kig efter dit tag, hvis det + ikke dukker op i browseren. Så ved du hurtigt, om det er gzip eller CSP, der + driller. + +Når det først sidder globalt i `server_proxy.conf`, får hver ny app bag proxyen +analytics gratis, uden at jeg rører appen. Det er hele pointen med at gøre det i +edge-laget. + +## Kilder og videre læsning + +- [nginx: ngx_http_sub_module (sub_filter)](https://nginx.org/en/docs/http/ngx_http_sub_module.html) +- [OpenResty: set_by_lua_block / header_filter_by_lua_block](https://github.com/openresty/lua-nginx-module#set_by_lua_block) +- [ZoeyVid/NPMplus på GitHub](https://github.com/ZoeyVid/NPMplus) diff --git a/src/content/blog/npm-til-npmplus.md b/src/content/blog/npm-til-npmplus.md new file mode 100644 index 0000000..3ffae29 --- /dev/null +++ b/src/content/blog/npm-til-npmplus.md @@ -0,0 +1,85 @@ +--- +title: "Fra Nginx Proxy Manager til NPMplus: en migrering uden vej tilbage" +description: "Hvad der gik godt, hvad der brød, og hvorfor backup ikke er til forhandling, når du skifter fra NPM til NPMplus." +date: 2026-06-07 +tags: [npmplus, nginx-proxy-manager, reverse-proxy, selfhost] +draft: false +--- + +I årevis har min edge kørt på **Nginx Proxy Manager** (NPM). Det er en fremragende +lille proxy med en pæn UI, men jeg ville have CrowdSec, moderne TLS-defaults og en +del andre forbedringer. Det fører naturligt til **NPMplus**, en forbedret fork af +NPM. Migreringen er for det meste smertefri, men der er nogle fælder du skal kende. + +## Der er ingen vej tilbage + +Det vigtigste først: **du kan ikke migrere tilbage** til den oprindelige NPM +bagefter. Tag en fuld backup før du rører ved noget, så du har muligheden for at +rulle tilbage hvis det går galt. Jeg snapshotter hele containeren først .. det er +fem sekunders arbejde og redder din aften. + +## Formularerne har ændret adfærd + +NPMplus har ændret mange af formularerne, så efter migreringen skal du gennemgå +**hver eneste proxy host** og tjekke at indstillingerne stadig er korrekte. Antag +ikke at alt bare flytter med 1:1. Hos mig var de fleste hosts fine, men et par +stykker skulle rettes i hånden. + +## Let's Encrypt-mountet skal fjernes igen + +Det her er den fælde folk hænger fast i. Under migreringen monterer du dit gamle +`letsencrypt`-bibliotek ind, så NPMplus kan importere dine eksisterende +certifikater. Men migreringen **fortsætter ikke** før du fjerner det mount igen. +Du får en besked i stil med: + +> All certbot certs have been copied, please remove the /etc/letsencrypt +> mountpoint and redeploy to continue the migration! + +Certifikaterne er flyttet til `/data`. Fjern `/etc/letsencrypt`-mountet, redeploy +din compose-fil, og så kører den videre. + +## Intet admin-password i loggen + +Du leder forgæves efter et nyt admin-password i opstartsloggen. Det kommer ikke, +fordi **dine eksisterende NPM-credentials følger med**. Log ind med det gamle +brugernavn og kodeord. + +## Proxy gennem proxy? Skift til https + +Hvis du proxyer NPMplus gennem en anden NPM(plus)-instans, så husk at skifte +schemet fra `http` til **`https`**. Det er en lille ting, men den giver en bøvlet +fejl hvis du overser den. + +## CrowdSec ser intet uden logs + +Vil du have **CrowdSec** med, så er der to ting der ofte spænder ben: + +- Sæt `LOGROTATE=true`. Uden den skrives access-logs aldrig til disk, og så har + CrowdSec ingenting at analysere. +- CrowdSec-konfigurationen peger på **container-stier** (fx + `/opt/npmplus/nginx/logs`), ikke host-stier, selvom dine bind mounts mapper + anderledes. + +Sidder du bag **Cloudflare**, så sæt `TRUST_CLOUDFLARE=true`, ellers ser CrowdSec +kun Cloudflares IP'er som kilde til al trafik, og så banlyser den i praksis hele +internettet på én gang. + +## HTTP/3 kræver UDP 443 + +NPMplus kan **HTTP/3**, men det kræver at du forwarder **UDP 443**, ikke kun TCP. +Enten åbner du porten i din firewall, eller også slår du funktionen fra. Glemmer +du det, virker siderne stadig (de falder bare tilbage til HTTP/2), så det er nemt +at overse. + +## Var det det værd? + +Ja. NPMplus er en solid drop-in-erstatning .. den velkendte UI, alle proxy hosts +og certifikater migrerede rent, og oveni fik jeg CrowdSec og bedre defaults. Bare +husk backuppen, for der er som sagt ingen vej tilbage. + +## Kilder og videre læsning + +- [ZoeyVid/NPMplus på GitHub](https://github.com/ZoeyVid/NPMplus) +- [Walkthrough: NPM til NPMplus med CrowdSec](https://trevorbotha.net/tech-related-blog/migrating-from-nginx-proxy-manager-to-npmplus-with-crowdsec-a-complete-walkthrough/) +- [NPMplus migration discussion #964](https://github.com/ZoeyVid/NPMplus/discussions/964) +- [Nginx Proxy Manager vs. NPMplus](https://www.virtualizationhowto.com/2025/09/nginx-proxy-manager-vs-npmplus-which-one-is-better-for-your-home-lab/) diff --git a/src/content/blog/proxmox-hjemmelab.md b/src/content/blog/proxmox-hjemmelab.md new file mode 100644 index 0000000..37a9f53 --- /dev/null +++ b/src/content/blog/proxmox-hjemmelab.md @@ -0,0 +1,22 @@ +--- +title: "Mit Proxmox-hjemmelab" +description: "Et kig på opbygningen: reverse proxy, SSO, CI/CD og en masse containere." +date: 2026-06-09 +tags: [proxmox, selfhost, homelab] +draft: false +--- + +Mit hjemmelab kører på **Proxmox VE** med en blanding af LXC-containere og enkelte +virtuelle maskiner. Et par af hjørnestenene: + +## Edge & sikkerhed +Al indgående trafik går gennem en **reverse proxy** med automatiske Let's Encrypt-certifikater, +og brugere logger ind centralt via **SSO (OIDC)**. + +## Kode & CI/CD +Jeg hoster min egen **Gitea** med tilhørende Actions-runnere, så projekter bygges og +deployes automatisk ved push — inklusive den her side. + +## Resten +Medieservere, databaser, dokument-redigering, overvågning og en lang række småtjenester. +Mere om de enkelte dele i kommende indlæg. diff --git a/src/content/blog/proxmox-lxc-fejl.md b/src/content/blog/proxmox-lxc-fejl.md new file mode 100644 index 0000000..f96e38a --- /dev/null +++ b/src/content/blog/proxmox-lxc-fejl.md @@ -0,0 +1,63 @@ +--- +title: "Proxmox LXC i praksis: de fejl jeg selv lavede" +description: "Unprivileged kontra privileged, uid-mapping til nobody, og hvorfor Docker i en unprivileged LXC driller på nye kerner." +date: 2026-05-26 +tags: [proxmox, lxc, homelab, selfhost] +draft: false +--- + +Næsten alt i mit homelab kører i **LXC-containere** på **Proxmox VE** frem for +fulde virtuelle maskiner. De er lette, starter på et øjeblik og deler kernen med +værten. Men der er et par steder hvor jeg har lært det på den hårde måde. + +## Unprivileged kontra privileged + +Proxmox defaulter til **unprivileged** containere i GUI'en, og det er den rigtige +default. Internetvendte tjenester som webservere og reverse proxies bør køre +unprivileged af sikkerhedshensyn. DNS, proxies, overvågning og lette tjenester er +perfekte LXC-kandidater. + +**Privileged** containere bør du kun bruge til isolerede homelabs uden +internetvendte tjenester, til NFS-servere hvor uid-mapping bliver for besværligt, +eller til hurtige testmiljøer. Min tommelfingerregel: unprivileged medmindre noget +tvinger mig til andet. + +## Alt bliver til `nobody` + +Den første væg man render ind i med unprivileged containere: hver fil og mappe +mappes til **`nobody`** (uid 65534). Det giver bøvl så snart du har restriktive +rettigheder, eller vil skrive filer med et bestemt uid/gid. Især når du monterer +**netværksshares** ind, introducerer uid-shiftet en hel del kompleksitet du skal +forstå før det spiller. + +Løsningen er ikke at give op og køre privileged. Det er at lære `lxc.idmap` at +kende og mappe de specifikke uid'er du har brug for. Det tager en eftermiddag at +forstå, og så har du det som værktøj resten af tiden. + +## Docker i en unprivileged LXC er en fælde lige nu + +Det her bed mig for nylig. De fleste guides til **Docker i LXC** online er +forældede .. de antager kerne 5.x og runc 1.1.x. På moderne Proxmox med +**kerne 6.14+** og **runc 1.2+** fejler de opskrifter før du overhovedet når frem +til de gamle workarounds. Unprivileged Docker er reelt brudt på nye kerner på +grund af runc's per-namespace sysctl-skrivning og Dockers AppArmor-probe. + +Næsten enhver fungerende opsætning i forummerne involverer enten +**kerneændringer på værten**, at slå **AppArmor** helt fra, eller begge dele. Mit +råd: vil du køre Docker, så kør det i en **VM** i stedet, eller i en bevidst +**privileged** LXC du holder isoleret. Det er ikke kampen værd at presse det ind i +en unprivileged container på en ny kerne. + +## LXC kontra VM + +Jeg vælger LXC til langt det meste: reverse proxy, databaser, statiske websites, +små API'er. Jeg vælger en **VM** når jeg har brug for en anden kerne, fuld +isolation, eller netop Docker-tunge workloads. Reglen er enkel: container når jeg +kan, VM når jeg skal. + +## Kilder og videre læsning + +- [Proxmox wiki: Unprivileged LXC containers](https://pve.proxmox.com/wiki/Unprivileged_LXC_containers) +- [Docker on Proxmox LXC: what actually works](https://dev.to/mattercoder/docker-on-proxmox-lxc-what-actually-works-and-why-unprivileged-doesnt-45km) +- [Privileged vs. unprivileged LXC](https://proxmoxpulse.com/articles/privileged-vs-unprivileged-lxc-containers-proxmox/) +- [Proxmox: LXC containers vs. VMs](https://homelabstarter.com/proxmox-lxc-vs-vm/) diff --git a/src/content/blog/velkommen.md b/src/content/blog/velkommen.md new file mode 100644 index 0000000..56c6171 --- /dev/null +++ b/src/content/blog/velkommen.md @@ -0,0 +1,18 @@ +--- +title: "Velkommen til min nye side" +description: "Hvad siden er, og hvad du kan forvente at finde her." +date: 2026-06-10 +tags: [meta, selfhost] +draft: false +--- + +Velkommen! Det her er min nye personlige side — bygget med **Astro** på **Bun**, +hostet på min egen Proxmox-infrastruktur, og deployet automatisk via Gitea Actions. + +Her kommer jeg til at skrive om det jeg roder med: + +- **Selvhosting & Proxmox** — hvordan jeg driver mit hjemmelab. +- **AI & automatisering** — at bygge ting med Claude Code og LLM'er. +- **Udvikling** — Bun, TypeScript, Django og alt derimellem. + +Siden opdateres løbende. God fornøjelse. diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro new file mode 100644 index 0000000..57e0af5 --- /dev/null +++ b/src/layouts/Base.astro @@ -0,0 +1,51 @@ +--- +import { ClientRouter } from 'astro:transitions'; +import Head from '../components/Head.astro'; +import Header from '../components/Header.astro'; +import Footer from '../components/Footer.astro'; +import { SITE } from '../consts'; +import '../styles/global.css'; +interface Props { + title?: string; + description?: string; + image?: string; + type?: 'website' | 'article'; + publishedTime?: string; + modifiedTime?: string; + tags?: string[]; + jsonLd?: unknown[]; +} +const { title, description, image, type, publishedTime, modifiedTime, tags, jsonLd } = Astro.props; +--- + + + + + + + + +
+
+ +
+