🕹️ RetroBlog

A self-hosted blog engine with swappable OS / game-console skins.

Write Markdown posts, flip between retro themes (Windows, PS1, PS2, PS3, Wii, NDS, Dreamcast, JV2002…), and run the whole thing from a single SQLite file.


Features

  • Markdown posts — create / edit / delete with slugs, tags, excerpts, dates.
  • Swappable skins — pick the default theme; optionally let visitors switch.
  • Admin panel — single-password login, signed session cookie.
  • Import / export — JSON backup of settings and posts.
  • Zero external services — data lives in one SQLite file (data/blog.db).

Stack: Next.js 16 (App Router) · React 19 · better-sqlite3 · marked.


Quick start (development)

pnpm install
pnpm dev

Open http://localhost:3000. Admin panel at http://localhost:3000/admin (default password admin in dev).


Configuration

All config is environment variables. Copy .env.example to .env and edit.

Variable Required Default Purpose
ADMIN_PASSWORD prod admin Password for /admin. Set this in production.
ADMIN_SESSION_SECRET prod ADMIN_PASSWORD Signs the admin session cookie. Use a long random string.
PORT no 3000 Port the server listens on.

Generate a secret:

openssl rand -hex 32

Data is stored in ./data/blog.db (created automatically). Back up that directory to back up the whole site.


Needs Docker + the Compose plugin.

# 1. Set production secrets
cp .env.example .env
$EDITOR .env            # set ADMIN_PASSWORD and ADMIN_SESSION_SECRET

# 2. Build and run
docker compose up -d --build

Site is live on http://localhost:3000. The database persists in the retroblog-data Docker volume across rebuilds.

Common operations:

docker compose logs -f          # tail logs
docker compose down             # stop (keeps the data volume)
docker compose up -d --build    # update after pulling new code

Back up the database:

docker compose exec retroblog sh -c 'cat /app/data/blog.db' > backup-$(date +%F).db

Put a TLS-terminating reverse proxy (Caddy, nginx, Traefik) in front for HTTPS in production. RetroBlog speaks plain HTTP on PORT.


Deploy — bare metal

Needs Node.js 22+ and pnpm (corepack enable). A C toolchain (build-essential, python3) is required once, to compile better-sqlite3.

# 1. Install deps and build
pnpm install --frozen-lockfile
pnpm build

# 2. Set production secrets
export ADMIN_PASSWORD='your-strong-password'
export ADMIN_SESSION_SECRET="$(openssl rand -hex 32)"

# 3. Start
pnpm start          # serves on PORT (default 3000)

Run it as a service (systemd)

/etc/systemd/system/retroblog.service:

[Unit]
Description=RetroBlog
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/retroblog
ExecStart=/usr/bin/pnpm start
Restart=on-failure
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=ADMIN_PASSWORD=your-strong-password
Environment=ADMIN_SESSION_SECRET=your-long-random-secret
User=www-data
Group=www-data

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now retroblog

The SQLite database lives in WorkingDirectory/data/blog.db — make sure the service User can write there, and back up that file.


Admin panel

Sign in at /admin with ADMIN_PASSWORD. From there:

  • Posts — full create / edit / delete with Markdown bodies, slugs, tags, dates.
  • Settings — branding (title, subtitle, footer, version), default theme for new visitors, whether the public theme switcher is shown, and which skins it offers.
  • Import / export — download a JSON backup of settings and/or posts, and import one back (posts can replace or append).

All /admin/* routes are gated by middleware behind the signed session cookie.


License

See repository.

S
Description
A simple, very opinionated blog engine.
Readme 449 KiB
Languages
TypeScript 62.2%
CSS 37.1%
Dockerfile 0.5%
JavaScript 0.2%