Tự động build blog (dùng Pelican) với GitHub Actions

Tự động build blog (dùng Pelican) với GitHub Actions
Photo by Testalize.me from Unsplash

Blog này của tôi dùng Pelican để build và đang lưu trữ trên GitHub Pages. Cơ chế của nó rất đơn giản, toàn bộ nội dung sẽ được ghi thành HTML tĩnh (kết hợp với JS và CSS) và đưa lên GitHub Pages. Với những gì tôi cần thì thực sự GitHub Pages quá tốt và tôi không cần gì hơn thế 😻.

Còn về Pelican thì đó là một câu chuyện dài 🌬️. Pelican là công cụ để build những trang web tĩnh (static site generator). GitHub Pages thì tích hợp sẵn Jekyll (ngày tôi biết đến nó là thế) và nếu dùng Jekyll thì việc build và deploy trang web là hoàn toàn tự động (nó còn có sẵn config mặc định khá ngon rồi). Thế nhưng tôi lại không chọn cách đó, mà lại đi còn đường đau khổ hơn 😫.

Có nhiều nguyên nhân khiến tôi lựa chọn Pelican chứ không phải là Jekyll. Tôi nhớ một số lý do chính đó là:

  • Jekyll tích hợp sẵn với GitHub Pages chuyển sang dùng kramdown thay cho Redcarpet (vốn là công cụ được dùng phổ biến trên GitHub, nhưng không hiểu sao GitHub Pages lại không dùng nữa 🤔), và tôi mất rất nhiều thời gian để cấu hình kramdown mà vẫn không được như ý.
  • Cú pháp markdown kiểu GitHub (GitHub flavored markdown) không được hỗ trợ bởi kramdown, do đó những bài viết cũ phải sửa lại mới hiển thị được.
  • Tôi không thể nào tùy biến Jekyll để xử lý một số chỗ trong file HTML đầu ra được. Do nguyên nhân trên nên đầu ra HTML không vừa ý nhưng tôi không thay đổi kiểu gì được 😩.
  • Cuối cùng là Jekyll dùng Liquid template và một công cụ gì đó (được viết bằng Ruby cho giống Jekyll và kramdown) để highlight code và tôi thường xuyên gặp lỗi khi viết bài về Python (do cú pháp template của Django hay Jinja rất giống Liquid và thường xuyên bị xử lý nhầm 🤦).

Chuyện cũng là của 6-7 năm về trước, giờ tôi không biết GitHub Pages và Jekyll đã thay đổi như thế nào rồi, nhưng tôi đang rất hài lòng với Pelican và không có nhu cầu thay đổi một công cụ khác nữa trong tương lai gần.

Tôi quản lý blog bằng 2 repo khác nhau: repo nguồn chứa mã nguồn (templates) và toàn bộ các bài viết bằng markdown, repo output chỉ chứa HTML được sinh bởi Pelican, hai repo liên kết bằng git submodule (thực ra thì không liên kết cũng được nhưng tôi liên kết lại cho dễ nhìn). Tôi đưa output lên GitHub Pages và nó chỉ cần deploy nguyên HTML đó là được (nên quá trình này khá là nhanh, nhanh hơn dùng Jekyll nhiều 😆).

Sử dụng Pelican kết hợp với GitHub Pages về cơ bản là tốt và tôi không thấy có vấn đề gì lớn trong nhiều năm qua. Duy có một vấn đề nhỏ. Đó là mỗi lần có bài viết mới, hay chỉnh sửa gì blog, tôi đều phải build lại trang và push lên GitHub. Dù đã tự động kha khá bằng Makefile nhưng việc này vẫn còn rất thủ công, do đó, tôi vẫn tìm một cách nào đó để tự động hóa nhiều hơn.

Chuyện này đúng ra gần đây tôi mới cảm thấy nó là vấn đề vì hiện tại blog đã ổn định và tôi chủ yếu thêm bài viết mới chứ không chỉnh sửa gì nhiều. Đồng thời chuyện DevOps rồi CI/CD cứ nghe thấy liên tục, nhiều người tự động hóa được bao nhiêu việc mà tôi vẫn còn làm thủ công thì tù quá. Thế mới bảo con đường tôi chọn trước đây là con đường đau khổ, nhưng biết làm thế nào bây giờ 😢.

Sau nhiều lần quyết tâm đi quyết tâm lại thì hôm nay, cuối cùng tôi cũng bắt tay vào làm thật. Tôi nghe nói đến GitHub Actions cũng khá lâu rồi, nhiều repo tôi fork về vẫn chạy Actions ầm ầm, nhưng tôi chưa thực sự động vào một action nào. Hôm nay, cuối cùng thì cũng tự tay cấu hình và xem action do tôi cấu hình chạy thật sự. Cảm giác đầu tiên là phê 🥳 sau đó là thấy sao trước đây tôi lại quá lười 😤.

GitHub Actions với tôi thì vừa quen mà vừa lạ. Quen là nó gần giống mấy công cụ CI tôi đã từng dùng thử (CircleCI, Travis). Về cơ bản là tôi sẽ cấu hình để mỗi khi tôi push commit mới, một action sẽ chạy và build trang web cho tôi lên GitHub Pages, tất cả đều tự động để tôi chỉ cần push lên repo nguồn thôi, còn repo output sẽ tự động hết.

Việc build trang web thì cũng đơn giản, tôi cấu hình cho nó chạy giống hệt như tôi vẫn chạy trước giờ trên PC thôi. Do quản lý bằng 2 repo riêng biệt, nên tôi có chỉnh sửa lại Makefile để build và push kết quả lên GitHub thế này:

github: LAST_COMMIT_MSG=`cd .. && git log -1 --pretty=%B`
github: publish
    cd output && git add . && git commit -m "$(LAST_COMMIT_MSG)" && git push origin

Giờ đây tôi cần chạy lệnh này (make github) tự động trên server (hoặc container, tôi cũng không hiểu sâu chỗ này) của GitHub. Để làm được việc đó, thì tôi thêm 1 file .github/workflows/build-blog.yml (tên thư mục thì bắt buộc còn tên file có thể thay đổi tùy ý):

name: Build blog
on:
  push:
    branches:
      - main
jobs:
  build_job:
    name: Build blog
    runs-on: ubuntu-latest
    env:
      TZ: 'Asia/Tokyo'
    steps:
      - name: Install Python
        uses: actions/setup-python@v2
        with:
          python-version: 3.10.2
          cache: 'pip'
      - name: Checkout source code
        uses: actions/checkout@v2
      - name: Install pelican and other packages
        run: |
          pip install -r requirements.txt
      - name: Checkout submodule
        uses: actions/checkout@v2
        with:
          repository: 'manhhomienbienthuy/manhhomienbienthuy.github.io'
          ssh-key: ${{ secrets.ACTION_DEPLOY_KEY }}
          path: 'output'
      - name: Setup git
        run: |
          git config --global user.name "username"
          git config --global user.email "username@domain"
          cd output
          git checkout main
      - name: Publish to GitHub pages
        run: |
          make github
      - name: Update dependency of submodule
        run: |
          git checkout main
          git add .
          git commit -m "Update dependency of submodule"
          git push origin main

Quá trình build có thể tóm tắt sơ lược như sau:

  • checkout repo nguồn và output
  • cài đặt Python và các package cần thiết
  • chạy lệnh build
  • commit sự thay đổi của cả output và nguồn rồi push lại lên GitHub

Mọi chuyện cũng đơn giản thôi nhưng tôi đã tự làm khó tôi khi sử dụng 2 repo khác nhau, và tôi phải mất 2 tiếng cuộc đời để tìm cách push từ action của repo này lên repo khác.

Tôi đã thử nhiều cách, nhưng mãi không được, bởi vì GitHub Actions checkout submodule luôn luôn dùng HTTPS (không rõ repo chính dùng HTTPS hay SSH, khả năng cao cũng là HTTPS) và không thể nào chuyển sang SSH được.

Cuối cùng thì cách tôi nghĩ ra là checkout submodule như một repo độc lập (dùng action/checkout riêng chứ không checkout submodule) với ssh key (thêm deploy key cho repo output để dùng vào lúc này) như tôi cấu hình ở trên.

Giờ đây, mỗi khi commit (ngay từ lúc commit file cấu hình này luôn) thì một action sẽ được chạy và tự động build luôn, quá tiện 👍. (Thật là ngại quá đi, 2 tiếng cuộc đời tôi đã phải sửa đi sửa lại mười mấy lần mới xong 😫 nên trong ảnh vẫn còn mấy lần build lỗi.)

action

Một điểm khá hay của GitHub action là nó chỉ trigger những lần tôi push từ máy tôi mà thôi, còn push từ chính action thì không chạy gì cả. Lúc đầu tôi cũng lo, nếu cứ trigger thì vòng lặp vô tận biết bao giờ mới dừng. Nhưng tôi cứ thử, kết quả là chẳng có vấn đề gì cả 💯.

Giờ đây, mọi thứ đã tự động, tôi chỉ cần lo viết bài (và chỉnh sửa template nếu cần) nữa là xong. Mỗi lần commit, trang web sẽ tự động được build và đưa lên GitHub Pages (nhưng sau đó phải pull về do có thêm 1 commit update submodule 😩).

Tôi xin lỗi nếu bài viết có bất kỳ typo nào. Nếu bạn nhận thấy điều gì bất thường, xin hãy cho tôi biết.

Nếu có bất điều gì muốn nói, bạn có thể liên hệ với tôi qua các mạng xã hội, tạo discussion hoặc report issue trên Github.

Welcome

manhhomienbienthuy

Đây là thế giới của manhhomienbienthuy (naa). Chào mừng đến với thế giới của tôi!

Bài viết liên quan

Bài viết mới

Chuyên mục

Lưu trữ theo năm

Thông tin liên hệ

Cảm ơn bạn đã quan tâm blog của tôi. Nếu có bất điều gì muốn nói, bạn có thể liên hệ với tôi qua các mạng xã hội, tạo discussion hoặc report issue trên Github.