3.3 KiB
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 asBLOG_POSTSindata/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 asimg/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:
- Commits only
data/blog-posts.jsonandpublic/img/blog/. Other changes in the checkout are never included. - Runs
git push. - Calls the site's
POST /api/deploy(optional). This triggersscripts/deploy-watch.shon the server.
- Commits only
- 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 whoseOrigindoesn'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
../portfolioat/repo. SetPORTFOLIO_DIRto use a different checkout. - Set
BLOG_MANAGER_UIDandBLOG_MANAGER_GIDto 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 anddeploy-watchmay then fail with permission errors. - Push credentials:
- For an HTTPS remote, set
GIT_PUSH_USERandGIT_PUSH_TOKEN. git receives them throughscripts/git-askpass.sh, and they're never written to disk. - Set
GIT_PUSH=falseto only commit and push by hand.
- For an HTTPS remote, set
- If the manager runs on the deploy host against the same checkout that
deploy-watchpulls, 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.tsmirrorsBlogPostinportfolio/data/content.ts. Keep the two in sync.