diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..260886a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +node_modules +.next +.git +.gitignore +data +*.tsbuildinfo +next-env.d.ts +.env* +README.md +Dockerfile +.dockerignore +docker-compose.yml +npm-debug.log* +.pnpm-debug.log* +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..48a327c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,45 @@ +# syntax=docker/dockerfile:1 + +# ---- deps: install node_modules (native build tools for better-sqlite3) ---- +FROM node:22-bookworm-slim AS deps +WORKDIR /app +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 make g++ \ + && rm -rf /var/lib/apt/lists/* +RUN corepack enable +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +RUN pnpm install --frozen-lockfile + +# ---- builder: compile the Next.js standalone server ---- +FROM node:22-bookworm-slim AS builder +WORKDIR /app +RUN corepack enable +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN pnpm build + +# ---- runner: minimal runtime image ---- +FROM node:22-bookworm-slim AS runner +WORKDIR /app +ENV NODE_ENV=production \ + NEXT_TELEMETRY_DISABLED=1 \ + PORT=3000 \ + HOSTNAME=0.0.0.0 + +# Non-root user; owns the data volume so SQLite can write. +RUN addgroup --system --gid 1001 nodejs \ + && adduser --system --uid 1001 nextjs + +# Standalone output bundles the traced node_modules (incl. the better-sqlite3 +# native binary), so no install/rebuild is needed here. +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static +COPY --from=builder /app/public ./public + +RUN mkdir -p /app/data && chown -R nextjs:nodejs /app/data +VOLUME ["/app/data"] +USER nextjs + +EXPOSE 3000 +CMD ["node", "server.js"] diff --git a/README.md b/README.md index 39a01bd..742d060 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,163 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +
-## Getting Started +# ๐Ÿ•น๏ธ RetroBlog -First, run the development server: +**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 -npm run dev -# or -yarn dev -# or +pnpm install pnpm dev -# or -bun dev ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +Open . Admin panel at +(default password `admin` in dev). -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +--- + +## 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 -Visit [http://localhost:3000/admin](http://localhost:3000/admin) and sign in with -`ADMIN_PASSWORD` (see `.env.example`; defaults to `admin` in dev). From there you can: +Sign in at `/admin` with `ADMIN_PASSWORD`. From there: -- **Posts** โ€” full create / edit / delete with Markdown bodies, slugs, tags, and dates. -- **Settings** โ€” branding (title, subtitle, footer, version), the default theme for +- **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). +- **Import / export** โ€” download a JSON backup of settings and/or posts, and + import one back (posts can replace or append). -Auth is a single password kept in `ADMIN_PASSWORD`, with a signed session cookie -(`ADMIN_SESSION_SECRET`). All `/admin/*` routes are gated by middleware. +All `/admin/*` routes are gated by middleware behind the signed session cookie. -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +--- -## Learn More +## License -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +See repository. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c894029 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +services: + retroblog: + build: . + image: retroblog:latest + container_name: retroblog + restart: unless-stopped + ports: + - "3000:3000" + environment: + # CHANGE THESE before exposing to the internet. + ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin} + ADMIN_SESSION_SECRET: ${ADMIN_SESSION_SECRET:-change-me-to-a-long-random-string} + volumes: + # Persist the SQLite database across container rebuilds. + - retroblog-data:/app/data + +volumes: + retroblog-data: diff --git a/next.config.ts b/next.config.ts index e9ffa30..263be30 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,9 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + // Emit a self-contained server in .next/standalone for slim Docker images + // and simple bare-metal deploys. + output: "standalone", }; export default nextConfig;