# ๐น๏ธ 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)
```bash
pnpm install
pnpm dev
```
Open . Admin panel at
(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:
```bash
openssl rand -hex 32
```
**Data** is stored in `./data/blog.db` (created automatically). Back up that
directory to back up the whole site.
---
## Deploy โ Docker (recommended)
Needs Docker + the Compose plugin.
```bash
# 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 . The database persists in the
`retroblog-data` Docker volume across rebuilds.
Common operations:
```bash
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:**
```bash
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.
```bash
# 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`:
```ini
[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
```
```bash
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.