Files
blog-manager/README.md
2026-11-01 13:23:44 -05:00

3.3 KiB

blog-manager

A local web app for managing the portfolio blog: create, edit and delete posts in a markdown editor with live preview, upload images, rename and merge categories and tags, and publish through the existing git and deploy pipeline.

There is no authentication. The server listens on 0.0.0.0:3051, so restrict access to that port with firewall rules.

How it works

  • Store: portfolio/data/blog-posts.json. The site imports it as BLOG_POSTS in data/content.ts. The manager edits this file in place, in the mounted portfolio checkout.
  • Images: uploads go to portfolio/public/img/blog/<slug>/ and are referenced as img/blog/<slug>/<file>.
  • Preview: rendered with the site's own remark pipeline and styled with the site's own CSS, so it looks like the live post page.
  • Publish:
    1. Commits only data/blog-posts.json and public/img/blog/. Other changes in the checkout are never included.
    2. Runs git push.
    3. Calls the site's POST /api/deploy (optional). This triggers scripts/deploy-watch.sh on the server.
  • Safety: writes are atomic. A save made from a stale copy, for example after a git pull, is rejected so you can reload. Browsers can't send cross-site writes, because requests whose Origin doesn't match the host are refused.

Run with Docker

cp .env.example .env   # set UID/GID, git identity, push token, deploy secret
docker compose up -d --build

Then open http://<host>:3051.

  • The container mounts ../portfolio at /repo. Set PORTFOLIO_DIR to use a different checkout.
  • Set BLOG_MANAGER_UID and BLOG_MANAGER_GID to the owner of the checkout (id -u / id -g). Otherwise, on Linux, files the manager writes end up owned by a different user, and git and deploy-watch may then fail with permission errors.
  • Push credentials:
    • For an HTTPS remote, set GIT_PUSH_USER and GIT_PUSH_TOKEN. git receives them through scripts/git-askpass.sh, and they're never written to disk.
    • Set GIT_PUSH=false to only commit and push by hand.
  • If the manager runs on the deploy host against the same checkout that deploy-watch pulls, that pull is a no-op fast-forward after a publish.

Run without Docker

npm install
npm run dev     # http://0.0.0.0:3051, uses ../portfolio unless REPO_DIR is set

Environment

Variable Default Purpose
REPO_DIR ../portfolio (/repo in Docker) Portfolio git checkout
GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL none Commit identity (needed unless the repo config has user.*)
GIT_PUSH true false = commit only
GIT_PUSH_USER, GIT_PUSH_TOKEN none HTTPS push credentials
DEPLOY_URL, DEPLOY_TRIGGER_SECRET none Live-site redeploy hook; skipped when unset
BLOG_MANAGER_PORT, BLOG_MANAGER_UID, BLOG_MANAGER_GID, PORTFOLIO_DIR 3051, 1000, 1000, ../portfolio Compose only

Notes

  • Categories come from the posts themselves. You create one by typing it into a post, and it disappears when no post uses it. Renaming a category to an existing one merges the two.
  • Renaming a post's slug moves its image folder and rewrites the references to it.
  • Every saved post goes live on the next publish; there are no drafts.
  • lib/types.ts mirrors BlogPost in portfolio/data/content.ts. Keep the two in sync.