Add Hugo blog starter and Forgejo post workflows
This commit is contained in:
parent
fd7398be9f
commit
62593d8316
13 changed files with 387 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
116
blog-starter/.forgejo/workflows/create-post.yml
Normal file
116
blog-starter/.forgejo/workflows/create-post.yml
Normal 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
|
||||
68
blog-starter/.forgejo/workflows/deploy.yml
Normal file
68
blog-starter/.forgejo/workflows/deploy.yml
Normal 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
4
blog-starter/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
/public/
|
||||
/resources/
|
||||
/themes/LoveIt/
|
||||
.hugo_build.lock
|
||||
85
blog-starter/README.md
Normal file
85
blog-starter/README.md
Normal 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.
|
||||
10
blog-starter/archetypes/default.md
Normal file
10
blog-starter/archetypes/default.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
+++
|
||||
title = "{{ replace .Name "-" " " | title }}"
|
||||
date = {{ .Date }}
|
||||
draft = true
|
||||
slug = "{{ .Name }}"
|
||||
tags = []
|
||||
categories = []
|
||||
+++
|
||||
|
||||
Write your post here.
|
||||
9
blog-starter/content/about/index.md
Normal file
9
blog-starter/content/about/index.md
Normal 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.
|
||||
7
blog-starter/content/posts/_index.md
Normal file
7
blog-starter/content/posts/_index.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
+++
|
||||
title = "Posts"
|
||||
date = 2026-03-19T12:00:00+07:00
|
||||
draft = false
|
||||
+++
|
||||
|
||||
Latest posts from the blog.
|
||||
17
blog-starter/content/posts/hello-world.md
Normal file
17
blog-starter/content/posts/hello-world.md
Normal 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
35
blog-starter/hugo.toml
Normal 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
|
||||
19
blog-starter/scripts/bootstrap-theme.sh
Normal file
19
blog-starter/scripts/bootstrap-theme.sh
Normal 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}"
|
||||
11
blog-starter/scripts/new-post.sh
Normal file
11
blog-starter/scripts/new-post.sh
Normal 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"
|
||||
1
blog-starter/static/.gitkeep
Normal file
1
blog-starter/static/.gitkeep
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
Loading…
Reference in a new issue