Skip to content

Repository files navigation

PortfolioCMS

Személyes portfólió oldal és a hozzá tartozó saját CMS. Két ASP.NET Core host, két Vue 3 SPA, Postgres és Redis; egy VPS-en, Docker Compose-zal, kifelé cloudflared tunnelen.

A rendszer szándékosan két hostra van osztva:

HostMit adHogyan érhető el
web (publikus)a portfólió oldal, olvasó API, kapcsolatfelvétel, látogatás-beaconcloudflared tunnel → 127.0.0.1:8080
admin (dashboard)minden írás (CRUD), csatolmánykezelés, statisztika, contact inboxtunnel + Cloudflare Access → 127.0.0.1:8090

A lényeg nem a kényelem, hanem hogy a publikus originon nem léteznek író endpointok — nem le vannak tiltva, hanem az a controller-készlet nincs betöltve. Az admin host a védelmét nem a portkötésre bízza: a Cloudflare Access assertionjét maga is ellenőrzi (CloudflareAccessMiddleware).

Architektúra

PortfolioCMS.Core közös mag: EF Core modell + migrációk, olvasó controllerek,
middleware-ek (CSP, bot, Access), ContentCache, SEO (sitemap,
útvonal-index, oldalankénti meta), indítási kiterjesztések
PortfolioCMS.Server publikus host: Core + kapcsolat, analytics beacon, sitemap/robots,
SPA fallback (valódi 404 + szerveroldali meta)
PortfolioCMS.Admin admin host: Core + CRUD és statisztika controllerek, Cloudflare Access
PortfolioCMS.Tests xUnit: unit tesztek, TestServer-es pipeline tesztek, API tesztek
eldobható Postgres konténerben (Testcontainers)
portfoliocms.client publikus SPA (Vue 3 + Vite)
portfoliocms.admin.client dashboard SPA (Vue 3 + Vite)
portfoliocms.shared a két SPA közös kódja: API kliens, content store, i18n, formázók.
Saját package.json / tsconfig / vitest — önmagában lintelhető,
típusellenőrizhető és tesztelhető. A két appba nem npm
függőségként, hanem a `@shared` aliasból épül be.
deploy/ üzemeltetés: runbook, mentés/visszaállítás, smoke teszt,
cloudflared példakonfiguráció, systemd unitok

Adattárolás:

  • Postgres — tartalom (Works, Skills, Experiences, About), csatolmányok bytea-ban, látogatás-események, kapcsolati üzenetek. A migrációk a Core-ban vannak, és mindkét host induláskor migrál (MigrateAndSeedAsync).
  • Redis — olvasási cache (ContentCache). Szándékosan fail-open: ha nem érhető el, minden kérés az adatbázisra megy, de az oldal áll. Minden admin írás explicit invalidál, a TTL csak biztonsági háló.

Kiszolgálás: mindkét host a saját wwwroot-jából adja a SPA-t. A /api alatti nem létező útvonal 404, nem index.html. A publikus hoston a MapSpaFallback dönt a status kódról (a nem létező slug valódi 404-et kap) és írja a kért oldal saját meta/OG tagjeit a kiszolgált HTML-be — a SPA ugyanezt megteszi futásidőben, de a szkriptet nem futtató crawler csak ezt látja.

Lokális fejlesztés

Kell hozzá: .NET 10 SDK, Node 22, Docker Desktop.

1. Konfiguráció

cp .env.example .env

Amit ki kell tölteni (a többinek van használható defaultja): POSTGRES_PASSWORD, ANALYTICS_HASH_SECRET, TURNSTILE_*, CF_ACCESS_*. A fájl minden változónál leírja, mire jó.

2. A teljes stack konténerben

docker compose up --build
  • publikus oldal: http://localhost:8080
  • admin: http://localhost:8090 — konténerben Production környezet fut, tehát az Access assertion ellenőrzés éles. Helyi admin munkához a hostot IDE-ből futtasd (lásd lentebb): Development-ben, CloudflareAccess értékek nélkül a middleware nem kerül be a pipeline-ba.

3. Backend IDE-ből, frontend Vite dev serverrel

Ez a szokásos fejlesztői kör. A PortfolioCMS.slnx mind a hat projektet tartalmazza, a két host Microsoft.AspNetCore.SpaProxy-val indul, tehát a npm run dev-et maga hívja.

Csak az adatbázist és a cache-t hozd fel konténerben:

docker compose up -d db redis

A db és a redis a compose-ban internal hálózaton van, port nélkül — a hoszt felől nem látszanak. A publikálásukhoz kell egy docker-compose.override.yml a repó gyökerében (a compose automatikusan betölti, és a .gitignore kihagyja, mert csak fejlesztéshez való):

services:
db:
ports:
- "127.0.0.1:5432:5432"redis:
ports:
- "127.0.0.1:6379:6379"

A szerveren e nélkül kell deployolni: docker compose -f docker-compose.yml up -d.

Ezután a hostok konfigurációja appsettings.Development.json-ból jön (gitignorolt, minden fejlesztő a sajátját írja):

{
"ConnectionStrings": {
"Default": "Host=localhost;Port=5432;Database=portfoliocms;Username=portfolio;Password=<a .env-beli jelszó>",
"Redis": "localhost:6379"
},
"Turnstile": {
"SiteKey": "1x00000000000000000000AA",
"SecretKey": "1x0000000000000000000000000000000AA"
},
"SeedMode": "dev"
}

A Turnstile értékek a Cloudflare mindig-átmenő teszt kulcsai. A SeedMode: dev demo tartalmat seedel; prod üres adatbázissal indul.

Indítás:

dotnet run --project PortfolioCMS.Server --launch-profile https

A Vite konfiguráció a dev tanúsítványt magától exportálja (dotnet dev-certs https), az első futás előtt viszont egyszer meg kell bízni benne:

dotnet dev-certs https --trust

Két környezeti változó a dev serverre:

  • DEV_SERVER_HTTP=1 — sima HTTP-t szolgál ki, ha valami nem fogadja el az önaláírt tanúsítványt
  • DEV_SERVER_PORT=<port> — a Vite port felülírása (alap: 53774 / 53775)

4. Tesztek

dotnet test PortfolioCMS.Tests/PortfolioCMS.Tests.csproj

A séma jsonb kolumnákat és FILTER aggregátumot használ, ezért az API tesztek egy eldobható Postgres konténert indítanak. Docker nélküli gépen ezek a tesztek kimaradnak (nem hasalnak el), a CI-ban futnak.

Frontend, mindkét app könyvtárában:

npm ci
npm run type-check
npx oxlint .&& npx eslint .
npm test

A lock fájlok Linuxon készültek — a natív csomagok wasm fallback ágát a Windowson futó npm kihagyja a lockból, és akkor a CI npm ci "not in sync" hibára fut.

A portfoliocms.shared ugyanez, csak npm install-lal (a saját lockja Windowson készült):

cd portfoliocms.shared
npm install
npm run type-check && npx oxlint .&& npx eslint .&& npm test

A három package.json közös csomagjainak verziója nem csúszhat el — semmi nem köti őket egymáshoz, ezért a CI külön ellenőrzi:

node scripts/check-dep-sync.mjs

5. NuGet csomagok

A négy .NET projekt feloldott csomaggráfja packages.lock.json-ban van kikötve, és az image build --locked-mode-ban restore-ol — tehát egy elavult lock a buildet buktatja, nem csendben old fel mást. PackageReference változás után a lock fájlt is commitolni kell:

dotnet restore PortfolioCMS.slnx /p:RestoreForceEvaluate=true

Környezeti változók

A hitelesnek tekintendő lista a kommentezett .env.example. Amelyik nélkül a host el sem indul:

VáltozóMi hasalna el nélküle
POSTGRES_PASSWORDa compose (:?), tehát mindkét host
ANALYTICS_HASH_SECRET (min. 16 byte)a publikus host — a látogató-hash sózása
TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEYa publikus host — üres secret csendben kikapcsolná a captchát
CF_ACCESS_TEAM_DOMAIN, CF_ACCESS_AUDaz admin host — enélkül hitelesítés nélkül írható lenne

A többi (PUBLIC_SITE_URL, ALLOWED_HOSTS, SEED_MODE, retention és mentés beállítások) defaultolódik. Development-ben a captcha és az Access értékek elhagyhatók.

Deploy

A master-re való push indítja a deploy.yml-t:

  1. ci — a ci.yml meghívása: backend tesztek, mindkét frontend típusellenőrzése, lintje, npm audit-ja és tesztjei, a shared csomag ugyanez, valamint a NuGet sebezhetőség-ellenőrzés. Egy közvetlen master push sem deployol tesztek nélkül. Az image build (és vele a Trivy szkennelés) itt kimarad: azt a következő job végzi.
  2. build-and-push — a két image (portfoliocms, portfoliocms-admin) buildje és pusholása GHCR-be, latest és sha-<commit> taggel.
  3. deploydocker-compose.yml és a deploy/ szkriptek másolása a VPS /opt/portfoliocms könyvtárába, majd SSH-n: .env írása a repository secretekből, GHCR login, pull, deploy előtti adatbázis dump (deploy/backup.sh), up -d, deploy/smoke-test.sh, végül a hétnél régebbi image-ek takarítása.

A dump azért van a séma hozzáérése előtt, mert a hostok induláskor migrálnak, és egy régebbi image nem tudja lebontani az újabb sémát — a visszalépés dump nélkül nem lenne elég.

Ehhez szükséges repository secretek:

  • SSH:SSH_HOST, SSH_USER, SSH_KEY, SSH_PORT
  • GHCR (a VPS oldali pullhoz):GHCR_USER, GHCR_TOKEN
  • alkalmazás:POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD, ANALYTICS_HASH_SECRET, ANALYTICS_RETENTION_DAYS, ATTACHMENT_ORPHAN_GRACE_DAYS, CONTACT_RETENTION_DAYS, CONTACT_KEEP_UNREAD, PUBLIC_SITE_URL, SEED_MODE, ALLOWED_HOSTS, TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY, CF_ACCESS_TEAM_DOMAIN, CF_ACCESS_AUD
  • mentés:BACKUP_RETENTION_DAYS, BACKUP_AGE_RECIPIENT, BACKUP_RCLONE_REMOTE

Visszalépés

A rollback.yml kézzel indítható (workflow_dispatch). SHA megadása nélkül az eggyel korábbi image párra lép vissza: a deploy minden alkalommal kimenti a futó image referenciákat a VPS .env.previous fájljába. A séma miatt a visszalépés általában visszaállítást is igényel — a menete a runbookban van.

Függőségfrissítés

A .github/dependabot.yml hetente négy ökoszisztémát néz: nuget, npm (a három frontend manifest), github-actions és docker (a két Dockerfile és a docker-compose.yml base image-ei). A PR-ek csoportosítva jönnek, minor/patch és major külön — egy Microsoft.* patch kiadás enélkül nyolc külön PR-t nyitna.

A három frontend manifest szándékosan egy PR-be esik: a scripts/check-dep-sync.mjs elhasal, ha ugyanaz a csomag máshol más ranget kap, tehát csak együtt léptethetők.

A CI oldali ellenőrzés ehhez: npm audit --audit-level=high mindkét frontendre, a dotnet list package --vulnerable --include-transitive a négy .NET projektre, és Trivy (HIGH,CRITICAL, ignore-unfixed) a PR-ben buildelt image-ekre.

Üzemeltetés

Amit a kódból nem lehet látni, mert a hoszton él, a deploy/RUNBOOK.md-ban van: a forgalom útja, a cloudflared konfiguráció (példa), a Cloudflare Access alkalmazás és AUD, az ellenőrzőlista, a deploy és a visszalépés menete, az adatbázis mentése (titkosítás, offsite tároló, systemd timer) és visszaállítása, a csatolmányok életciklusa, valamint az erőforrás-korlátok és a logrotáció.

Megjegyzés a dokumentációhoz

A 2026-08-11-i biztonsági és optimalizálási átvizsgálás (TODO.md) tételei vagy meg vannak oldva, vagy GitHub issue-ként vannak nyitva — a fájl ezért nem maradt a repóban. Ami a döntések miértjét rögzíti, az a kód mellett van: a docker-compose.yml, a két Program.cs és a Core/Services XML kommentjei szándékosan bőbeszédűek.

About

Saját üzemeltetésű portfólió CMS: .NET 10 API + két Vue 3 SPA (publikus oldal és admin), PostgreSQL + Redis, kétnyelvű tartalom, cookie nélküli látogatói statisztika, kapcsolat inbox. Admin Cloudflare Access mögött, Docker Compose deploy.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages