Featured image of post 如何使用 GitHub Issue 发布 Hugo 博客

如何使用 GitHub Issue 发布 Hugo 博客

近年来,静态博客的发布方式层出不穷,但许多方法要么复杂,要么难以长期坚持。本文介绍一种简单高效的方式:通过 GitHub Issue 作为 Hugo 博客的发布端,利用 GitHub Actions 自动将 Issue 转换为 Hugo 内容并部署到 GitHub Pages。这种方法尤其适合喜欢用 GitHub 移动端 App 随时随地发布博客的用户。本教程基于老 T 的实践经验,解决了私有仓库图片下载、标签提取等问题,适合公开和私有仓库。

为什么选择 GitHub Issue?

  • 便捷性:GitHub 移动端 App 在国内网络环境下比网页版更稳定,发布只需几次点击,比微信朋友圈还简单。
  • 无限制:Issue 几乎没有数量或容量限制,适合博客内容存储。
  • 自动化:通过 GitHub Actions,可以将 Issue 自动转换为 Hugo 内容,并触发站点构建。
  • 灵活性:支持图片、标签、分类,适合短篇“说说”或长篇文章。

前提条件

  • 一个 Hugo 博客项目,已配置好 GitHub Pages(公开或私有仓库)。
  • GitHub 个人访问令牌(PAT),具有 repo 权限(私有仓库需要)和 workflow 权限(触发工作流)。在 GitHub SettingsDeveloper settingsPersonal access tokens 创建。
  • 基本的 GitHub Actions 和 Hugo 知识。
  • 仓库结构包含 content/posts/ 目录,用于存放生成的文章。

实现步骤

1. 配置 GitHub Actions 工作流

我们需要两个工作流文件:

  • deploy.yml:将 Issue 转换为 Hugo 内容并提交到仓库。
  • hugo.yml:构建 Hugo 站点并部署到 GitHub Pages。

1.1 创建 deploy.yml

.github/workflows/deploy.yml 中添加以下内容,用于处理 Issue 转换为 Hugo 内容,并触发 Hugo 构建:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
name: Sync Issues to Hugo Content

on:
  issues:
    types: [opened]
  workflow_dispatch:

permissions:
  contents: write

concurrency:
  group: "issues-to-hugo"
  cancel-in-progress: false

defaults:
  run:
    shell: bash

jobs:
  convert_issues:
    name: Convert GitHub Issues
    runs-on: ubuntu-latest
    if: github.event_name == 'workflow_dispatch' || github.event_name == 'issues' && contains(github.event.issue.labels.*.name, '发布') && github.actor != 'IssueBot'
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          clean: true
          
      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Install dependencies
        run: |
          pip install requests PyGithub==2.4.0 beautifulsoup4

      - name: Create content/posts directory
        run: |
          mkdir -p content/posts
          echo "Created content/posts directory"

      - name: Convert issues to Hugo content
        id: convert
        env:
          GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
        run: |
          find content/posts -type f 2>/dev/null | sort > /tmp/original_files.txt || touch /tmp/original_files.txt
          if [ -s /tmp/original_files.txt ]; then
            xargs sha1sum < /tmp/original_files.txt > /tmp/original_hashes.txt
          else
            touch /tmp/original_hashes.txt
          fi
          
          python .github/workflows/issue_to_hugo.py \
            --repo "${{ github.repository }}" \
            --output "content/posts" \
            --debug
          
          find content/posts -type f 2>/dev/null | sort > /tmp/new_files.txt || touch /tmp/new_files.txt
          if [ -s /tmp/new_files.txt ]; then
            xargs sha1sum < /tmp/new_files.txt > /tmp/new_hashes.txt
          else
            touch /tmp/new_hashes.txt
          fi
          
          if cmp -s /tmp/original_hashes.txt /tmp/new_hashes.txt; then
            echo "needs_build=false" >> $GITHUB_OUTPUT
            echo "🔄 没有内容变更,将跳过提交"
          else
            echo "needs_build=true" >> $GITHUB_OUTPUT
            echo "✅ 检测到内容变更,将执行提交"
          fi
          
      - name: Commit changes (if any)
        if: steps.convert.outputs.needs_build == 'true'
        env:
          GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
        run: |
          git config --global user.name "IssueBot"
          git config --global user.email "actions@users.noreply.github.com"
          git remote set-url origin https://x-access-token:${{ secrets.PAT_TOKEN }}@github.com/${{ github.repository }}.git
          
          if [ -n "${{ github.base_ref }}" ]; then
            DEFAULT_BRANCH="${{ github.base_ref }}"
          else
            DEFAULT_BRANCH=$(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
          fi
          
          if [ -z "$DEFAULT_BRANCH" ]; then
            DEFAULT_BRANCH="master"
          fi
          
          git checkout $DEFAULT_BRANCH
          
          git add content/posts
          
          if ! git diff-index --quiet HEAD --; then
            git commit -m "Automated: Sync GitHub Issues as content"
            echo "正在推送变更到分支: $DEFAULT_BRANCH"
            git push origin $DEFAULT_BRANCH
          else
            echo "没有需要提交的变更"
          fi

      - name: Trigger Hugo build
        if: steps.convert.outputs.needs_build == 'true'
        env:
          GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
        run: |
          curl -X POST \
            -H "Accept: application/vnd.github.v3+json" \
            -H "Authorization: Bearer ${{ secrets.PAT_TOKEN }}" \
            https://api.github.com/repos/${{ github.repository }}/dispatches \
            -d '{"event_type": "trigger-hugo-build"}'

关键点

  • 触发条件:Issue 打开时带有“发布”标签,或手动触发。
  • 防止递归:github.actor != 'IssueBot' 确保 IssueBot 的提交不会再次触发工作流。
  • 提交检测:仅在内容变更时提交(通过文件哈希比较)。
  • 触发 Hugo 构建:通过 repository_dispatch 事件 (trigger-hugo-build) 调用 Hugo 工作流。

1.2 创建 hugo.yml

.github/workflows/hugo.yml 中添加以下内容,用于构建和部署 Hugo 站点:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
name: Deploy Hugo site to Pages

on:
  push:
    branches: ["master"]
  workflow_dispatch:
  repository_dispatch:
    types: [trigger-hugo-build]

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

defaults:
  run:
    shell: bash

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      HUGO_VERSION: 0.128.0
    steps:
      - name: Install Hugo CLI
        run: |
          wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
          && sudo dpkg -i ${{ runner.temp }}/hugo.deb
      - name: Install Dart Sass
        run: sudo snap install dart-sass
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: recursive
      - name: Setup Pages
        id: pages
        uses: actions/configure-pages@v5
      - name: Install Node.js dependencies
        run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
      - name: Build with Hugo
        env:
          HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
          HUGO_ENVIRONMENT: production
        run: |
          hugo \
            --minify \
            --baseURL "${{ steps.pages.outputs.base_url }}/"
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./public
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

关键点

  • 触发条件:pushmaster 分支、手动触发,或 repository_dispatch 事件 (trigger-hugo-build)。
  • 权限:确保 pages: writeid-token: write 用于 GitHub Pages 部署。

1.3 添加转换脚本 issue_to_hugo.py

.github/workflows/issue_to_hugo.py 中添加以下 Python 脚本,用于将 Issue 转换为 Hugo 内容,支持私有仓库图片下载和标签提取:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
import os
import argparse
import re
import requests
import json
import logging
from urllib.parse import unquote
from datetime import datetime
from github import Github, Auth
from bs4 import BeautifulSoup

CATEGORY_MAP = ["生活", "技术", "法律", "瞬间", "社会"]
PUBLISH_LABEL = "发布"

def setup_logger(debug=False):
    logger = logging.getLogger('issue-to-hugo')
    level = logging.DEBUG if debug else logging.INFO
    logger.setLevel(level)
    
    handler = logging.StreamHandler()
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    
    return logger

def extract_cover_image(body):
    """提取并删除封面图(正文第一张图片)"""
    img_pattern = r"!\[.*?\]\((https?:\/\/[^\)]+)\)"
    match = re.search(img_pattern, body)
    
    if match:
        img_url = match.group(1)
        body = body.replace(match.group(0), "")
        return img_url, body
    return None, body

def safe_filename(filename):
    """生成安全的文件名,保留或推断文件扩展名"""
    clean_url = re.sub(r"\?.*$", "", filename)
    basename = os.path.basename(clean_url)
    decoded_name = unquote(basename)
    
    name, ext = os.path.splitext(decoded_name)
    safe_name = re.sub(r"[^a-zA-Z0-9\-_]", "_", name)
    if not ext.lower() in [".jpg", ".jpeg", ".png", ".gif", ".webp"]:
        ext = ""
    if len(safe_name) > 100 - len(ext):
        safe_name = safe_name[:100 - len(ext)]
    
    return safe_name + ext

def download_image(url, output_path, token=None):
    """下载图片到指定路径,基于内容类型确定扩展名,并添加 GitHub 认证头"""
    try:
        headers = {}
        if token:
            headers['Authorization'] = f'token {token}'
        
        response = requests.get(url, stream=True, headers=headers)
        if response.status_code == 200:
            content_type = response.headers.get("content-type", "").lower()
            ext = ".jpg"
            if "image/png" in content_type:
                ext = ".png"
            elif "image/jpeg" in content_type or "image/jpg" in content_type:
                ext = ".jpg"
            elif "image/gif" in content_type:
                ext = ".gif"
            elif "image/webp" in content_type:
                ext = ".webp"
            
            base, current_ext = os.path.splitext(output_path)
            if current_ext.lower() not in [".jpg", ".jpeg", ".png", ".gif", ".webp"]:
                output_path = base + ext
            else:
                output_path = base + current_ext
            
            with open(output_path, 'wb') as f:
                f.write(response.content)
            logging.info(f"图片下载成功: {url} -> {output_path}")
            return output_path
        else:
            logging.error(f"图片下载失败,状态码: {response.status_code}, URL: {url}")
    except Exception as e:
        logging.error(f"下载图片失败: {url} - {e}")
    return None

def replace_image_urls(body, issue_number, output_dir, token=None):
    """替换正文中的远程图片为本地图片"""
    img_pattern = r"!\[(.*?)\]\((https?:\/\/[^\)]+)\)"
    
    def replacer(match):
        alt_text = match.group(1)
        img_url = match.group(2)
        filename = f"{issue_number}_{safe_filename(img_url)}"
        output_path = os.path.join(output_dir, filename)
        final_path = download_image(img_url, output_path, token)
        if final_path:
            final_filename = os.path.basename(final_path)
            return f"![{alt_text}]({final_filename})"
        return match.group(0)
    
    return re.sub(img_pattern, replacer, body, flags=re.IGNORECASE)

def sanitize_markdown(content):
    """清理Markdown中的不安全内容"""
    if not content:
        return ""
    
    soup = BeautifulSoup(content, "html.parser")
    allowed_tags = ["p", "a", "code", "pre", "blockquote", "ul", "ol", "li", "strong", "em", "img", "h1", "h2", "h3", "h4", "h5", "h6"]
    for tag in soup.find_all(True):
        if tag.name not in allowed_tags:
            tag.unwrap()
    
    return str(soup)

def extract_tags_from_body(body, logger):
    """从正文最后一行提取标签,使用 $tag$ 格式,并返回清理后的正文"""
    if not body:
        logger.debug("Body is empty, no tags to extract")
        return [], body
    
    body = body.replace('\r\n', '\n').rstrip()
    lines = body.split('\n')
    if not lines:
        logger.debug("No lines in body, no tags to extract")
        return [], body
    
    last_line = lines[-1].strip()
    logger.debug(f"Last line for tag extraction: '{last_line}'")
    
    tags = re.findall(r'\$(.+?)\$', last_line, re.UNICODE)
    tags = [tag.strip() for tag in tags if tag.strip()]
    
    if tags:
        logger.debug(f"Extracted tags: {tags}")
        body = '\n'.join(lines[:-1]).rstrip()
    else:
        logger.debug("No tags found in last line")
    
    return tags, body

def convert_issue(issue, output_dir, token, logger):
    """转换单个issue为Hugo内容"""
    try:
        labels = [label.name for label in issue.labels]
        if PUBLISH_LABEL not in labels or issue.state != "open":
            logger.debug(f"跳过 issue #{issue.number} - 未标记为发布")
            return False
        
        pub_date = issue.created_at.strftime("%Y%m%d")
        slug = f"{pub_date}_{issue.number}"
        post_dir = os.path.join(output_dir, slug)
        
        if os.path.exists(post_dir):
            logger.info(f"跳过 issue #{issue.number} - 目录 {post_dir} 已存在")
            return False
        
        os.makedirs(post_dir, exist_ok=True)
        
        body = issue.body or ""
        logger.debug(f"Raw issue body: '{body}'")
        cover_url, body = extract_cover_image(body)
        tags, body = extract_tags_from_body(body, logger)
        body = sanitize_markdown(body)
        body = replace_image_urls(body, issue.number, post_dir, token)
        logger.info(f"图片处理完成,{issue.number} 号 issue")
        
        categories = [tag for tag in labels if tag in CATEGORY_MAP]
        category = categories[0] if categories else "生活"
        
        cover_name = None
        if cover_url:
            try:
                cover_filename = f"cover_{safe_filename(cover_url)}"
                cover_path = os.path.join(post_dir, cover_filename)
                final_cover_path = download_image(cover_url, cover_path, token)
                if final_cover_path:
                    cover_name = os.path.basename(final_cover_path)
                    logger.info(f"封面图下载成功:{cover_url} > {cover_name}")
                else:
                    logger.error(f"封面图下载失败:{cover_url}")
            except Exception as e:
                logger.error(f"封面图下载失败:{cover_url} - {e}")
        
        title_escaped = issue.title.replace('"', '\\"')
        category_escaped = category.replace('"', '\\"')
        frontmatter_lines = [
            "---",
            f'title: "{title_escaped}"',
            f"date: \"{issue.created_at.strftime('%Y-%m-%d')}\"",
            f"slug: \"{slug}\"",
            f"categories: [\"{category_escaped}\"]",
            f"tags: {json.dumps(tags, ensure_ascii=False)}"
        ]
        
        if cover_name:
            frontmatter_lines.append(f"image: \"{cover_name}\"")
        
        frontmatter_lines.append("---\n")
        frontmatter = "\n".join(frontmatter_lines)
        
        md_file = os.path.join(post_dir, "index.md")
        with open(md_file, "w", encoding="utf-8") as f:
            f.write(frontmatter + body)
        
        logger.info(f"成功转换 issue #{issue.number}{md_file}")
        return True
    except Exception as e:
        logger.exception(f"转换 issue #{issue.number} 时发生严重错误")
        error_file = os.path.join(output_dir, f"ERROR_{issue.number}.tmp")
        with open(error_file, "w") as f:
            f.write(f"Conversion failed: {str(e)}")
        return False

def main():
    args = parse_arguments()
    logger = setup_logger(args.debug)
    
    token = args.token or os.getenv("GITHUB_TOKEN")
    if not token:
        logger.error("Missing GitHub token")
        return
    
    try:
        auth = Auth.Token(token)
        g = Github(auth=auth)
        repo = g.get_repo(args.repo)
        logger.info(f"已连接至 GitHub 仓库:{args.repo}")
    except Exception as e:
        logger.error(f"连接GitHub失败: {str(e)}")
        return
    
    os.makedirs(args.output, exist_ok=True)
    logger.info(f"输出目录: {os.path.abspath(args.output)}")
    
    processed_count = 0
    error_count = 0
    
    try:
        issues = repo.get_issues(state="open")
        total_issues = issues.totalCount
        logger.info(f"开始处理 {total_issues} 个打开状态的 issue")
        
        for issue in issues:
            if issue.pull_request:
                continue
            try:
                if convert_issue(issue, args.output, token, logger):
                    processed_count += 1
            except Exception as e:
                error_count += 1
                logger.error(f"处理 issue #{issue.number} 时出错: {str(e)}")
                try:
                    error_comment = f"⚠️ 转换为Hugo内容失败,请检查格式错误:\n\n```\n{str(e)}\n```"
                    if len(error_comment) > 65536:
                        error_comment = error_comment[:65000] + "\n```\n...(内容过长,部分已省略)"
                    
                    issue.create_comment(error_comment)
                    try:
                        error_label = repo.get_label("conversion-error")
                    except:
                        error_label = repo.create_label("conversion-error", "ff0000")
                    issue.add_to_labels(error_label)
                except Exception as inner_e:
                    logger.error(f"创建评论或添加标签时出错: {inner_e}")
    except Exception as e:
        logger.exception(f"获取issues时出错: {e}")
        
    summary = f"处理完成!成功转换 {processed_count} 个issues,{error_count} 个错误"
    if processed_count == 0:
        logger.info(summary + " - 没有需要处理的内容变更")
    else:
        logger.info(summary)
        
    if args.debug:
        logger.debug("内容目录状态:")
        logger.debug(os.listdir(args.output))

def parse_arguments():
    parser = argparse.ArgumentParser(description='Convert GitHub issues to Hugo content')
    parser.add_argument('--token', type=str, default=None, help='GitHub access token')
    parser.add_argument('--repo', type=str, required=True, help='GitHub repository in format owner/repo')
    parser.add_argument('--output', type=str, default='content/posts', help='Output directory')
    parser.add_argument('--debug', action='store_true', help='Enable debug logging')
    return parser.parse_args()

if __name__ == "__main__":
    main()

关键点

  • 图片下载download_image 使用 PAT 认证头(Authorization: token {token}),支持私有仓库附件下载。
  • 标签提取:从 Issue 正文最后一行提取 $tag$ 格式标签(支持空格,如 $搞啥 呢$)。
  • 重复检查:跳过已存在的文章目录(YYYYMMDD_X)。
  • Markdown 生成:生成标准 Hugo frontmatter 和内容,图片转为本地路径。

2. 设置 PAT

  1. 创建 PAT

    • 访问 GitHub SettingsDeveloper settingsPersonal access tokensTokens (classic)
    • 创建新 PAT,勾选 repo(包括私有仓库访问)和 workflow(触发工作流)。
    • 复制生成的 token。
  2. 添加到仓库

    • 转到仓库(e.g., h2dcc/lawtee.github.io)→ SettingsSecrets and variablesActionsSecrets
    • 添加新 secret,命名为 PAT_TOKEN,粘贴 PAT 值。

3. 编写 Issue 发布博客

在 GitHub Issue 中按以下格式编写博客文章:

  1. 添加“发布”标签
    • 创建新 Issue,添加标签 发布 和分类标签(如 技术生活)。
  2. 正文格式
    • 标题:Issue 标题作为文章标题。
    • 封面图:正文第一张图片作为封面(Markdown 格式:![Image](URL))。
    • 正文:Markdown 格式,支持图片、标题、链接等。
    • 标签:最后一行以 $tag$ 格式添加标签(支持空格,如 $Hugo Post$)。
  3. 示例 Issue
    1
    2
    3
    4
    5
    6
    7
    
    ![Image](https://github.com/user-attachments/assets/5cc86d74-ff70-401f-820d-520a99a504b9)
    ## 我的第一篇 Hugo 博客
    这是一篇通过 GitHub Issue 发布的博客。
    支持 **Markdown** 格式,图片会自动下载到本地。
    <!--more-->
    ![Another Image](https://github.com/user-attachments/assets/926e7e9b-d279-4db9-bbb9-60bdcedd1804)
    $Hugo$ $博客$ $Hugo Post$
    
  4. 提交:保存 Issue,触发 deploy.yml 工作流。

4. 工作流运行流程

  1. 触发 deploy.yml

    • Issue 打开(带有“发布”标签)或手动触发。
    • 脚本 issue_to_hugo.py 运行:
      • 提取标题、分类(从标签)、正文、封面图、标签。
      • 下载图片(使用 PAT 认证,适用于私有仓库)。
      • 生成 Markdown 文件到 content/posts/YYYYMMDD_X/index.md
      • 提交到 master 分支。
    • 触发 hugo.yml 通过 repository_dispatch 事件。
  2. 触发 hugo.yml

    • 构建 Hugo 站点(hugo --minify)。
    • 部署到 GitHub Pages。

5. 验证发布

  1. 检查 Actions 日志

    • 打开 GitHub Actions 选项卡。
    • 确认 Sync Issues to Hugo Content 运行:
      • 日志显示 图片下载成功成功转换 issue #X
      • 提交到 master(e.g., Automated: Sync GitHub Issues as content)。
    • 确认 Deploy Hugo site to Pages 运行:
      • 检查 Build with HugoDeploy to GitHub Pages 步骤。
  2. 检查生成文件

    • 打开 content/posts/YYYYMMDD_X/
      • index.md 包含 frontmatter(title, date, slug, categories, tags, image)和正文。
      • 本地图片文件(e.g., X_uuid.jpg)存在。
    • 示例 index.md
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      
      ---
      title: "我的第一篇 Hugo 博客"
      date: "2025-10-27"
      slug: "20251027_2"
      categories: ["技术"]
      tags: ["Hugo", "博客", "Hugo Post"]
      image: "cover_5cc86d74-ff70-401f-820d-520a99a504b9.jpg"
      ---
      ## 我的第一篇 Hugo 博客
      这是一篇通过 GitHub Issue 发布的博客。
      支持 **Markdown** 格式,图片会自动下载到本地。
      <!--more-->
      ![Another Image](2_926e7e9b-d279-4db9-bbb9-60bdcedd1804.jpg)
      
  3. 访问站点

    • 访问 GitHub Pages 站点(e.g., https://h2dcc.github.io 或私有仓库的 Pages URL)。
    • 确认新文章显示,图片加载正常,标签和分类正确。

6. 常见问题及解决

  • 图片下载失败(404)
    • 原因:私有仓库图片需要 PAT 认证。
    • 解决:确保 PAT_TOKENrepo 权限,脚本已添加认证头。
  • Hugo 构建未触发
    • 原因repository_dispatch 事件失败。
    • 解决:检查 deploy.ymlTrigger Hugo build 步骤日志,确认 curl 请求返回 204。
  • 标签未提取
    • 原因:标签格式错误或不在最后一行。
    • 解决:确保标签以 $tag$ 格式在最后一行,空格需包含在 $ 内(如 $搞啥 呢$)。
  • 重复文章
    • 原因:脚本会跳过已有目录(YYYYMMDD_X)。
    • 解决:删除 content/posts/YYYYMMDD_X/ 后重新运行。

7. 优化建议

  • 单 Issue 处理:修改 deploy.ymlissue_to_hugo.py 支持仅处理触发 Issue,减少运行时间。
  • 错误通知:在 Issue 中添加评论,通知转换失败原因。
  • 移动端优化:使用 GitHub 移动端 App + Shortcut Maker,简化发布流程。
  • 日志查看:启用 --debug 模式,检查详细日志。

总结

通过 GitHub Issue 发布 Hugo 博客,只需几个步骤即可实现从移动端快速发布到自动部署的全流程。相比传统方式,这种方法简单、高效,尤其适合需要随时记录灵感的博主。无论是公开还是私有仓库,配合 PAT 认证和 GitHub Actions,图片、标签、分类都能完美处理。快去试试吧!

Built with Hugo, Powered by Github.
全站约 370 篇文章 合计约 1058523 字