Add lightweight self-hosted Hugo CMS with GitHub API backend

Implements a Go-based CMS for editing Hugo posts and uploading images
via the GitHub REST API, with no database and Git as the source of truth.

Features:
- Single admin auth with bcrypt password and signed session cookies
- Dashboard, post listing with search, create/edit with EasyMDE editor
- Media library for static/uploads/ with drag-drop and clipboard paste
- Client-side autosave, unsaved changes warning, dark mode, responsive UI
- Modular packages for auth, github, posts, media, session, and handlers

Co-authored-by: codegirl007 <s.raide@gmail.com>
This commit is contained in:
Cursor Agent
2026-07-06 18:56:17 +00:00
co-authored by codegirl007
parent 68dfdfa33b
commit ce15f54a52
28 changed files with 2945 additions and 0 deletions
+149
View File
@@ -0,0 +1,149 @@
# Hugo CMS
A lightweight, self-hosted CMS for Hugo websites. Edit posts and upload images from desktop or mobile while keeping **Git as the single source of truth**. No database required.
All repository operations go through the **GitHub REST API** — the server never runs `git` commands.
## Architecture
```
Browser → Go Server → GitHub REST API → GitHub Repository → GitHub Action → Hugo Site
```
## Features
- Single admin authentication (bcrypt password hash + secure session cookie)
- Dashboard with post stats and recent posts
- Create and edit Hugo posts with automatic front matter generation
- EasyMDE markdown editor with split preview, toolbar, drag/drop images, and keyboard shortcuts
- Media library for uploading images to `static/uploads/`
- Client-side autosave (every 30 seconds) and unsaved changes warning
- Dark mode and responsive layout
- Auto-generated slugs from titles
## Project Structure
```
cms/
├── cmd/
│ ├── server/ # Main HTTP server
│ └── hashpassword/ # Utility to generate bcrypt hashes
├── internal/
│ ├── auth/ # Password verification
│ ├── config/ # Environment configuration
│ ├── github/ # GitHub REST API client
│ ├── handlers/ # HTTP route handlers
│ ├── media/ # Image upload service
│ ├── posts/ # Post parsing and saving
│ ├── session/ # Signed session cookies
│ └── templates/ # Embedded HTML templates and static assets
└── web/ # (assets embedded in internal/templates/static)
```
## Configuration
Copy `.env.example` to `.env` and fill in the values:
| Variable | Description |
|----------|-------------|
| `GITHUB_TOKEN` | GitHub personal access token with `repo` scope |
| `GITHUB_OWNER` | Repository owner (user or org) |
| `GITHUB_REPO` | Repository name |
| `GITHUB_BRANCH` | Branch to commit to (default: `master`) |
| `SESSION_SECRET` | Random string for signing session cookies |
| `ADMIN_USERNAME` | Admin login username |
| `ADMIN_PASSWORD_HASH` | Bcrypt hash of admin password |
| `ADDR` | Listen address (default: `:8080`) |
| `COOKIE_SECURE` | Set `false` for local HTTP dev (default: `true`) |
### Generate a password hash
```bash
cd cms
go run ./cmd/hashpassword 'your-secure-password'
```
### Generate a session secret
```bash
openssl rand -hex 32
```
## Running Locally
```bash
cd cms
go mod tidy
export GITHUB_TOKEN=...
export GITHUB_OWNER=...
export GITHUB_REPO=...
export SESSION_SECRET=...
export ADMIN_USERNAME=admin
export ADMIN_PASSWORD_HASH=...
export COOKIE_SECURE=false
go run ./cmd/server
```
Open http://localhost:8080/admin
## HTTP Routes
| Method | Path | Description |
|--------|------|-------------|
| GET | `/login` | Login page |
| POST | `/login` | Authenticate |
| POST | `/logout` | End session |
| GET | `/admin` | Dashboard |
| GET | `/admin/posts` | Post listing |
| GET | `/admin/posts/new` | New post editor |
| GET | `/admin/posts/:slug` | Edit post |
| GET | `/admin/media` | Media library |
| POST | `/api/posts/save` | Save post to GitHub |
| POST | `/api/media/upload` | Upload image |
| GET | `/api/media` | List uploaded images |
## Deployment
Build a static binary and run behind a reverse proxy (nginx, Caddy, etc.) with HTTPS:
```bash
cd cms
CGO_ENABLED=0 go build -o hugo-cms ./cmd/server
```
Example Caddy config:
```
cms.example.com {
reverse_proxy localhost:8080
}
```
Set `COOKIE_SECURE=true` in production so session cookies are only sent over HTTPS.
## Post Format
Posts are saved to `content/posts/<slug>.md` with Hugo front matter:
```yaml
---
title: "Hello World"
date: 2026-07-06T18:00:00Z
draft: false
tags:
- hugo
- programming
---
My markdown content.
```
Images are stored at `static/uploads/<filename>` and referenced as `![](/uploads/image.png)`.
## GitHub Token Permissions
The token needs write access to the repository contents. A fine-grained token with **Contents: Read and write** on the target repo, or a classic token with the `repo` scope, is sufficient.
## License
MIT