initial commit

This commit is contained in:
Hunter
2026-11-01 13:23:44 -05:00
commit e03ea4f770
53 changed files with 10802 additions and 0 deletions

57
README.md Normal file
View File

@@ -0,0 +1,57 @@
# 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
```bash
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
```bash
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.