4 điểm bởi GN⁺ 2025-04-09 | 2 bình luận | Chia sẻ qua WhatsApp
  • shell của GitHub Actions là một thiết lập quy định cách thực thi khối run:, nhưng trên thực tế mục tiêu của nó không phải là một danh sách shell cố định mà còn có thể là các tệp thực thi trong $PATH
  • Trong workflow thì đây là tùy chọn, còn trong định nghĩa action thì là bắt buộc, và giá trị mặc định thay đổi theo runner, như bash trên Linux/macOS và pwsh trên Windows
  • Khi chỉ định rõ như shell: bash, GitHub sẽ gắn thêm các cờ bổ sung như bash --noprofile --norc -eo pipefail, nhưng bản thân mục tiêu thực thi vẫn có thể là một chương trình tùy ý
  • Nếu tệp thực thi không nhận trực tiếp một đầu vào là tệp đơn, cần thêm đối số {0} vào giá trị shell; GitHub sẽ thay nó bằng đường dẫn tới tệp tạm chứa khối run
  • Ngay cả những tên quen thuộc như bash cũng được phân giải từ $PATH, nên việc thay đổi $GITHUB_PATH hoặc có một tệp thực thi giả có thể ảnh hưởng đến cách các bước sau đó được chạy

Cách hoạt động mặc định của từ khóa shell

  • Từ khóa shell trong GitHub Actions dùng để chỉ định shell nào sẽ chạy một khối run: cụ thể
  • Trong workflow thì đây là tùy chọn, nhưng trong định nghĩa action thì bắt buộc
  • Shell mặc định được quyết định theo môi trường runner
    • Trên Linux và macOS thường là bash
    • Trên Windows thường là pwsh

Chỉ định shell tường minh và các cờ bổ sung

  • GitHub trong tài liệu defaults.run.shell cho biết rằng khi chỉ định shell tường minh, GitHub cũng áp dụng các cờ do GitHub chọn
  • Ví dụ, khi chỉ định shell: bash, dạng thực thi sẽ trở thành
    • bash --noprofile --norc -eo pipefail
  • Chỉ nhìn vào hành vi này thì rất dễ nghĩ rằng GitHub quản lý một danh sách giá trị shell hợp lệ bị giới hạn, và chỉ gắn các cờ đặc biệt cho những giá trị đó

Tệp thực thi tùy ý trong $PATH cũng có thể trở thành shell

  • Trên thực tế, có thể chỉ định bất kỳ tệp thực thi nào có trong $PATH cho shell
  • GitHub sẽ dùng tệp thực thi đã chỉ định đó để chạy khối run
  • Nếu lệnh đó không trực tiếp nhận một đầu vào là tệp đơn, cần truyền {0} trong giá trị shell
    • GitHub sẽ đổi {0} thành đường dẫn tệp tạm
    • Tệp tạm này chứa nội dung của khối run sau khi mở rộng template

Ví dụ dùng C làm trình chạy step

  • Những công cụ hoạt động như trình biên dịch/trình thông dịch C cũng có thể được dùng để thực thi step trong GitHub Actions
  • Ví dụ chỉ định tcc -run {0} làm shell hoạt động bình thường
- run: sudo apt install -y tcc
- shell: tcc -run {0}
  run: |
    #include <stdio.h>
    int main() {
      printf("Hello, world!\n");
      return 0;
    }

Ví dụ thay đổi cách phân giải shell bằng $GITHUB_PATH

  • Nếu sửa đổi $PATH động thông qua $GITHUB_PATH, thì shell: bash ở các bước sau có thể trỏ tới một tệp thực thi khác với kỳ vọng
  • Ví dụ tạo một tệp thực thi tên bash trong thư mục hiện tại rồi thêm thư mục hiện tại vào $GITHUB_PATH
  • Sau đó khi dùng shell: bash, GitHub có thể chạy bash giả được tìm thấy trong $PATH
- run: |
    touch ./bash
    chmod +x ./bash
    echo '#!/bin/sh' > ./bash
    echo 'echo hello from fake bash' >> ./bash
    echo "${PWD}" >> "${GITHUB_PATH}"
- run: |
    echo "this doesn't do what you expect"
  shell: bash

Điểm bất ngờ về mặt bảo mật

  • Khó có thể khẳng định dứt khoát liệu hành vi này có quan trọng về mặt bảo mật hay không
  • Trong GitHub Actions đã có sẵn nhiều con đường mà việc ghi tệp có thể dẫn tới thực thi, và GITHUB_ENV cũng nằm trong số đó
  • Dù vậy, việc GitHub vẫn tra cứu qua $PATH ngay cả với những giá trị shell quen thuộc như bash có thể là điều ngoài dự đoán
  • Đặc biệt, nếu GitHub chèn các cờ dòng lệnh tùy theo những giá trị shell quen thuộc, người ta có thể kỳ vọng bash sẽ được cố định vào một đường dẫn cụ thể như /bin/bash
  • Tổng quát hơn, cũng có thể nghĩ rằng GitHub chỉ cho phép các công cụ đã được đăng ký sẵn trong tool cache, nhưng hành vi quan sát được là sử dụng các tệp thực thi trong $PATH

2 bình luận

 
tujuc 2025-04-09

Chỉ nhìn vào repo github/runner-image thôi cũng thấy có khá nhiều gói đã được cài sẵn để có thể dùng ngay...

Tạo image là dung lượng đội lên 1GB ngay...

 
GN⁺ 2025-04-09
Ý kiến trên Hacker News
  • Trước đây tôi từng dùng cờ -x để khiến bash in ra mọi lệnh mà nó chạy trong workflow Actions, và nó khá hữu ích cho việc gỡ lỗi
    https://github.com/jstrieb/just.sh/blob/2da1e2a3bfb51d583be0...
    • Nhân tiện, nếu bật pipefail thì khi một phần trong pipeline thất bại, bước đó sẽ fail, nhưng không có output lỗi nên có thể bạn sẽ không biết vì sao nó fail
      pipefail cũng không ngăn được các trạng thái lỗi phức tạp hơn. Ví dụ, trong bước curl ... | sudo tar ... của cấu hình, các trường hợp như giải nén tar thất bại, sudo thất bại, hay lỗi shell sẽ lộ ra, nhưng nếu curl bị lỗi mạng và fail giữa chừng, tar có thể chỉ giải nén một nửa binary just rồi shell thoát ngay. Khi đó không có thông báo lỗi, file thực thi bị hỏng vẫn nằm trên đĩa, và nếu bật bỏ qua khi lỗi thì thậm chí có thể nó còn được thử chạy
  • Một mẹo GitHub Actions riêng tư khá hay mà tôi thấy ở chỗ làm là có thể dùng wildcard trong tên sự kiện repository_dispatch để match
    Có thể viết kiểu on: repository_dispatch: - security_scan - security_scan::*. Nếu tập trung hóa release pipeline, bạn có thể buộc từng repository đi qua workflow tái sử dụng đã định nghĩa, và nếu gửi sự kiện như security_scan::$product_name::$version thì trong tab Actions của repository release trung tâm sẽ dễ hơn nhiều để biết workflow của sản phẩm và phiên bản nào đang chạy
    • Tôi tò mò cách tiếp cận tập trung hóa này trên thực tế có hoạt động tốt không. Tổ chức có yêu cầu mọi thứ phải được build theo đúng cùng một cách không?
      Tôi vừa gia nhập một tổ chức đang cố làm điều tương tự, nhưng thực tế trông gần như vô dụng. Template thường xuyên bị hỏng, và nhiều cái được viết với giả định về một cách build cụ thể mà không có tài liệu về việc code cần được build theo cách nào
  • Tôi nghĩ càng làm ít việc bên trong GitHub Actions thì càng tốt
    Thường tôi thích đặt logic vào hệ thống build như Make rồi chỉ gọi từ GitHub Actions, hoặc viết một chương trình dòng lệnh nhỏ để gọi. Những thứ như vậy dễ gỡ lỗi cục bộ hơn CI rất nhiều. Đây là một mẹo thú vị, nhưng tôi không chắc nó hữu dụng ở đâu
    • Workflow của chúng tôi thực ra chỉ cỡ make buildmake test
      Sau khi bị mua lại, tôi xem file workflow của công ty mua lại thì thấy nó dài hàng trăm dòng và có nhiều đoạn lặp. Có thể gọi tôi là cổ lỗ, nhưng tôi muốn thoát khỏi làng YAML càng nhanh càng tốt
    • Bài này có vẻ không phải tác giả khuyên mọi người làm vậy, mà là cho biết điều đó là khả thi
      Ngay cả với một tính năng bạn không định dùng thật, biết hệ thống có thể làm được gì vẫn hữu ích cho bảo mật và gỡ lỗi
    • Theo cách hiểu của tôi thì việc này không hữu ích, chắc chắn là không hữu ích. Nhưng nó có rủi ro bảo mật tiềm tàng
      Đặc biệt khi “khám phá” runner tự host
  • Thế hệ chúng tôi từng khiếp sợ khi được yêu cầu chuyển các bảng tính liên tục thay đổi thành code
    Thế hệ tiếp theo sẽ run lên khi được yêu cầu thiết lập kỷ luật cho triển khai làm bằng GitHub Actions
    • Hiện tôi đang làm việc chuyển một enterprise lớn sang GH Actions, chính xác hơn là “pipeline dựa trên YAML gắn với git”. Ở đây kỷ luật nên trông như thế nào?
      Nếu có thể mô tả được, có lẽ chúng tôi có thể hiện thực hóa nó trong tổ chức của mình
    • Tôi tò mò vì sao cái này lại là thiếu kỷ luật
  • Cũng có thể đánh lừa shell mặc định bash để nó chạy chương trình tùy ý
  • Nếu những người khác đọc Action biết chuyện gì đang diễn ra thì có vẻ khá hữu ích
    Tôi đã nhiều lần thấy shell script khởi đầu gần như chỉ là vài dòng lệnh gõ tay rồi biến thành con quái vật hơn trăm dòng, và mỗi lần như vậy tôi đều ước có mảng và kiểu dữ liệu thật sự, cùng các tính năng batteries included của thư viện chuẩn Python. Nhưng tôi sẽ không triển khai Action build của công ty bằng elisp đâu
  • Code của Github Actions Runner khá dễ đọc. Đây là nơi định nghĩa các đối số mặc định cho các shell/binary phổ biến: https://github.com/actions/runner/blob/main/src/Runner.Worke...
    Nó được expose qua phương thức ScriptHandlerHelpers.GetScriptArgumentsFormat. Trong ScriptHandler.cs có code chuẩn bị môi trường process và đối số, còn code thực sự khởi chạy process nằm ở đây: https://github.com/actions/runner/blob/main/src/Runner.Worke...
    Nhìn chung tôi ngạc nhiên theo hướng tích cực về sự đơn giản của phần code này. Nó rất thủ tục và xử lý rất nhiều trường hợp ngoại lệ, nhưng có vẻ dễ hiểu và dễ gỡ lỗi
  • Giờ cuối cùng cũng có thể dùng C trong CI/CD production và gọi đó là “công việc hệ thống cấp thấp”
    Có lẽ assembly cũng được
  • Điều này khiến tôi hy vọng rằng dùng goeval [1] để chạy trực tiếp code Go như tác vụ CI từ file YAML workflow của GitHub có thể trở nên dễ dàng
    Tuy nhiên goeval hiện chưa hỗ trợ nhập file trực tiếp mà chỉ hỗ trợ standard input, nên cần mẹo shell. Hiện tại là kiểu go run github.com/dolmen-go/goeval@v1 - <<'EOF' ... EOF, cần một ít boilerplate. Nhân tiện, tôi là tác giả của goeval
    [1] https://github.com/dolmen-go/goeval
    • Tôi không hiểu vì sao cần “mẹo” shell. go run github.com/dolmen-go/goeval@v1 - < file.go không được sao?
    • Tôi vào repository xem có phải là chương trình tự động đổi khoảng trắng thành tab không, nhưng nhìn thì có vẻ nó hướng nhiều hơn tới biểu thức một dòng
      Trong bối cảnh này, nếu muốn quảng bá một dự án đồ chơi thì giá mà đưa ví dụ liên quan hơn “in hello” thì đã đỡ mất một cú click
  • GitHub CI YAML có điểm gì hơn bash script kiểu run: pipeline.sh?
    • Có token GitHub API được quản lý tự động, hữu ích cho tự động hóa release, xuất bản artifact và container
      Có thể chạy đồng thời công việc trên nhiều hệ điều hành và kiến trúc CPU, đồng thời lấy được thông tin ngữ cảnh về sự kiện kích hoạt công việc và trạng thái repository. Nó tiện cho các job theo từng PR hoặc tự động hóa release, và cũng tích hợp được với GitHub web UI, chẳng hạn linter hiển thị vấn đề trên từng dòng của PR hoặc render lỗi test lên trang web. Cũng có thể dùng cache nhỏ để tránh tải lại hoặc build lại các file không thay đổi. Lý tưởng nhất là đưa càng nhiều càng tốt vào các công cụ thông thường chạy được cục bộ, còn cấu hình GitHub CI chỉ đảm nhiệm phần keo dán cần cho trigger, caching và tích hợp GitHub
    • Trong GitHub UI, bạn có thể xem tổng quan từng bước, mở rộng hoặc thu gọn output theo bước
      Có thể dễ dàng tận dụng GitHub Actions do người khác tạo trong pipeline, module hóa workflow và kiểm soát phụ thuộc cùng thực thi song song. Chắc còn nhiều nữa, nhưng lợi ích cốt lõi là bạn không phải tự triển khai những thứ đó
    • Với GitHub, lợi ích là người dùng ít có khả năng chuyển sang Git forge khác hơn