2025-06-12 - Dev Log

Gitea Actions CI/CD Pipeline for Skyweave

  • Date: 2025-05-14
  • Summary: This session focused on establishing a full Continuous Integration (CI) and Continuous Deployment (CD) pipeline for the skyweave Lua-based API project. We successfully configured a Gitea Actions runner, built a multi-stage CI workflow for linting and unit testing, and troubleshooted numerous environment and configuration issues to achieve a stable, passing build.
  • Potential Tags: #skyweave, #gitea-actions, #ci-cd, #lua, #openwrt, #embedded-devops
  • Potential Links: [[RVLink System Exploitation]], [[Skyweave Lua Framework]], [[Gitea Runner Configuration]]

Key Topics & Discussion Points

Initial CI/CD Goal: Automated Deployment

  • Objective: The initial request was to create a Gitea Action to automatically deploy the skyweave application to the MV2400 roof unit on a git push.
  • Initial Plan:
    • Create a .gitea/workflows/deploy.yml file.
    • Use Gitea Secrets to store SSH credentials (MV2400_HOST, MV2400_USER, etc.).
    • Use an action like appleboy/ssh-action to scp files and restart the service.

Gitea Runner Setup & Troubleshooting

  • Requirement: A Gitea Runner (act_runner) is required to execute the workflow jobs.
  • Initial Runner Issue: The user encountered a -bash: ./act_runner: Text file busy error.
    • Diagnosis: This error typically means the process is already running.
    • Troubleshooting: lsof and ps commands proved the process was not running, pointing to a more subtle issue.
  • Second Runner Issue: After re-downloading the binary, a new error appeared: fatal error: mallocgc called without a P or outside bootstrapping.
    • Diagnosis: This is a classic Go runtime error indicating a CPU architecture mismatch (e.g., running an amd64 binary on an aarch64 machine).
    • Resolution: The user needed to download the correct binary for their machine’s architecture, found by running uname -m.
  • Final Runner Approach: Docker
    • Decision: The user opted to use the official gitea/act_runner:latest Docker image for a cleaner setup.
    • Initial Docker Error: Error: daemon Docker Engine socket not found.
    • Diagnosis: The runner container, which needs to spawn other containers for CI jobs, could not access the host’s Docker daemon.
    • Resolution: The host’s Docker socket was mounted into the runner container by adding the -v /var/run/docker.sock:/var/run/docker.sock flag to the docker run command.

CI Stage 1: Lua Linting with luacheck

  • Objective: Automatically scan all Lua code for syntax errors and style violations.
  • Initial ci.yml Error: The first attempt failed because the actions/checkout@v4 step requires a Node.js runtime, which was not present in the minimal alpine:latest container.
    • Resolution: Switched the job’s container to node:lts-alpine and installed Lua tools inside it.
  • luacheck Configuration (.luacheckrc):
    • Challenge 1: luacheck flagged built-in busted functions (describe, it, assert) as undefined variables in the test files.
    • Initial Attempt: A separate .luacheckrc in the spec/ directory was proposed, but this hierarchical approach did not work in the CI environment.
    • Final, Correct Solution: The user discovered the correct syntax for the root .luacheckrc file to define per-file globals:
files['spec/'] = {
    read_globals = { 'describe', 'it', 'assert', ... }
}

Challenge 2: The linter was failing the build due to benign warnings (e.g., “line is too long”, “unused variable”).

  • Resolution: The user discovered that the warning codes in the .luacheckrc ignore table must be numeric strings (e.g., "111") not alphanumeric ("W111").

CI Stage 2: Unit Testing with busted

  • Objective: Run unit tests to verify the logic of individual Lua modules.
  • Test Target: We created a sample test file, spec/utils_spec.lua, to test the parse_query_string function in skyweave/utils.lua.
  • Initial busted Error: The test failed with a module 'json' not found error.
    • Diagnosis: The test environment did not know the location of the json.lua library, which resides in the lib/ directory. The application relies on a specific deployment path (/usr/lib/lua/) that doesn’t exist in the CI container.
    • Resolution: A pre-test step was added to the CI workflow to copy the library into the path searched by the test runner: cp lib/json.lua skyweave/.

Advanced Testing: Integration Test Simulation

  • User Question: The user correctly pointed out that running application code would fail in a generic container because it relies on OpenWrt-specific binaries like iwinfo and uci.
  • Proposed Solution: A high-fidelity simulation using a custom Docker image.
    • Base Image: Use an x86 build of OpenWrt/LEDE to provide a similar filesystem and base utilities.
    • Mocking: Create mock scripts (fake iwinfo, uci, etc.) that live in a separate directory (/usr/bin_mock) and prepend this directory to the container’s $PATH. These scripts would return predictable, static output for the application to parse during tests.
    • CI Job: A new integration-test job would build this Docker image, run a web server with the CGI script, and then use curl or a Python script to make real HTTP calls to the running service to verify end-to-end functionality against the mocked data.

Decisions Made

  1. The Gitea Runner will be run via the official Docker image for ease of management.
  2. The CI pipeline will consist of two main jobs: lint-all-code and unit-test-lua.
  3. A single, unified .luacheckrc file will be used, leveraging the files['spec/'] syntax to apply specific rules to test files.
  4. Specific, non-critical luacheck warnings will be ignored via the .luacheckrc file to prevent CI failures on stylistic issues.
  5. A pre-test step will be included in the CI workflow to copy necessary libraries (json.lua) into the test environment’s path.

Action Items

  • Expand Unit Test Coverage: Write more busted tests for other functions in utils.lua and for the business logic in the skyweave/handlers/ modules.
  • (Future) Implement Integration Testing: When ready, create the test/Dockerfile and the mock scripts to build the high-fidelity simulation environment.
  • (Future) Add Integration Test Job: Add the integration-test job to the .gitea/workflows/test.yml file to automate the end-to-end testing process.

Key Takeaways & Conclusions

  • CI for Embedded is Nuanced: Testing applications for bespoke embedded environments requires simulating those environments, often through custom Docker images and mock executables.
  • Configuration is Key: The majority of troubleshooting involved finding the correct syntax and structure for configuration files (.luacheckrc) and CI workflows (.yml), and understanding the specific requirements of the tools (luacheck, busted, act_runner).
  • Iterative Problem Solving: The final, successful pipeline was achieved through a collaborative, iterative process of proposing a solution, analyzing the failure log, and refining the approach.
  • Success! A fully functional, two-stage CI pipeline is now operational, automatically linting and unit-testing the skyweave project.

Wesley Ray · blog · git · resume