commit cb8da8fa6d72e194237285ae49ab84348c810dee Author: forgejo-admin Date: Mon Aug 10 02:37:19 2026 +0000 Split codice/contenuti: repo pubblico (codice) — articoli in astro-blog-content (privato) diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml new file mode 100644 index 0000000..4d8c240 --- /dev/null +++ b/.forgejo/workflows/deploy.yml @@ -0,0 +1,22 @@ +name: deploy-blog + +on: + push: + branches: [ main ] + paths: + - "src/**" + - "package.json" + - "Dockerfile" + - "astro.config.mjs" + - "docker-compose.yml" + - "nginx.conf" + - ".forgejo/workflows/**" + +jobs: + deploy: + runs-on: self-hosted + steps: + - name: Deploy blog (script condiviso, host) + run: | + set -e + bash /root/.hermes/scripts/deploy-blog.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe057c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# .gitignore — astro-blog (repo pubblico: SOLO codice) +# Gli articoli vivono nel repo privato forgejo-admin/astro-blog-content: +# la build li copia in src/content/blog/ (mai committati qui). +src/content/blog/* +!src/content/blog/.gitkeep + +node_modules +dist +.env +*.log +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b74d67a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +# ---- Stage 1: build del sito statico con Node.js ---- +FROM docker.io/library/node:22-alpine AS build +WORKDIR /app + +COPY package.json ./ +RUN npm install --no-audit --no-fund + +COPY astro.config.mjs tsconfig.json ./ +COPY src ./src +COPY public ./public + +RUN npx astro build + +# ---- Stage 2: serve con nginx ---- +FROM docker.io/library/nginx:1.27-alpine +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/GUIDA-REDATTORE-BLOG.md b/GUIDA-REDATTORE-BLOG.md new file mode 100644 index 0000000..a92d816 --- /dev/null +++ b/GUIDA-REDATTORE-BLOG.md @@ -0,0 +1,190 @@ +# Guida del redattore — Blog di my-vps + +Guida per chi (umano o agente AI) deve scrivere e pubblicare articoli sul +blog di my-vps. + +Ultimo aggiornamento: 2026-08-10 + +--- + +## 1. Panoramica + +- **Blog**: + - sul **web** (con password): `https://blog.194.164.167.80.nip.io` — + accesso tramite **Authelia** (SSO, login unico). + - in **VPN** (senza password, rete privata): `http://100.64.0.2:8083`. +- **Sito statico** generato con **Astro**, servito da **nginx** in un + container, esposto da **Traefik**. +- **Due repo Forgejo separati**: + - **codice** (PUBBLICO): `forgejo-admin/astro-blog` + (`https://git.194.164.167.80.nip.io/forgejo-admin/astro-blog`) — + tema, layout, configurazione. Non contiene articoli. + - **articoli** (PRIVATO): `forgejo-admin/astro-blog-content` + (`https://git.194.164.167.80.nip.io/forgejo-admin/astro-blog-content`) — + i post in Markdown, in `blog/`. +- **Copia di lavoro sul server**: `/opt/astro-blog` (codice) e + `/opt/astro-blog-content` (articoli). +- **Pipeline**: a ogni push su `main` di **uno dei due repo**, **Forgejo + Actions** (runner self-hosted) esegue `/root/.hermes/scripts/deploy-blog.sh` + che ricostruisce l'immagine e ricrea il container in automatico. + +> La regola d'oro: **ogni post = un file Markdown nel repo PRIVATO +> `astro-blog-content` + `git push` su `main`**. Il deploy è automatico. + +## 2. Struttura di un post + +### 2.1 File e URL + +- Il post va in `blog/.md` nella copia di lavoro del repo **privato** + (o su Forgejo: repo `astro-blog-content` → cartella `blog/`). +- `` determina l'URL finale: `/posts//`. +- Lo slug: minuscolo, trattini al posto degli spazi, niente spazi/caratteri + speciali. Es. `come-si-installa-podman.md` → `/posts/come-si-installa-podman/`. +- Non usare slug duplicati (la build fallisce). + +### 2.2 Frontmatter (schema obbligatorio) + +```markdown +--- +title: "Titolo del post" +description: "Una o due frasi di riassunto (opzionale ma consigliato)." +pubDate: 2026-08-10T10:30:00+02:00 +author: "forgejo-admin" +tags: ["infrastruttura", "podman"] +--- +``` + +| Campo | Obbligatorio | Note | +|---|---|---| +| `title` | si | testo tra virgolette | +| `description` | no | mostrata in lista e nel feed RSS; consigliata | +| `pubDate` | si | data ISO **con offset**, ora esatta al secondo in `Europe/Rome` | +| `author` | no | default `admin`; usare `forgejo-admin` | +| `tags` | no | array di stringhe, es. `["meta", "infrastruttura"]` | + +**Attenzione agli orari**: il sito usa `Europe/Rome`. In estate l'offset è +`+02:00`, in inverno `+01:00`. Usare sempre l'offset esatto del momento in cui +si vuole che il post risulti pubblicato. + +Esempio valido: `pubDate: 2026-08-10T10:30:00+02:00`. + +### 2.3 Corpo del post + +- Markdown standard: titoli `##`, elenchi, citazioni, `code fence`, link, grassetto. +- Non serve altro: il rendering è automatico. +- Il post viene ordinato per `pubDate` **decrescente** (il più recente in cima), + in home, archivio e RSS. + +### 2.4 Modello (post esistente) + +In `blog/` del repo privato ci sono i post già pubblicati: usarli come +riferimento per struttura e frontmatter. + +## 3. Workflow di pubblicazione + +### 3.1 Procedura standard (deploy automatico) + +```sh +# SUL SERVER (copia di lavoro del repo PRIVATO) +ssh root@194.164.167.80 + +cd /opt/astro-blog-content +git pull origin main + +# crea o modifica il post +nano blog/.md + +# versiona e pubblica: il push fa partire il deploy automatico +git add blog/.md +git commit -m "Nuovo post: " +git push origin main +``` + +Fatto: il runner ricostruisce l'immagine e ricrea il container da solo +(qualche decina di secondi). Verificare l'esito con il par. 4. + +### 3.2 Procedura per l'agente AI (Hermes/opencode) + +Stessi passi, via strumenti: + +1. `git pull` in `/opt/astro-blog-content`. +2. Creare il file Markdown `blog/.md` con frontmatter corretto (par. 2). +3. `git add` + `git commit` + `git push origin main` (deploy automatico). +4. **Verificare sempre** il risultato (par. 4) e riportarlo all'utente. + +### 3.3 Fallback manuale (solo in emergenza) + +Se l'automazione non funziona, deploy a mano dal server: + +```sh +cd /opt/astro-blog +git pull origin main +git -C /opt/astro-blog-content pull origin main +rm -f src/content/blog/*.md +cp /opt/astro-blog-content/blog/*.md src/content/blog/ +podman build -t astro-blog:latest . +systemctl restart container-astro-blog.service +sleep 5 +systemctl is-active container-astro-blog.service +``` + +Il container è gestito da systemd (`container-astro-blog.service`): +il restart lo ricrea con l'immagine appena costruita. + +## 4. Verifica della pubblicazione + +```sh +# nuovo post raggiungibile? (via VPN, senza password) +curl -s -o /dev/null -w "%{http_code}\n" http://100.64.0.2:8083/posts// + +# home e archivio (via VPN) +curl -s -o /dev/null -w "%{http_code}\n" http://100.64.0.2:8083/ +curl -s -o /dev/null -w "%{http_code}\n" http://100.64.0.2:8083/archivio/ + +# feed RSS (contiene il nuovo post?) +curl -s http://100.64.0.2:8083/rss.xml | grep -c "" + +# sul web: deve chiedere il login Authelia (302/401) +curl -s -o /dev/null -w "%{http_code}\n" https://blog.194.164.167.80.nip.io/ + +# esito del run CI +journalctl -u forgejo-runner -n 30 +# oppure UI: https://git.194.164.167.80.nip.io/forgejo-admin/astro-blog-content/actions +``` + +## 5. Regole editoriali + +- **Lingua**: italiano. Tono semplice e diretto, stile "appunti". +- **Date**: sempre esatte al secondo, fuso `Europe/Rome` con offset (vedi 2.2). +- **Description**: 1-2 frasi, utile per lista e RSS. +- **Tags**: usare un vocabolario coerente e riusare i tag esistenti + (`meta`, `infrastruttura`, ...). Evitare tag inventati per ogni post. +- **Immagini**: il layout attuale non prevede gallerie; gli asset statici + possono stare in `public/` del repo del codice e riferirsi con percorso + assoluto (`/nome.png`). +- **Niente segreti**: non pubblicare password, chiavi, token o dati sensibili. + Il blog sul web è protetto da login, ma la prudenza resta la regola. +- **Attribuzione**: `author` di default `forgejo-admin` (o il nome del redattore). +- **Coerenza**: se si modifica un post già pubblicato, aggiornare `pubDate` + solo se il contenuto è stato riscritto in modo significativo. + +## 6. Problemi frequenti + +| Sintomo | Causa probabile | Rimedio | +|---|---|---| +| Il push non fa partire il deploy | runner spento o errore CI | `journalctl -u forgejo-runner`; UI `/actions` | +| Post non visibile dopo il push | deploy fallito in CI | console del run; correggere e ri-pushare | +| `404` su `/posts//` | slug/nome file diverso da quanto atteso | verificare il nome del file | +| Build fallita con errore `zod`/frontmatter | campo mancante o `pubDate` non valido | correggere il frontmatter (2.2) | +| Slug duplicato | due file generano lo stesso slug | rinominare uno dei file | +| Blog `502` su tutto | socket Podman di Traefik | vedi TRAEFIK-FORGEJO-BLOG.md (Nota operativa socket) | +| Home ok ma post vecchi | deploy non eseguito (CI fallita) | fallback manuale (3.3) | + +## 7. Riferimenti + +- Documentazione operativa del server: `/root/README.md` e `/root/*.md`. +- Deploy/blog (architettura): `/root/TRAEFIK-FORGEJO-BLOG.md`. +- Automazione CI: `/root/FORGEJO-ACTIONS.md`. +- Repo codice (pubblico): `https://git.194.164.167.80.nip.io/forgejo-admin/astro-blog`. +- Repo articoli (privato): `https://git.194.164.167.80.nip.io/forgejo-admin/astro-blog-content`. +- Astro content collections: https://docs.astro.build (schema e Markdown). diff --git a/README.md b/README.md new file mode 100644 index 0000000..0edff61 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# astro-blog + +Blog statico in **Astro**, containerizzato, ospitato su **Forgejo** e servito +via **Traefik** sul VPS my-vps (194.164.167.80). + +- Repo **pubblico** (questo): solo il **codice** del blog. +- Repo **privato**: `forgejo-admin/astro-blog-content` — gli **articoli** + (Markdown in `blog/`). + +## Accesso al blog + +| Canale | URL | Autenticazione | +|---|---|---| +| Web (pubblico) | https://blog.194.164.167.80.nip.io | password Authelia (SSO) | +| VPN | http://100.64.0.2:8083 | nessuna (rete privata) | + +## Struttura + +``` +astro-blog/ +├── Dockerfile # build multistage: Node.js → nginx +├── nginx.conf # config nginx del container +├── docker-compose.yml # deploy con label Traefik (+ Authelia) +├── astro.config.mjs +├── src/ +│ ├── content/config.ts # schema collezione blog +│ ├── content/blog/ # NON versionato qui (arriva dal repo privato in build) +│ ├── layouts/ # BaseLayout, PostLayout +│ ├── lib/posts.ts # ordinamento post +│ ├── consts.ts # titolo, descrizione, timezone, formatDateTime +│ ├── pages/ # index, archivio, posts/[...slug], rss.xml.js +│ └── styles/global.css +└── public/favicon.svg +``` + +## Come funziona la build (separazione codice/contenuti) + +A ogni push su `main` (di QUESTO repo **o** del repo contenuti), il runner +Forgejo Actions esegue `/root/.hermes/scripts/deploy-blog.sh` che: + +1. `git pull` in `/opt/astro-blog` (codice) e `/opt/astro-blog-content` (articoli) +2. copia `blog/*.md` → `src/content/blog/` +3. `podman build -t astro-blog:latest` + `systemctl restart container-astro-blog.service` + +## Comandi (fallback manuale, sul server) + +```sh +cd /opt/astro-blog && git pull origin main +git -C /opt/astro-blog-content pull origin main +rm -f src/content/blog/*.md && cp /opt/astro-blog-content/blog/*.md src/content/blog/ +podman build -t astro-blog:latest . +systemctl restart container-astro-blog.service +``` + +## Note + +- Tutti gli orari nel sito sono in `Europe/Rome`, al secondo + (`formatDateTime` in `src/consts.ts`). +- L'immagine è stateless: ogni articolo richiede una nuova build (automatica). +- Scrivere articoli: vedi `GUIDA-REDATTORE-BLOG.md` (pubblica) e il README + del repo privato `astro-blog-content`. diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 0000000..8bf2c71 --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,9 @@ +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + site: 'http://blog.194.164.167.80.nip.io', + output: 'static', + build: { + format: 'directory', + }, +}); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0d4f95a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +services: + blog: + build: + context: . + dockerfile: Dockerfile + image: astro-blog:latest + container_name: astro-blog + restart: unless-stopped + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.blog.rule=Host(`blog.194.164.167.80.nip.io`)" + - "traefik.http.routers.blog.entrypoints=web" + - "traefik.http.routers.blog.middlewares=authelia@docker" + - "traefik.http.routers.blog-tls.rule=Host(`blog.194.164.167.80.nip.io`)" + - "traefik.http.routers.blog-tls.entrypoints=websecure" + - "traefik.http.routers.blog-tls.middlewares=authelia@docker" + - "traefik.http.routers.blog-tls.tls=true" + - "traefik.http.routers.blog-tls.tls.certresolver=le" + - "traefik.http.routers.blog-tls.service=blog" + - "traefik.http.services.blog.loadbalancer.server.port=80" + +networks: + web: + external: true diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..7331333 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,23 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ =404; + } + + location = /index.html { + add_header Cache-Control "no-cache"; + } + + location /assets/ { + expires 30d; + add_header Cache-Control "public, immutable"; + } + + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Forwarded-Proto "http" always; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..20e2e88 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "astro-blog", + "version": "1.0.0", + "description": "Blog statico in Astro, containerizzato su my-vps", + "type": "module", + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview" + }, + "dependencies": { + "astro": "^4.16.0", + "@astrojs/rss": "^4.0.7" + } +} diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..9ee68b2 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,4 @@ + + + B + diff --git a/src/consts.ts b/src/consts.ts new file mode 100644 index 0000000..66a08f4 --- /dev/null +++ b/src/consts.ts @@ -0,0 +1,16 @@ +export const SITE_TITLE = 'Il Blog di my-vps'; +export const SITE_DESCRIPTION = 'Blog statico in Astro, ospitato su Forgejo e servito via Traefik.'; +export const SITE_TIMEZONE = 'Europe/Rome'; + +export function formatDateTime(d: Date): string { + return new Intl.DateTimeFormat('it-IT', { + timeZone: SITE_TIMEZONE, + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false, + }).format(d).replace(/\//g, '-'); +} diff --git a/src/content/blog/.gitkeep b/src/content/blog/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/content/config.ts b/src/content/config.ts new file mode 100644 index 0000000..6e75427 --- /dev/null +++ b/src/content/config.ts @@ -0,0 +1,14 @@ +import { defineCollection, z } from 'astro:content'; + +const blog = defineCollection({ + type: 'content', + schema: z.object({ + title: z.string(), + description: z.string().optional(), + pubDate: z.coerce.date(), + author: z.string().default('admin'), + tags: z.array(z.string()).default([]), + }), +}); + +export const collections = { blog }; diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro new file mode 100644 index 0000000..b4b85f2 --- /dev/null +++ b/src/layouts/BaseLayout.astro @@ -0,0 +1,118 @@ +--- +import { SITE_TITLE, SITE_DESCRIPTION, formatDateTime, SITE_TIMEZONE } from '../consts'; +import '../styles/global.css'; + +interface Props { + title?: string; + description?: string; +} + +const { title = SITE_TITLE, description = SITE_DESCRIPTION } = Astro.props; +--- + + + + + + + + {title} + + + + + + +
+ +
+ + + + + + diff --git a/src/layouts/PostLayout.astro b/src/layouts/PostLayout.astro new file mode 100644 index 0000000..344fdc0 --- /dev/null +++ b/src/layouts/PostLayout.astro @@ -0,0 +1,42 @@ +--- +import type { CollectionEntry } from 'astro:content'; +import { formatDateTime, SITE_TIMEZONE } from '../consts'; + +interface Props { + post: CollectionEntry<'blog'>; +} + +const { post } = Astro.props; +const { title, description, pubDate, author, tags } = post.data; +--- + +
+
+

{title}

+

+ + {' · '}{author} +

+ {description &&

{description}

} +
+ +
+ +
+ + {tags.length > 0 && ( +
+
    + {tags.map((t) =>
  • {t}
  • )} +
+
+ )} +
+ + diff --git a/src/lib/posts.ts b/src/lib/posts.ts new file mode 100644 index 0000000..66430c3 --- /dev/null +++ b/src/lib/posts.ts @@ -0,0 +1,8 @@ +import { getCollection } from 'astro:content'; +import type { CollectionEntry } from 'astro:content'; + +export function getSortedPosts(posts: CollectionEntry<'blog'>[]) { + return [...posts].sort( + (a, b) => new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime(), + ); +} diff --git a/src/pages/archivio.astro b/src/pages/archivio.astro new file mode 100644 index 0000000..f0f3272 --- /dev/null +++ b/src/pages/archivio.astro @@ -0,0 +1,29 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro'; +import { getCollection } from 'astro:content'; +import { getSortedPosts } from '../lib/posts'; +import { formatDateTime } from '../consts'; + +const posts = getSortedPosts(await getCollection('blog')); +--- + + +

Archivio

+
    + { + posts.map((post) => ( +
  • + + {post.data.title} +
  • + )) + } +
+
+ + diff --git a/src/pages/index.astro b/src/pages/index.astro new file mode 100644 index 0000000..a04326e --- /dev/null +++ b/src/pages/index.astro @@ -0,0 +1,45 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro'; +import { getCollection } from 'astro:content'; +import { getSortedPosts } from '../lib/posts'; +import { formatDateTime, SITE_TIMEZONE } from '../consts'; + +const posts = getSortedPosts(await getCollection('blog')); +--- + + +

Benvenuti nel blog

+

+ Raccolta di appunti e articoli. Il sito è un progetto Astro + containerizzato, con il codice sorgente ospitato su Forgejo + e il routing gestito da Traefik. +

+ + + +

Ora: ({SITE_TIMEZONE})

+
+ + diff --git a/src/pages/infrastruttura.astro b/src/pages/infrastruttura.astro new file mode 100644 index 0000000..a3d8989 --- /dev/null +++ b/src/pages/infrastruttura.astro @@ -0,0 +1,99 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro'; +import { SITE_TITLE } from '../consts'; + +const services = [ + { name: 'Authelia (login unico)', url: 'https://auth.194.164.167.80.nip.io/', desc: 'Portale SSO: una password + 2FA sbloccano tutti i servizi riservati.' }, + { name: 'Forgejo', url: 'https://git.194.164.167.80.nip.io/', desc: 'Hosting git con Forgejo Actions per il deploy automatico del blog.' }, + { name: 'Blog', url: 'https://blog.194.164.167.80.nip.io/', desc: 'Questo sito: statico, generato con Astro.' }, + { name: 'Adminer', url: 'https://adminer.194.164.167.80.nip.io/', desc: 'Amministrazione del database PostgreSQL da browser (accesso con SSO).' }, + { name: 'Dashboard Traefik', url: 'https://traefik.194.164.167.80.nip.io/dashboard/', desc: 'Vista del reverse proxy e delle route attive (accesso con SSO).' }, +]; +--- + + +

L'infrastruttura

+

+ Tutto gira su un piccolo server (il "my-vps") come container gestiti da + systemd e dietro un reverse proxy. Questa pagina riassume, in modo sicuro + e senza dettagli riservati, com'è fatta l'infrastruttura. +

+ +

Il server

+
    +
  • Una VM Linux (Debian) con Podman e systemd.
  • +
  • Ogni servizio è un container supervisionato da un'unità systemd + (container-<nome>.service): riavvio automatico al boot + e in caso di crash.
  • +
  • Il traffico pubblico passa da Traefik, che termina le + connessioni e instrada verso i container.
  • +
  • Backup automatici giornalieri e una suite di sicurezza attiva + (firewall, fail2ban, accessi protetti).
  • +
+ +

Tecnologie

+
    +
  • Astro — generatore statico di questo blog.
  • +
  • Forgejo — server git con CI integrata (Actions).
  • +
  • Traefik — reverse proxy e gateway TLS.
  • +
  • PostgreSQL — database relazionale (contenuto in una + rete interna, non esposto).
  • +
  • Adminer — interfaccia web leggera per PostgreSQL.
  • +
  • Authelia — login unico (SSO) con 2FA per i servizi + riservati.
  • +
  • Hermes e OmniRoute — agenti e proxy + per l'integrazione con modelli di linguaggio.
  • +
  • Infrastruttura descritta anche tramite Ansible e + documentata sul server.
  • +
+ +

Servizi pubblici

+ + + + + + { + services.map((s) => ( + + + + + + )) + } + +
ServizioIndirizzoCosa fa
{s.name}{s.url.replace(/^https?:\/\//, '')}{s.desc}
+

+ I servizi con SSO (dashboard, Adminer, IDE, Logseq e + Forgejo) si sbloccano dal portale auth.…nip.io con una sola + password + 2FA: le credenziali non vengono mai pubblicate su questo sito. +

+ +

Sicurezza

+
    +
  • HTTPS attivo su tutti i servizi pubblici con + certificati Let's Encrypt rinnovati automaticamente.
  • +
  • I servizi riservati sono protetti da un login unico + (SSO) con doppia autenticazione.
  • +
  • Le porte di servizi interni (database, API) non sono raggiungibili da + internet: solo rete interna o localhost.
  • +
  • fail2ban protegge l'accesso SSH dal brute-force.
  • +
  • Backup giornalieri e procedure di disaster recovery documentate.
  • +
+ +

← Torna alla home

+
+ + diff --git a/src/pages/posts/[...slug].astro b/src/pages/posts/[...slug].astro new file mode 100644 index 0000000..889c107 --- /dev/null +++ b/src/pages/posts/[...slug].astro @@ -0,0 +1,23 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro'; +import PostLayout from '../../layouts/PostLayout.astro'; +import { getCollection } from 'astro:content'; +import { getSortedPosts } from '../../lib/posts'; + +export async function getStaticPaths() { + const posts = getSortedPosts(await getCollection('blog')); + return posts.map((post) => ({ + params: { slug: post.slug }, + props: { post }, + })); +} + +const { post } = Astro.props; +const { Content } = await post.render(); +--- + + + + + + diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts new file mode 100644 index 0000000..f460714 --- /dev/null +++ b/src/pages/rss.xml.ts @@ -0,0 +1,18 @@ +import rss from '@astrojs/rss'; +import { getCollection } from 'astro:content'; +import { SITE_TITLE, SITE_DESCRIPTION } from '../consts'; + +export async function GET(context: any) { + const posts = await getCollection('blog'); + return rss({ + title: SITE_TITLE, + description: SITE_DESCRIPTION, + site: context.site, + items: posts.map((post) => ({ + title: post.data.title, + description: post.data.description, + pubDate: post.data.pubDate, + link: `/posts/${post.slug}/`, + })), + }); +} diff --git a/src/pages/servizi.astro b/src/pages/servizi.astro new file mode 100644 index 0000000..1045626 --- /dev/null +++ b/src/pages/servizi.astro @@ -0,0 +1,134 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro'; + +const servizi = [ + { + slug: 'auth', + name: 'Login unico (SSO)', + url: 'https://auth.194.164.167.80.nip.io/', + desc: 'Portale di accesso con una sola password + doppia autenticazione (2FA). Sblocca tutti i servizi riservati.', + tags: ['accesso'], + }, + { + slug: 'ide', + name: 'IDE VSCodium + RooCode', + url: 'https://ide.194.164.167.80.nip.io/', + desc: 'Editor di codice in browser (VSCodium) con l\'assistente AI RooCode collegato a OmniRoute. Accesso con SSO.', + tags: ['editor', 'AI'], + }, + { + slug: 'logseq', + name: 'Logseq', + url: 'https://logseq.194.164.167.80.nip.io/', + desc: 'Appunti e conoscenza in formato testo (outliner) con note tracciate su Forgejo. Accesso con SSO.', + tags: ['note', 'wiki'], + }, + { + slug: 'git', + name: 'Forgejo — git', + url: 'https://git.194.164.167.80.nip.io/', + desc: 'Server git con CI integrata (Actions): è il cuore dei repository, incluso questo sito.', + tags: ['git', 'CI'], + }, + { + slug: 'adminer', + name: 'Adminer + PostgreSQL', + url: 'https://adminer.194.164.167.80.nip.io/', + desc: 'Amministrazione del database PostgreSQL da browser. Accesso con SSO.', + tags: ['database'], + }, + { + slug: 'dashboard', + name: 'Dashboard Traefik', + url: 'https://traefik.194.164.167.80.nip.io/dashboard/', + desc: 'Vista del reverse proxy: rotte attive, certificati e stato dei servizi. Accesso con SSO.', + tags: ['infra'], + }, + { + slug: 'blog', + name: 'Questo blog', + url: 'https://blog.194.164.167.80.nip.io/', + desc: 'Il sito che stai leggendo: statico, generato con Astro e pubblicato automaticamente via CI.', + tags: ['web'], + }, +]; +--- + + +

Servizi di my-vps

+

+ Tutti i servizi sono raggiungibili da questa pagina. I servizi riservati + si sbloccano con un login unico (SSO): una sola password + + doppia autenticazione, gestita dal portale + auth.…nip.io. +

+ + + +

Accesso rapido

+ + + + + + { + servizi.map((s) => ( + + + + + + )) + } + +
ServizioIndirizzoAccesso
{s.name}{s.url.replace(/^https?:\/\//, '')}{s.tags.includes('editor') || s.tags.includes('note') || s.tags.includes('database') || s.tags.includes('infra') || s.tags.includes('accesso') ? 'SSO + 2FA' : 'pubblico'}
+ +

+ Vedi anche la pagina Infrastruttura per + server, tecnologie e sicurezza nel loro insieme. +

+

← Torna alla home

+
+ + diff --git a/src/pages/servizi/adminer.astro b/src/pages/servizi/adminer.astro new file mode 100644 index 0000000..fb80b59 --- /dev/null +++ b/src/pages/servizi/adminer.astro @@ -0,0 +1,61 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro'; +--- + + +

Adminer + PostgreSQL

+

+ my-vps offre un database PostgreSQL e un'interfaccia di + amministrazione comoda da browser (Adminer). +

+ +

Cosa è

+
    +
  • PostgreSQL 17 gira in un container dedicato, in una + rete interna separata: non è mai esposto su internet.
  • +
  • Adminer è un frontend leggero per gestire il database + (tabelle, query, utenti, backup) da qualunque browser.
  • +
  • Adatto ad applicazioni e sperimentazioni che hanno bisogno di un + database vero, persistente e di cui si fa il backup automatico.
  • +
+ +

Come accedere

+ + + + + + +
Indirizzohttps://adminer.194.164.167.80.nip.io/
Accessologin unico (SSO Authelia) + in pagina il login del database: le credenziali sono nella documentazione privata del server.
Host databasepostgres (rete interna db), porta 5432 locale
+

+ Alla prima apertura Adminer reindirizza al portale di login + (auth.…nip.io); poi si inserisce host/utente/password del + database (documentazione privata del server). +

+ +

Sicurezza

+
    +
  • Il database ascolta solo su 127.0.0.1:5432 e sulla rete + interna: irraggiungibile da internet.
  • +
  • Adminer è protetto dal SSO (login unico + 2FA) e da HTTPS (Let's Encrypt).
  • +
  • Autenticazione del database con scram-sha-256 (richiesta + anche in locale).
  • +
  • Backup automatico del database (dump logico) con il backup giornaliero + del server.
  • +
+ +

← Tutti i servizi · Home

+
+ + diff --git a/src/pages/servizi/auth.astro b/src/pages/servizi/auth.astro new file mode 100644 index 0000000..860d13f --- /dev/null +++ b/src/pages/servizi/auth.astro @@ -0,0 +1,57 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro'; +--- + + +

Login unico (SSO)

+

+ Un solo accesso per tutti i servizi riservati: una password + + doppia autenticazione (codice temporaneo). Il portale è + gestito da Authelia, il servizio SSO di my-vps. +

+ +

Cosa è

+
    +
  • Authelia è il "portone" dell'infrastruttura: chi si + autentica qui ottiene l'accesso a dashboard, amministrazione, IDE e note + senza ripetere il login.
  • +
  • La protezione è doppia: password più codice temporaneo + (TOTP) da un'app authenticator.
  • +
  • Ogni servizio riservato, quando aperto senza login, reindirizza a + questo portale.
  • +
+ +

Come si usa

+ + + + + + +
Indirizzohttps://auth.194.164.167.80.nip.io/
Primo accessoaprire uno dei servizi riservati (es. dashboard) → si viene reindirizzati qui → password + codice 2FA.
Dopo il logindashboard, Adminer, IDE, Logseq e Forgejo sono sbloccati per la sessione.
+ +

Sicurezza

+
    +
  • Una sola coppia di credenziali da custodire (password master + app + authenticator), invece di una password per ogni servizio.
  • +
  • Il portale gira in un container isolato nella rete interna: non è mai + esposto al di fuori del reverse proxy.
  • +
  • Le credenziali non vengono pubblicate su questo sito: sono nella + documentazione privata del server.
  • +
+ +

← Tutti i servizi · Home

+
+ + diff --git a/src/pages/servizi/dashboard.astro b/src/pages/servizi/dashboard.astro new file mode 100644 index 0000000..a3e6c55 --- /dev/null +++ b/src/pages/servizi/dashboard.astro @@ -0,0 +1,57 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro'; +--- + + +

Dashboard Traefik

+

+ Il pannello del reverse proxy che riceve tutto il traffico + e lo instrada verso i container. +

+ +

Cosa è

+
    +
  • Traefik è il gateway di my-vps: accetta le richieste + su HTTP/HTTPS e le consegna al servizio giusto in base al nome + (es. git.… → Forgejo, blog.… → Astro).
  • +
  • Termina il TLS e rinnova automaticamente i + certificati Let's Encrypt.
  • +
  • La dashboard mostra rotte, servizi e stato di configurazione in tempo + reale.
  • +
+ +

Come accedere

+ + + + + +
Indirizzohttps://traefik.194.164.167.80.nip.io/dashboard/
Accessologin unico (SSO Authelia): password + codice 2FA, una sola volta per tutti i servizi riservati. Le credenziali sono nella documentazione privata del server.
+

+ Alla prima apertura il servizio reindirizza al portale di login + (auth.…nip.io): dopo l'autenticazione si torna qui. +

+ +

Cosa ci vedi

+
    +
  • Le rotte HTTP/HTTPS attive (blog, git, adminer, ide, + logseq, dashboard).
  • +
  • I certificati TLS e le loro scadenze.
  • +
  • Lo stato dei servizi (healthy/unhealthy).
  • +
+ +

← Tutti i servizi · Home

+
+ + diff --git a/src/pages/servizi/git.astro b/src/pages/servizi/git.astro new file mode 100644 index 0000000..479bee0 --- /dev/null +++ b/src/pages/servizi/git.astro @@ -0,0 +1,60 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro'; +--- + + +

Forgejo — git

+

+ Il server git self-hosted di my-vps, con CI integrata + (Actions) che pubblica automaticamente questo blog. +

+ +

Cosa è

+
    +
  • Hosting di repository git (pubblici e privati) in un container dietro + Traefik.
  • +
  • Integra Forgejo Actions: un runner esegue i workflow + definiti nei repository (es. il deploy del blog a ogni push).
  • +
  • Hosting "first-party": il codice e le Actions non dipendono da servizi + esterni.
  • +
+ +

Come accedere

+ + + + + + + +
Indirizzohttps://git.194.164.167.80.nip.io/
Login webSSO: pulsante "Sign in with Authelia" (password + 2FA del login unico).
Clone SSH/HTTPSgit clone http://git.194.164.167.80.nip.io/<utente>/<repo>.git
Registrazionedisabilitata: gli account si creano solo dall'amministratore.
+ +

Repository attivi

+
    +
  • astro-blog — sorgente di questo sito (deploy automatico via Actions).
  • +
  • roo-workspace — workspace dell'IDE VSCodium (sync automatica).
  • +
  • logseq-notes — note di Logseq (sync automatica).
  • +
+ +

Perché self-hosted

+
    +
  • I repository restano sul server, senza limiti o sorveglianza di terze + parti.
  • +
  • La CI è locale: i workflow girano nel runner di my-vps.
  • +
  • È il punto di aggancio di molti altri servizi (workspace, blog).
  • +
+ +

← Tutti i servizi · Home

+
+ + diff --git a/src/pages/servizi/ide.astro b/src/pages/servizi/ide.astro new file mode 100644 index 0000000..e3bc6cc --- /dev/null +++ b/src/pages/servizi/ide.astro @@ -0,0 +1,72 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro'; +--- + + +

IDE VSCodium + RooCode

+

+ Un editor di codice completo, VSCodium (la build aperta di + VS Code), eseguito in un container e usato dal browser. +

+ +

Cosa è

+
    +
  • L'editor gira come container (vscodium) gestito da + systemd, dietro il reverse proxy Traefik.
  • +
  • L'interfaccia è un desktop in browser (KasmVNC): VSCodium si apre nella + finestra del browser, senza installare nulla in locale.
  • +
  • Include l'estensione RooCode (assistente AI di + programmazione) già configurata per usare OmniRoute come + endpoint di modelli.
  • +
+ +

Come accedere

+ + + + + + +
Indirizzohttps://ide.194.164.167.80.nip.io/
Accessosolo login unico (SSO Authelia): niente altre password.
Workspace/config/workspace — repository roo-workspace su Forgejo.
+

+ Alla prima apertura il servizio reindirizza al portale di login + (auth.…nip.io); poi il desktop si apre direttamente. +

+ +

Workspace tracciato con git

+
    +
  • La cartella di lavoro è un repository git (privato) su + Forgejo: forgejo-admin/roo-workspace.
  • +
  • Le modifiche vengono committate e caricate automaticamente ogni 15 + minuti (timer git-sync.timer), quindi il lavoro è sempre + salvato e versionato.
  • +
  • Si può clonare il workspace da qualsiasi macchina per lavorarci anche + fuori dal browser.
  • +
+ +

RooCode e OmniRoute

+
    +
  • RooCode è configurato con un provider "OpenAI Compatible" che punta a + http://omniroute:20128/v1 (solo rete interna del server, + mai esposto).
  • +
  • Modello predefinito: auto/best-coding (scelto da OmniRoute + fra i modelli disponibili).
  • +
  • La configurazione si può cambiare in RooCode → Settings → Providers + senza toccare il server.
  • +
+ +

← Tutti i servizi · Home

+
+ + diff --git a/src/pages/servizi/logseq.astro b/src/pages/servizi/logseq.astro new file mode 100644 index 0000000..1e29ed8 --- /dev/null +++ b/src/pages/servizi/logseq.astro @@ -0,0 +1,72 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro'; +--- + + +

Logseq

+

+ Logseq è un'applicazione per prendere appunti in stile + "outliner": tutto è testo collegato, cercabile e versionabile. Qui gira in + un container e si usa dal browser. +

+ +

Cosa è

+
    +
  • Logseq desktop eseguito in un container (logseq) gestito + da systemd, con accesso in browser tramite KasmVNC.
  • +
  • Le note vivono come file Markdown nella cartella + notes del server: niente database proprietario, il contenuto + resta tuo e portabile.
  • +
  • Struttura standard Logseq: pages/, journals/, + assets/.
  • +
+ +

Come accedere

+ + + + + + +
Indirizzohttps://logseq.194.164.167.80.nip.io/
Accessosolo login unico (SSO Authelia): niente altre password.
Note/config/notes — repository logseq-notes su Forgejo.
+

+ Alla prima apertura il servizio reindirizza al portale di login + (auth.…nip.io); poi il desktop si apre direttamente. +

+ +

Note tracciate con git

+
    +
  • La cartella delle note è un repository git (privato) su + Forgejo: forgejo-admin/logseq-notes.
  • +
  • Le modifiche vengono committate e caricate automaticamente ogni 15 + minuti (timer git-sync.timer): cronologia completa di ogni + nota, ripristinabile in qualsiasi momento.
  • +
  • Si può clonare il repository per leggere le note anche senza aprire + Logseq.
  • +
+ +

Perché testo + git

+
    +
  • I file Markdown si aprono con qualunque editor e sono facili da + migrare.
  • +
  • Il versionamento dà sicurezza: niente perdita di appunti e possibilità + di tornare a qualsiasi stato precedente.
  • +
  • La conoscenza resta sul server (self-hosted), senza dipendere da + servizi esterni.
  • +
+ +

← Tutti i servizi · Home

+
+ + diff --git a/src/styles/global.css b/src/styles/global.css new file mode 100644 index 0000000..ac43a9a --- /dev/null +++ b/src/styles/global.css @@ -0,0 +1,3 @@ +html { -webkit-text-size-adjust: 100%; } +body { margin: 0; } +time { font-variant-numeric: tabular-nums; } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d78f81e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "astro/tsconfigs/base" +}