- PHP 82.6%
- CSS 11.3%
- JavaScript 5.8%
- Dockerfile 0.3%
| data | ||
| deploy | ||
| public | ||
| src | ||
| tools | ||
| .env.example | ||
| .gitignore | ||
| .htaccess | ||
| LICENSE | ||
| README.md | ||
Written Whisper
Blog personal simplu, rapid si sigur. Scris in PHP pur, fara framework, fara dependente externe. Functioneaza pe orice hosting cu PHP si SQLite.
Suporta trei tipuri de continut: articole, note scurte si fotografii.
Cuprins
- Cerinte
- Structura proiectului
- Instalare pe shared hosting
- Instalare pe VPS cu nginx
- Instalare pe VPS cu Apache
- Instalare cu Docker
- Configurare din browser
- Configurare avansata
- Utilizare
- Markdown
- Teme
- Comentarii
- Fediverse / ActivityPub
- Import din alte platforme
- Unelte CLI
- Functionalitati
- Backup si restaurare
- Securitate
Cerinte
- PHP 8.0 sau mai nou
- Extensia PDO SQLite (activa implicit pe majoritatea hostingurilor)
- Extensia fileinfo (pentru validarea uploadurilor de imagini)
- Apache cu mod_rewrite sau nginx
Structura proiectului
written-whisper/
|
|- public/ <- DOCUMENT ROOT (singurul director expus web)
| |- index.php <- pagina principala cu feed
| |- router.php <- router pentru URL-uri curate (/slug) + 404 custom
| |- search.php <- cautare
| |- setup.php <- configurare initiala din browser
| |- random.php <- redirect spre articol aleatoriu
| |- arhiva.php <- arhiva completa grupata pe luna/an
| |- 404.php <- pagina 404 custom cu articole aleatorii
| |- webmention.php <- endpoint primire webmentions
| |- media.php <- serveste imaginile din data/uploads/
| |- feed.php <- RSS
| |- atom.php <- Atom feed
| |- feed-json.php <- JSON Feed 1.1
| |- sitemap.php <- sitemap XML pentru Google
| |- post.php <- redirect permanent spre URL curat
| |- page.php <- redirect permanent spre URL curat
| |- favicon.svg <- favicon implicit (inlocuibil din admin)
| |- logo.svg
| |- .htaccess <- URL-uri curate + securitate + ErrorDocument 404
| |- uploads/ <- logo si favicon (trebuie sa fie writable)
| |- assets/
| | |- css/
| | | |- main.css <- stiluri site public (teme, layout, scrollbar, lightbox)
| | | `- admin.css <- stiluri panou admin
| | `- js/
| | |- upload.js <- upload imagini cu compresie (WebP + fallback JPEG)
| | |- theme.js <- comutator de teme (light/dark)
| | `- main.js <- scroll-to-top, bara progres, lightbox, copy link
| |- partials/
| | |- header.php <- header site (logo + titlu + navigare + teme)
| | `- footer.php <- footer site (feed-uri + contor online)
| `- admin/
| |- index.php <- panou de administrare
| `- upload.php <- endpoint AJAX upload imagini
|
|- src/ <- cod PHP (NU este accesibil din browser)
| |- config.php <- configurare site + incarcare settings.json
| |- db.php <- baza de date, CRUD, comentarii, statistici
| |- security.php <- CSRF, sesiuni, rate limiting, validare upload
| |- helpers.php <- h(), fmt_date() (RO), body_to_html(), reading_time(), highlight_code(), webmention
| `- markdown.php <- parser Markdown complet (GFM + extensii)
|
|- data/ <- stocare date (NU este accesibil din browser)
| |- blog.db <- baza de date SQLite (creat automat)
| |- posts/ <- fisiere .md exportate automat la salvare postari
| |- pages/ <- fisiere .md exportate automat la salvare pagini
| |- uploads/ <- imaginile din articole/note/fotografii (trebuie sa fie writable)
| `- settings.json <- setari editabile din admin
|
|- tools/
| |- README.md <- documentatie unelte CLI
| |- generate_hash.php <- genereaza hash bcrypt pentru parola
| |- import_markdown.php <- importa orice fisiere .md cu YAML front matter
| |- migrate_pureblog.php <- migrare dedicata Pure Blog -> Written Whisper
| |- export_markdown.php <- genereaza fisiere .md din baza de date
| `- import_pureblog_comments.php <- importa comentariile din PureComments
|
|- deploy/
| |- docker/ <- Dockerfile, docker-compose.yml, php.ini
| |- vps/ <- nginx.conf, apache.conf
| `- shared/
| `- .htaccess <- .htaccess pentru shared hosting (root)
|
|- .htaccess <- redirect spre public/ (shared hosting)
`- .env.example <- template pentru variabile de mediu
Instalare pe shared hosting
Metoda cea mai simpla, compatibila cu orice hosting cPanel/Plesk.
Nota ActivityPub:
public/.htaccessinclude deja o directiva care dezactiveaza ModSecurity pentru endpoint-urile ActivityPub (/inbox,/outbox,/actor). Fara aceasta, unele hosturi blocheaza POST-urile cu corp JSON de la servere federate (Mastodon, Pixelfed), impiedicand urmaritorii sa se conecteze.
Pasul 1 - Incarca fisierele
Prin FTP sau File Manager incarca pe server:
public/ -> in webroot-ul domeniului (ex: public_html/public/)
src/ -> UN NIVEL DEASUPRA webroot-ului (ex: public_html/src/)
data/ -> UN NIVEL DEASUPRA webroot-ului (ex: public_html/data/)
Structura finala pe server:
public_html/
|- .htaccess <- fisierul .htaccess din radacina proiectului
|- public/
|- src/
`- data/
Pasul 2 - Incarca .htaccess-ul din radacina
Incarca fisierul .htaccess din radacina proiectului direct in public_html/.
Acesta redirecteaza automat toate cererile spre directorul public/.
Alternativa mai buna: In cPanel mergi la Domains si schimba Document Root din
public_htmlinpublic_html/public. Astfel nu mai ai nevoie de .htaccess-ul din radacina.
Pasul 3 - Permisiuni
Asigura-te ca aceste directoare au permisiunile 755:
public_html/data/public_html/data/uploads/public_html/public/uploads/
In cPanel File Manager: click dreapta pe director → Change Permissions → 755.
Pasul 4 - Configureaza din browser
Mergi la https://domeniultau.ro/setup.php si completeaza:
- URL site - adresa completa a blogului
- Utilizator admin - numele de login
- Parola - minim 8 caractere (salvata criptat automat)
Dupa salvare esti redirectionat automat la panoul de admin.
Daca preferi configurarea manuala, editeaza
src/config.phpvia FTP (vezi sectiunea Configurare avansata).
Pasul 5 - Gata
Mergi la https://domeniultau.ro - blog-ul functioneaza.
Admin: https://domeniultau.ro/admin/
Instalare pe VPS cu nginx
1. Copiaza proiectul pe server
git clone https://github.com/user/written-whisper.git /var/www/written-whisper
# sau copiaza prin SCP/SFTP
2. Permisiuni
chown -R www-data:www-data /var/www/written-whisper/data
chown -R www-data:www-data /var/www/written-whisper/public/uploads
chmod 755 /var/www/written-whisper/data
chmod 755 /var/www/written-whisper/data/uploads
chmod 755 /var/www/written-whisper/public/uploads
3. Configureaza nginx
cp deploy/vps/nginx.conf /etc/nginx/sites-available/written-whisper
nano /etc/nginx/sites-available/written-whisper
ln -s /etc/nginx/sites-available/written-whisper /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Configuratia esentiala:
server {
listen 80;
server_name domeniultau.ro;
root /var/www/written-whisper/public;
index index.php;
location / {
try_files $uri $uri/ /router.php?_slug=$uri;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
}
}
4. SSL cu Let's Encrypt
apt install certbot python3-certbot-nginx
certbot --nginx -d domeniultau.ro -d www.domeniultau.ro
5. Configureaza
Mergi la https://domeniultau.ro/setup.php pentru configurare initiala.
Instalare pe VPS cu Apache
1. Module necesare
a2enmod rewrite headers expires
systemctl restart apache2
2. VirtualHost
cp deploy/vps/apache.conf /etc/apache2/sites-available/written-whisper.conf
nano /etc/apache2/sites-available/written-whisper.conf
a2ensite written-whisper
systemctl reload apache2
Configuratia esentiala:
<VirtualHost *:80>
ServerName domeniultau.ro
DocumentRoot /var/www/written-whisper/public
<Directory /var/www/written-whisper/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
3. SSL
certbot --apache -d domeniultau.ro
Instalare cu Docker
Rapid (development)
git clone https://github.com/user/written-whisper.git
cd written-whisper
cp .env.example .env
# Editeaza .env cu datele tale
docker compose -f deploy/docker/docker-compose.yml up -d
Acceseaza: http://localhost:8080
Variabile de mediu (.env)
SITE_TITLE=Written Whisper
SITE_DESCRIPTION=Blogul meu personal
SITE_URL=http://localhost:8080
ADMIN_USER=admin
ADMIN_PASS=$2y$12$hash_generat_cu_tools/generate_hash.php
PORT=8080
Productie cu Docker
# Genereaza hash pentru parola
docker run --rm php:8.2-cli php -r "echo password_hash('parola_ta', PASSWORD_BCRYPT);"
# Porneste containerul
docker compose -f deploy/docker/docker-compose.yml up -d
Configurare din browser
setup.php - configurare initiala
La prima instalare, mergi la /setup.php. Pagina cere:
| Camp | Descriere |
|---|---|
| URL site | Adresa completa a blogului (ex: https://example.com) |
| Utilizator admin | Numele de login in panoul de admin |
| Parola | Minim 8 caractere - salvata automat ca hash bcrypt |
Dupa salvare, /setup.php redirectioneaza automat la /admin/ si nu mai este
accesibil odata ce credentialele sunt configurate.
Daca rulezi cu variabile de mediu (
ADMIN_USERsetat in.envsau pe server),setup.phpredirectioneaza direct la admin.
Schimbarea setarilor din admin
Din /admin/ → Setari (trei sub-pagini):
Platformă (?section=settings):
| Camp | Descriere |
|---|---|
| Logo | Incarca o imagine noua |
| Favicon | Incarca un favicon (recomandat: png patrat 64×64 px) |
| Numele blogului | Titlul afisat in browser si in header |
| Descriere | Apare sub logo si in meta tags |
| URL site | Adresa blogului, folosita in RSS/Atom si Open Graph |
| Data lansarii | Afisata in footer ca „Online din X mai 2026 · N zile" |
| Postari pe pagina | Numarul de postari afisate pe pagina principala (implicit 10) |
Fediverse (?section=settings-fedi):
| Camp | Descriere |
|---|---|
| Banner | Imagine de antet pentru profilul Fediverse (1500×500px) |
| Avatar | Imagine rotunda de profil; fallback pe logo-ul platformei |
| Username | Handle-ul in Fediverse (ex: blog) — handle complet: @blog@domeniu.ro |
| Bio | Descriere de profil in Mastodon/Pixelfed, independenta de descrierea blogului |
| Profil extern Mastodon | URL profil extern, pentru rel="me" si fediverse:creator |
Sub formularul Fediverse apare lista abonatilor actuali cu avatarul, numele si data abonarii.
Credentiale (?section=settings-creds):
Schimba utilizatorul si parola fara acces FTP - parola curenta este ceruta ca verificare.
Configurare avansata
Fisierul src/config.php
data/settings.json are prioritate fata de valorile din config.php si fata de
variabilele de mediu - configurarea din browser suprascrie fisierul.
define('SITE_TITLE', 'Numele blogului');
define('SITE_DESCRIPTION', 'O descriere scurta');
define('SITE_URL', 'https://example.com');
define('ADMIN_USER', 'admin');
define('ADMIN_PASS', '$2y$12$abc...'); // hash bcrypt
define('POSTS_PER_PAGE', 10);
Ordinea prioritatilor
data/settings.json (cel mai prioritar - configurat din browser)
↓
variabile de mediu (SITE_URL, ADMIN_USER, ADMIN_PASS etc.)
↓
valorile hardcodate din src/config.php (fallback)
Generarea unui hash bcrypt pentru parola
php tools/generate_hash.php parola-ta-secreta
Utilizare
URL-uri publice
| URL | Descriere |
|---|---|
/ |
Feed principal cu toate postarile |
/titlul-articolului-1 |
Articol individual (URL curat) |
/nota-42 |
Nota individuala |
/foto-43 |
Fotografie individuala |
/despre |
Pagina custom |
/arhiva.php |
Arhiva completa grupata pe luna si an |
/random.php |
Redirectioneaza spre un articol aleatoriu |
/search.php?q=cuvant |
Cautare |
/webmention.php |
Endpoint primire webmentions (POST) |
/feed.php |
RSS |
/atom.php |
Atom feed |
/feed-json.php |
JSON Feed 1.1 |
/sitemap.php |
Sitemap XML pentru Google |
Schema URL-urilor
Slug-urile sunt generate automat cu transliterare - diacriticele romanesti sunt
convertite la ASCII (ș→s, ț→t, â→a, î→i, ă→a):
- Articole:
/titlul-articolului-1(slug din titlu + ID) - Note:
/nota-42(prefixnota-+ ID) - Fotografii:
/foto-43(prefixfoto-+ ID) - Pagini custom:
/despre(slug din titlu)
Panoul de admin (/admin/)
Postari
Trei tipuri de continut:
- Articol - titlu + text lung cu suport Markdown complet
- Nota - text scurt, ca un tweet sau un gand
- Foto - o imagine cu descriere optionala
Fiecare postare poate fi:
- Publicata - vizibila pe site
- Draft - salvata dar invizibila; buton previzualizare disponibil doar pentru admin
- Programata - se publica automat la data si ora stabilita; pana atunci e invizibila
Previzualizare draft
Cand esti logat ca admin, poti accesa URL-ul unui draft direct din browser sau prin butonul previzualizare din tabelul de postari. Vizitatorii obisnuiti primesc 404. Pe pagina de previzualizare, butonul Inapoi la admin duce inapoi la dashboard.
Pagini
Pagini statice configurabile din admin. Campuri disponibile:
| Camp | Descriere |
|---|---|
| Titlu | Titlul paginii si textul din meniu |
| Slug | URL-ul paginii (generat automat din titlu) |
| Continut | Text cu suport Markdown complet |
| Ordine meniu | Numarul de ordine in navigare (0, 1, 2...) |
| Afiseaza in meniu | Bifat = pagina apare in navigare; debifat = pagina exista dar nu apare in meniu |
Paginile ascunse din meniu sunt marcate cu [ascuns din meniu] in lista din admin.
La fiecare salvare, pagina este exportata automat si ca fisier .md in data/pages/.
Comentarii
Pagina Comentarii are trei sub-pagini:
- Platformă (
?section=comments) — comentariile lasate direct pe blog; butoane Aproba / Raspunde / Sterge; raspunsul adminului apare auto-aprobat - Webmention (
?section=comments-wm) — webmention-uri primite; Aproba / Sterge; afiseaza sursa, autorul si un extras - Fediverse (
?section=comments-fedi) — reply-uri primite din Mastodon/Pixelfed/etc.; acelasi flux + livrare AP automata la raspuns
Badge-ul din bara de navigare arata suma tuturor comentariilor in asteptare din toate sursele.
Statistici
Vizitatori unici per IP, fara boți. Filtrati automat: Googlebot, Bingbot, crawlerele SEO (SEMrush, Ahrefs), scanerele de securitate (Shodan, Censys), monitorele de uptime, cererile fara User-Agent.
Dashboard (sectiunea Postari) - carduri rezumative:
| Card | Descriere |
|---|---|
| Vizitatori azi | IP-uri unice in ziua curenta |
| Vizitatori 30 zile | IP-uri unice in ultimele 30 de zile |
| Vizitatori 1 an | IP-uri unice in ultimul an |
| Vizitatori total | Toate IP-urile unice din istoricul complet |
Pagina Statistici (?section=stats) - detalii pe ultimele 30 de zile:
| Coloana | Continut |
|---|---|
| Sursa | Top referreri (domeniul din care a venit vizitatorul), cu favicon |
| Post | Top postari dupa numarul de vizualizari |
Datele nu expira niciodata.
Header
- Logo - in stanga, linking spre pagina principala
- Titlu si descriere - centrate, pe acelasi rand cu logo-ul
- ⚄ - buton articol aleatoriu (langa butoanele de tema)
- ☾ / ☀ - comuta intre dark si light
- ◧ - picker cu toate temele disponibile
Inserarea imaginilor
In articole, note si pagini, apasa Incarca imagine. Imaginea este comprimata automat in browser inainte de upload:
- Desktop / Android: comprima in WebP (max 1920px, calitate 82%)
- iOS: fallback automat la JPEG (WebP encoding nu e suportat de iOS)
Sintaxa Markdown pentru imagini: 
Markdown
Parserul Markdown este scris intern, fara dependente externe. Suporta sintaxa standard plus extensiile GFM (GitHub Flavored Markdown).
Formatare de baza
| Sintaxa | Rezultat |
|---|---|
# Titlu |
Heading H1 |
## Subtitlu |
Heading H2 (pana la H6) |
Titlu\n====== |
Heading H1 (stil Setext) |
Titlu\n------ |
Heading H2 (stil Setext) |
**bold** sau __bold__ |
bold |
*italic* sau _italic_ |
italic |
***bold italic*** sau ___bold italic___ |
bold italic |
~~taiat~~ |
taiat |
`cod inline` |
cod inline |
[text](url) |
link (extern = tab nou, intern = acelasi tab) |
 |
imagine inline |
--- sau *** sau ___ |
linie orizontala |
Blocuri
| Sintaxa | Rezultat |
|---|---|
```limba\ncod\n``` |
bloc de cod cu evidentierea limbii |
~~~limba\ncod\n~~~ |
bloc de cod (varianta cu tilde) |
> citat |
blockquote |
> > citat nested |
blockquote imbricat |
- element sau * element |
lista neordonata |
1. element |
lista ordonata |
- sub-element |
lista nested (indentare 2+ spatii) |
- [ ] sarcina |
task list nebifata |
- [x] sarcina |
task list bifata |
Tabele GFM
| Coloana 1 | Coloana 2 | Coloana 3 |
|:----------|:---------:|----------:|
| stanga | centru | dreapta |
| text | text | text |
Alinierea se seteaza cu : in randul separator: :--- stanga, :---: centru, ---: dreapta.
Altele
| Sintaxa | Rezultat |
|---|---|
| Doua spatii la sfarsit de linie | line break dur (<br>) |
\ la sfarsit de linie |
line break dur (<br>) |
\*, \_, \\ etc. |
caracter literal (backslash escape) |
©, ,   |
entitati HTML (trec nemodificate) |
<https://example.com> |
autolink |
 singur pe linie |
figura cu legenda |
[^1] in text + [^1]: text pe linie separata |
nota de subsol (footnote) cu back-link ↩ |
Link-urile externe (http://, https://) se deschid automat in tab nou.
Link-urile interne (cu /) raman in acelasi tab.
Teme
Site-ul detecteaza automat preferinta de tema a sistemului de operare (light/dark).
Comutator manual
In header-ul site-ului apar trei butoane:
- ⚄ - articol aleatoriu
- ☾ / ☀ - comuta intre dark si light
- ◧ - deschide picker-ul cu toate temele
Picker-ul se pozitioneaza automat in functie de locul butonului pe ecran, fara a iesi din viewport pe mobile.
Teme disponibile
| Tema | Descriere |
|---|---|
| Auto | Urmeaza preferinta OS |
| Luminos | Fundal deschis, clasic |
| Cald | Tonuri calde, crem |
| Hartie | Aspect de ziar vechi |
| E-ink | Gri monocrom, stil ecran electronic |
| Intunecat | Dark mode standard |
| Adanc | Dark mode profund (GitHub-like) |
| Terminal | Verde pe negru |
Preferinta se salveaza in localStorage si persista intre vizite.
Fediverse / ActivityPub
Written Whisper implementeaza protocolul ActivityPub complet, transformand blogul intr-un nod federat in Fediverse. Postarile ajung direct la cititorii care te urmaresc din Mastodon, Pixelfed, Lemmy, Loops sau orice platforma compatibila ActivityPub.
Handle Fediverse
Handle-ul tau in Fediverse este @username@domeniu.ro, configurabil din
Admin → Setari → Fediverse → Username.
Oricine poate sa te urmeze tastand handle-ul complet in bara de cautare a platformei
lor (Mastodon, Pixelfed etc.). Cautarea dupa username scurt (fara @domeniu) functioneaza
doar pe instantele care au descoperit deja contul anterior.
Endpoint-uri ActivityPub
| URL | Descriere |
|---|---|
/.well-known/webfinger |
Descoperire cont (RFC 7033) |
/.well-known/nodeinfo |
Descoperire server (NodeInfo 2.0) |
/.well-known/host-meta |
Descoperire OStatus/Diaspora (XML) |
/.well-known/host-meta.json |
Descoperire OStatus/Diaspora (JSON) |
/nodeinfo/2.0 |
Informatii server (software, protocoale, postari) |
/actor |
Profilul Actor JSON-LD |
/actor/followers |
Colectie abonati |
/actor/following |
Colectie urmariți (goala - blogul nu urmareste pe nimeni) |
/actor/featured |
Postari fixate (momentan goala) |
/inbox |
Primire activitati ActivityPub (Follow, Undo, Create) |
/outbox |
Servire activitati publice (postari ca Create Note) |
Ce se federalizeaza
| Tip postare | Format in Fediverse |
|---|---|
| Nota | Note — postare text standard |
| Articol | Note cu titlul bold ca prim paragraf |
| Fotografie | Note cu Document attachment (imagine) |
Cand publici o postare → activitate Create livrata tuturor abonatilor.
Cand editezi → Update. Cand stergi → Delete.
Profil Fediverse
Configureaza din Admin → Setari → Fediverse:
| Camp | Descriere |
|---|---|
| Username | Handle-ul contului (@username@domeniu) |
| Bio | Descriere de profil, independenta de descrierea blogului |
| Avatar | Imagine rotunda de profil; fallback pe logo-ul platformei |
| Banner | Imagine de antet (1500×500px recomandat) |
| Profil extern | URL profil Mastodon extern, pentru rel="me" si fediverse:creator |
Securitate ActivityPub
- HTTP Signatures — toate cererile outgoing sunt semnate cu cheia RSA privata
- Verificare semnatura — Follow-urile primite sunt verificate criptografic
- Chei RSA 2048-bit — generate automat la prima rulare, stocate in
data/ap_keys.json
Comentarii din Fediverse
Reply-urile primite din Mastodon/Pixelfed/etc. la postarile tale apar automat ca comentarii in sectiunea Comentarii → Fediverse din admin, cu avatarul si linkul actorului. Se modereaza la fel ca comentariile locale (Aproba/Sterge).
Cand raspunzi unui comentariu Fediverse din admin, raspunsul este livrat
automat ca activitate Create Note (cu inReplyTo si Mention) in inbox-ul
celui care a comentat.
Comentarii
Pe site
Formularul de comentarii apare sub fiecare articol, nota si fotografie.
Campuri:
- Nume (obligatoriu)
- Email (optional, nu se afiseaza public)
- Mesaj (obligatoriu, minim 3 caractere)
Suporta raspunsuri la comentarii (un nivel de imbricare).
Moderare
Comentariile intra ca in asteptare si nu apar pe site pana la aprobare. Adminul le aproba sau sterge din sectiunea Comentarii a adminului. Adminul poate raspunde direct din admin, raspunsul apare auto-aprobat.
Stocare
Comentariile sunt salvate doar in baza de date (data/blog.db), in tabelul
comments. Nu exista fisiere .md pentru comentarii - spre deosebire de postari
si pagini, nu pot fi reconstituite din disc. La backup, data/blog.db este
singurul fisier care le contine.
Protectie anti-spam
- Token CSRF pe fiecare formular
- Camp honeypot invizibil (botii il completeaza)
- Maxim 5 comentarii pe ora per adresa IP
Import din alte platforme
Orice fisier .md cu YAML front matter poate fi importat in Written Whisper.
# Orice director cu fisiere .md:
php tools/import_markdown.php /cale/catre/posts
# Cu copiere imagini (Pure Blog, Jekyll etc.):
php tools/import_markdown.php /blog/content/posts \
--images /blog/content/images \
--images-prefix /content/images
# Dry-run (preview fara a scrie):
php tools/import_markdown.php /cale/posts --dry-run
# Re-importa postari deja existente:
php tools/import_markdown.php /cale/posts --force
# Migrare dedicata Pure Blog:
php tools/migrate_pureblog.php /cale/pureblog/content --dry-run
php tools/migrate_pureblog.php /cale/pureblog/content
# Import comentarii din PureComments:
php tools/import_pureblog_comments.php /cale/PureComments/db/comments.sqlite --dry-run
php tools/import_pureblog_comments.php /cale/PureComments/db/comments.sqlite
Front matter recunoscut automat:
| Camp | Variante acceptate |
|---|---|
| Titlu | title |
| Slug | slug, permalink, link, url |
| Data | date, created_at, published_at, ISO 8601 |
| Status | status: draft/published, draft: true, published: false |
| Tip | type: article/note/photo (default: article) |
Campurile tags, categories, description, excerpt, layout sunt ignorate.
Importul este idempotent - postarile deja existente sunt sarite (sau actualizate cu --force).
Export automat Markdown
Fiecare postare creata sau editata din admin este salvata automat ca fisier .md
in data/posts/ cu YAML front matter complet.
Fiecare pagina creata sau editata din admin este salvata automat ca fisier .md
in data/pages/. La stergere, fisierul este sters automat. La schimbarea slug-ului,
fisierul vechi este sters si creat unul nou.
Fisierele sunt portabile si pot fi importate in alte platforme care suporta Markdown.
Unelte CLI
Cinci scripturi PHP care ruleaza din linia de comanda. Nu sunt accesibile din browser.
Documentatie completa in tools/README.md.
generate_hash.php
Genereaza un hash bcrypt dintr-o parola in text clar.
php tools/generate_hash.php parola_mea_secreta
# Output: $2y$12$abc123...
import_markdown.php
Importa orice fisiere .md cu YAML front matter. Compatibil cu Pure Blog,
Jekyll, Hugo, Ghost sau fisiere scrise manual.
php tools/import_markdown.php /cale/posts [--images <dir>] [--dry-run] [--force]
migrate_pureblog.php
Wrapper specializat pentru migrarea din Pure Blog. Configureaza automat directoarele si prefixurile specifice Pure Blog.
php tools/migrate_pureblog.php /cale/pureblog/content [--dry-run]
export_markdown.php
Genereaza fisiere .md in data/posts/ pentru postarile din baza de date
care nu au inca fisier fizic. Util dupa o migrare sau pentru reconstituirea
data/posts/ dintr-un blog.db existent.
php tools/export_markdown.php # doar postarile fara fisier
php tools/export_markdown.php --force # rescrie toate fisierele
php tools/export_markdown.php --dry-run # simulare fara a scrie nimic
import_pureblog_comments.php
Importa comentariile din baza de date PureComments in Written Whisper.
Rezolva automat diferentele de slug si remapaeza relatiile parent_id.
php tools/import_pureblog_comments.php /cale/PureComments/db/comments.sqlite --dry-run
php tools/import_pureblog_comments.php /cale/PureComments/db/comments.sqlite
Functionalitati
Continut si publicare:
- Configurare din browser - setup.php pentru configurare initiala, fara FTP
- Credentiale din admin - schimba utilizator si parola din panoul de admin
- Draft cu previzualizare - drafts vizibile doar de admin, cu buton de previzualizare
- Postari programate - status "scheduled" cu data/ora de publicare; auto-publish fara cron
- Pagini custom - pagini statice cu optiune de afisare/ascundere din meniu
- Upload imagini - compresie WebP pe desktop, fallback JPEG automat pe iOS
- Export Markdown postari - fiecare postare salvata automat ca
.mdindata/posts/ - Export Markdown pagini - fiecare pagina salvata automat ca
.mdindata/pages/ - Sync automat - fisierele
.mdnoi detectate indata/posts/sunt importate automat la deschiderea adminului
Navigare si experienta vizitator:
- Arhiva -
/arhiva.phpcu toate postarile grupate pe luna si an, luni in romana - Articol aleatoriu - buton ⚄ in header redirectioneaza la o postare aleatorie
- Bara de progres citire - linie subtire in partea de sus, urmareste pozitia in articol
- Timp de citire estimat - calculat automat din numarul de cuvinte, afisat in meta post
- Lightbox imagini - click pe imaginile din articole le deschide marite, fara pagina noua
- Buton copy link - copiaza URL-ul postului in clipboard
- Pagina 404 custom - mesaj amuzant + 5 articole aleatorii recomandate
- Cautare - cauta in titlu, continut si descriere
- Buton sus - scroll to top, apare dupa 400px scroll
- Paginare - pe feed si in admin (20 postari/pagina in admin)
Markdown:
- Markdown complet - headinguri ATX si Setext, bold, italic, strikethrough, tabele GFM, liste nested, task lists, blockquote nested, line breaks dure, backslash escapes, entitati HTML
- Footnotes -
[^1]inline cu definitii[^1]: text, sectiune de note de subsol cu back-link ↩ - Syntax highlighting - colorarea codului server-side fara JS; suporta bash, php, python, yaml, json, html, css, javascript
- Linkuri externe in tab nou - linkurile interne raman in acelasi tab
Comentarii si interactiuni:
- Comentarii - sistem complet pe articole, note si fotografii, cu moderare, raspunsuri, anti-spam
- Comentarii Fediverse - reply-urile din Mastodon/Pixelfed apar ca comentarii moderate; raspuns din admin livrat AP
- Webmention - primire (endpoint
/webmention.phpcu validare sursa), trimitere automata la publicare, moderare in admin, afisare pe post
Fediverse / ActivityPub:
- Actor complet -
PersoncupublicKey,discoverable,indexable,featured,featuredTags,endpoints.sharedInbox - WebFinger - descoperire cont (
/.well-known/webfinger) - NodeInfo 2.0 - descoperire server (
/.well-known/nodeinfo,/nodeinfo/2.0) - host-meta - compatibilitate OStatus/Diaspora/Friendica (XML + JSON)
- HTTP Signatures - semnare cereri outgoing, verificare cereri incoming
- Livrare automata -
Createla publicare,Updatela editare,Deletela stergere - Update profil - schimbarea setarilor Fediverse trimite
Updateactorului catre toti abonații - Continut negociat -
/slugreturneaza JSON-LD pentru clienti AP, HTML pentru browsere - Redirect domeniu canonic - hostname-urile alternative sunt redirectionate (301) spre domeniul principal
Feed si indexare:
- RSS / Atom / JSON Feed - trei formate; includ articole, note si fotografii cu continut HTML complet
- Sitemap XML - pentru indexare Google
- SEO - Open Graph cu imagine (prima imagine din post sau logo), Twitter Card, meta description per pagina
- rel="me" - pentru verificarea profilului Fediverse (Mastodon etc.)
- fediverse:creator - meta tag pentru atribuire continut in Fediverse
Aspect:
- Teme - 8 teme (4 light + 3 dark + auto), comutare manuala, persistenta in localStorage
- Scrollbar - urmeaza culorile temei active (border/muted)
- Font monospaced - Cascadia Code / Fira Mono / system monospace
- Luni in romana - datele apar in romana (ian., feb., mai, iun. etc.)
- Responsive - functioneaza pe orice ecran (mobil, tableta, desktop)
- Dark mode auto - urmeaza preferinta sistemului de operare
Statistici:
- Vizitatori unici - azi / 30 zile / 1 an / total, fara boți, fara expirare
- Graf vizite zilnice - bar chart SVG cu vizitele din ultimele 30 de zile
- Top referreri - domeniile din care au venit vizitatorii, cu favicon
- Top postari - articolele cele mai citite dupa vizualizari
- URL-uri curate -
/titlul-articolului,/nota-42,/foto-43,/despre - Slug ASCII - diacriticele romanesti sunt transliterate automat (ș→s, ț→t etc.)
- Contor online - footer arata de cand este blog-ul online (configurat din Setari)
- Setari din admin - titlu, descriere, logo, favicon, URL site, data lansarii, profil Fediverse, postari pe pagina
- Imagini separate - imaginile din articole in
data/uploads/, logo si favicon inpublic/uploads/
Import si migrare:
- Import Markdown - importa orice
.mdcu YAML front matter - Migrare Pure Blog - unealta dedicata pentru migrarea din Pure Blog
- Import comentarii Pure Blog - importa comentariile din PureComments cu rezolvare automata de slug-uri si remapare parent_id
Tehnic:
- Securitate - CSRF pe toate formularele (inclusiv login), actiuni destructive doar prin POST, SSRF blocat, CSP diferentiat admin/public, rate limiting, sesiuni sigure, validare MIME
- Parole bcrypt - hash automat la salvare, fara plaintext niciodata
- SQLite - baza de date intr-un singur fisier, zero configurare
- Portabil - shared hosting, VPS, bare metal, Docker
- Fara dependente - PHP pur, nicio biblioteca externa, niciun build step
- Navigare admin bazata pe URL - fiecare sectiune si sub-sectiune are URL propriu, back/forward functioneaza
Backup si restaurare
Tot continutul este in:
data/blog.db <- baza de date (postari, pagini, comentarii, statistici, vizitatori)
data/settings.json <- setarile din admin (titlu, logo, favicon, credentiale, data lansarii)
data/posts/ <- fisiere .md exportate automat (backup portabil postari)
data/pages/ <- fisiere .md exportate automat (backup portabil pagini)
data/uploads/ <- imaginile din articole, note si fotografii
public/uploads/ <- logo si favicon
Comentariile sunt salvate doar in
blog.db- nu exista fisiere.mdpentru ele. Dacablog.dbse pierde, comentariile nu pot fi reconstituite din disc.
Backup
Copiaza data/ si public/uploads/ pe calculatorul tau.
In cPanel poti descarca direct prin File Manager sau Archive Manager.
Restaurare
- Incarca
blog.dbsisettings.jsonin directoruldata/ - Incarca imaginile in
data/uploads/ - Incarca logo si favicon in
public/uploads/ - Gata - toate postarile, paginile, imaginile si setarile sunt restaurate
Securitate
- CSRF - token unic per sesiune pe toate formularele, inclusiv login; actiunile destructive (stergere, aprobare) accepta doar POST
- Rate limiting login - maxim 5 incercari, blocare cu fereastra fixa per IP (nu rolling)
- Rate limiting comentarii - maxim 5 comentarii pe ora per IP
- Rate limiting webmention - maxim 10 webmentions pe ora per IP
- Honeypot - camp ascuns pentru detectarea botilor la comentarii
- Session hijacking - verificare IP si User-Agent la fiecare cerere admin
- Upload - validare MIME type real (nu doar extensie), max 10 MB; SVG exclus
- Parole - hash bcrypt cu cost 12, generat automat la setup si la schimbare
- setup.php - accesibil doar inainte de configurare; blocat automat dupa
- Filtrare boți - Googlebot, Bingbot, crawlerele SEO, scanerele de securitate si cererile fara User-Agent sunt ignorate din statistici
- SSRF blocat - endpoint webmention valideaza schema (http/https), respinge IP-uri private si rezervate
- CSP - Content Security Policy diferentiat: strict (
script-src 'self') pentru paginile publice, permisiv pentru admin (inline scripts necesare) - Headere HTTP - X-Content-Type-Options, Referrer-Policy,
frame-ancestors 'none' - Directoare protejate -
src/sidata/in afara webroot-ului - PRG pattern - redirect dupa POST la comentarii (previne duplicatele la refresh)
- Timezone UTC - toate datele stocate si comparate in UTC pentru consistenta
Cerinte tehnice
| Componenta | Versiune minima |
|---|---|
| PHP | 8.0 |
| PDO SQLite | inclus in PHP |
| fileinfo | inclus in PHP |
| Apache | 2.4+ cu mod_rewrite |
| nginx | 1.18+ cu PHP-FPM |
| Docker | 20.10+ |
Realizat cu
Proiect construit cu Claude Code de la Anthropic.