Add Hugo blog starter and Forgejo post workflows

This commit is contained in:
xtcnet 2026-03-19 13:32:15 +07:00
parent fd7398be9f
commit 62593d8316
13 changed files with 387 additions and 0 deletions

View file

@ -24,6 +24,11 @@ A lightweight all-in-one Docker deployment that combines reverse proxy managemen
- Accessible only via domain through NPM proxy
- CI/CD via Forgejo Runner for automated Docker builds
### Blog Starter
- `blog-starter/` contains a ready-to-use Hugo + LoveIt starter
- includes a Forgejo Actions workflow that deploys generated files to `/opt/blog/public`
- intended to be used as the base of a separate blog repository
---
## Quick Start

View file

@ -0,0 +1,116 @@
name: Create Blog Post
on:
workflow_dispatch:
inputs:
title:
description: "Post title"
required: true
type: string
slug:
description: "URL slug, for example my-new-post"
required: true
type: string
summary:
description: "Short summary"
required: false
type: string
tags:
description: "Comma-separated tags"
required: false
type: string
categories:
description: "Comma-separated categories"
required: false
type: string
draft:
description: "Create as draft"
required: true
type: boolean
default: true
jobs:
create-post:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate post file
env:
INPUT_TITLE: ${{ inputs.title }}
INPUT_SLUG: ${{ inputs.slug }}
INPUT_SUMMARY: ${{ inputs.summary }}
INPUT_TAGS: ${{ inputs.tags }}
INPUT_CATEGORIES: ${{ inputs.categories }}
INPUT_DRAFT: ${{ inputs.draft }}
run: |
python3 - <<'PY'
import os
import re
from datetime import datetime, timezone
from pathlib import Path
def split_csv(value: str):
if not value:
return []
return [item.strip() for item in value.split(",") if item.strip()]
title = os.environ["INPUT_TITLE"].strip()
slug = os.environ["INPUT_SLUG"].strip().lower()
summary = os.environ.get("INPUT_SUMMARY", "").strip()
draft = os.environ.get("INPUT_DRAFT", "true").strip().lower() == "true"
tags = split_csv(os.environ.get("INPUT_TAGS", ""))
categories = split_csv(os.environ.get("INPUT_CATEGORIES", ""))
if not title:
raise SystemExit("Title is required")
if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", slug):
raise SystemExit("Slug must use lowercase letters, numbers, and hyphens only")
content_dir = Path("content/posts")
content_dir.mkdir(parents=True, exist_ok=True)
target = content_dir / f"{slug}.md"
if target.exists():
raise SystemExit(f"Post already exists: {target}")
now = datetime.now(timezone.utc).astimezone().replace(microsecond=0).isoformat()
def format_array(values):
if not values:
return "[]"
return "[{}]".format(", ".join(f'\"{value}\"' for value in values))
escaped_title = title.replace('"', '\\"')
escaped_summary = summary.replace('"', '\\"')
body = f"""++++
title = \"{escaped_title}\"
date = {now}
draft = {\"true\" if draft else \"false\"}
slug = \"{slug}\"
summary = \"{escaped_summary}\"
tags = {format_array(tags)}
categories = {format_array(categories)}
++++
Write your post here.
"""
target.write_text(body, encoding="utf-8")
print(target)
PY
- name: Commit new post
env:
POST_SLUG: ${{ inputs.slug }}
run: |
git config user.name "forgejo-actions"
git config user.email "forgejo-actions@localhost"
git add "content/posts/${POST_SLUG}.md"
git commit -m "Create post: ${POST_SLUG}"
git push

View file

@ -0,0 +1,68 @@
name: Deploy Blog
on:
push:
branches:
- master
- main
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y curl git rsync openssh-client tar
- name: Install Hugo Extended
env:
HUGO_VERSION: "0.145.0"
run: |
curl -fsSL -o hugo.deb \
"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb"
sudo dpkg -i hugo.deb
hugo version
- name: Bootstrap LoveIt theme
run: |
chmod +x scripts/bootstrap-theme.sh
./scripts/bootstrap-theme.sh
- name: Build site
run: |
hugo --gc --minify
test -f public/index.html
- name: Configure SSH
env:
BLOG_DEPLOY_KEY: ${{ secrets.BLOG_DEPLOY_KEY }}
BLOG_DEPLOY_KNOWN_HOSTS: ${{ secrets.BLOG_DEPLOY_KNOWN_HOSTS }}
run: |
install -d -m 700 ~/.ssh
printf '%s\n' "$BLOG_DEPLOY_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
if [ -n "${BLOG_DEPLOY_KNOWN_HOSTS}" ]; then
printf '%s\n' "$BLOG_DEPLOY_KNOWN_HOSTS" > ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
fi
- name: Deploy to server
env:
BLOG_DEPLOY_HOST: ${{ secrets.BLOG_DEPLOY_HOST }}
BLOG_DEPLOY_PORT: ${{ secrets.BLOG_DEPLOY_PORT }}
BLOG_DEPLOY_USER: ${{ secrets.BLOG_DEPLOY_USER }}
BLOG_DEPLOY_PATH: ${{ secrets.BLOG_DEPLOY_PATH }}
run: |
test -n "$BLOG_DEPLOY_HOST"
test -n "$BLOG_DEPLOY_PORT"
test -n "$BLOG_DEPLOY_USER"
test -n "$BLOG_DEPLOY_PATH"
ssh -p "$BLOG_DEPLOY_PORT" "$BLOG_DEPLOY_USER@$BLOG_DEPLOY_HOST" "mkdir -p '$BLOG_DEPLOY_PATH/public'"
rsync -az --delete \
-e "ssh -p $BLOG_DEPLOY_PORT" \
public/ "$BLOG_DEPLOY_USER@$BLOG_DEPLOY_HOST:$BLOG_DEPLOY_PATH/public/"

4
blog-starter/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/public/
/resources/
/themes/LoveIt/
.hugo_build.lock

85
blog-starter/README.md Normal file
View file

@ -0,0 +1,85 @@
# Blog Starter
This folder is a standalone starter for a Hugo blog that uses the LoveIt theme
and deploys to the static blog host installed by `install.sh`.
## What This Starter Includes
- Hugo site structure
- LoveIt bootstrap script
- sample content
- a helper script to create new posts
- a Forgejo Actions workflow that builds and deploys to `/opt/blog/public`
## Recommended Setup
Create a separate Git repository for your blog, then copy this starter into that
repository root.
## Local Writing Workflow
1. Bootstrap the theme:
```bash
./scripts/bootstrap-theme.sh
```
2. Create a new post:
```bash
./scripts/new-post.sh my-first-post
```
3. Start the local preview server:
```bash
hugo server
```
4. Edit the generated file under `content/posts/`.
5. When ready to publish, set `draft = false`, commit, and push.
## Forgejo Web Workflow
If you want to create posts directly from the Forgejo web UI, use the manual
workflow in `.forgejo/workflows/create-post.yml`.
From the Actions page:
1. Run `Create Blog Post`
2. Fill in:
- `title`
- `slug`
- optional `summary`
- optional comma-separated `tags`
- optional comma-separated `categories`
- `draft`
3. The workflow creates `content/posts/<slug>.md`
4. The normal deploy workflow publishes the post on the next push
This keeps you out of front matter for most day-to-day writing.
## Forgejo Secrets
The workflow expects these repository secrets:
- `BLOG_DEPLOY_HOST`: server hostname or IP
- `BLOG_DEPLOY_PORT`: SSH port, usually `22`
- `BLOG_DEPLOY_USER`: deploy user on the server
- `BLOG_DEPLOY_KEY`: private SSH key for the deploy user
- `BLOG_DEPLOY_PATH`: target directory, usually `/opt/blog`
- `BLOG_DEPLOY_KNOWN_HOSTS`: optional `known_hosts` entry for stricter SSH
## Expected Server State
- Blog host installed from this repo's `install.sh`
- `d3v-blog` container running
- the deploy user can write to `/opt/blog/public`
- Nginx Proxy Manager forwards `blog.yourdomain.com` to `d3v-blog:80`
## Notes
- This starter fetches LoveIt into `themes/LoveIt` using Git.
- The workflow installs Hugo Extended before building.
- Deployment uses `rsync --delete` to keep `/opt/blog/public` in sync with the
latest generated `public/` output.

View file

@ -0,0 +1,10 @@
+++
title = "{{ replace .Name "-" " " | title }}"
date = {{ .Date }}
draft = true
slug = "{{ .Name }}"
tags = []
categories = []
+++
Write your post here.

View file

@ -0,0 +1,9 @@
+++
title = "About"
date = 2026-03-19T12:00:00+07:00
draft = false
+++
This is the About page for your Hugo blog.
Update this page with your own bio, project information, or contact details.

View file

@ -0,0 +1,7 @@
+++
title = "Posts"
date = 2026-03-19T12:00:00+07:00
draft = false
+++
Latest posts from the blog.

View file

@ -0,0 +1,17 @@
+++
title = "Hello World"
date = 2026-03-19T12:00:00+07:00
draft = false
slug = "hello-world"
tags = ["welcome", "hugo"]
categories = ["general"]
+++
This is the first post published from the Hugo + LoveIt starter.
When you are ready to write your own content:
1. Create a new post with `./scripts/new-post.sh your-post-slug`
2. Edit the generated Markdown file
3. Preview with `hugo server`
4. Commit and push to trigger deployment

35
blog-starter/hugo.toml Normal file
View file

@ -0,0 +1,35 @@
baseURL = "https://blog.example.com/"
languageCode = "en-us"
title = "D3V Blog"
theme = "LoveIt"
defaultContentLanguage = "en"
enableRobotsTXT = true
[permalinks]
posts = "/posts/:slug/"
[params]
defaultTheme = "auto"
dateFormat = "2006-01-02"
description = "Notes, updates, and guides."
[params.author]
name = "D3V Team"
[markup]
[markup.goldmark]
[markup.goldmark.renderer]
unsafe = true
[menu]
[[menu.main]]
identifier = "posts"
name = "Posts"
url = "/posts/"
weight = 1
[[menu.main]]
identifier = "about"
name = "About"
url = "/about/"
weight = 2

View file

@ -0,0 +1,19 @@
#!/bin/sh
set -eu
THEME_DIR="themes/LoveIt"
THEME_REPO="https://github.com/dillonzq/LoveIt.git"
THEME_REF="${LOVEIT_REF:-master}"
mkdir -p themes
if [ -d "${THEME_DIR}/.git" ]; then
echo "Updating LoveIt theme..."
git -C "${THEME_DIR}" fetch --depth 1 origin "${THEME_REF}"
git -C "${THEME_DIR}" checkout -f FETCH_HEAD
else
echo "Cloning LoveIt theme..."
git clone --depth 1 --branch "${THEME_REF}" "${THEME_REPO}" "${THEME_DIR}"
fi
echo "LoveIt theme is ready in ${THEME_DIR}"

View file

@ -0,0 +1,11 @@
#!/bin/sh
set -eu
if [ "${1:-}" = "" ]; then
echo "Usage: $0 my-post-slug" >&2
exit 1
fi
slug="$1"
hugo new "posts/${slug}.md"
echo "Created content/posts/${slug}.md"

View file

@ -0,0 +1 @@