From f1c8af254f474061f345c2ab64bcd671fa4c755b Mon Sep 17 00:00:00 2001 From: Jack Woods Date: Sat, 4 Apr 2026 12:27:51 -0400 Subject: [PATCH] Making a CI-CD pipeline --- .github/workflows/ci.yml | 44 ++++++++++++++++ .github/workflows/release.yml | 81 +++++++++++++++++++++++++++++ host/linkctl-daemon/install.sh | 94 ++++++++++++++++++++++++++++++++++ src/serial_cli.h | 1 + 4 files changed, 220 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100755 host/linkctl-daemon/install.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f405c41 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + strategy: + matrix: + include: + - os: macos-14 + target: daemon-mac + # - os: windows-latest + # target: daemon-win + # - os: ubuntu-latest + # target: firmware + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + # --- daemon-mac --- + - name: Install dependencies (macOS) + if: matrix.target == 'daemon-mac' + run: brew install libwebsockets cjson openssl@3 + + - name: Build daemon (macOS) + if: matrix.target == 'daemon-mac' + working-directory: host/linkctl-daemon + run: | + mkdir -p build && cd build + cmake .. + make -j$(sysctl -n hw.ncpu) + + - name: Upload build artifact + if: matrix.target == 'daemon-mac' + uses: actions/upload-artifact@v4 + with: + name: linkctl-daemon-${{ matrix.os }} + path: host/linkctl-daemon/build/linkctl-daemon diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3f12e77 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,81 @@ +name: Release + +on: + push: + tags: + - 'v*.*.*' + +permissions: + contents: write + +jobs: + build-release: + strategy: + matrix: + include: + - os: macos-14 + target: daemon-mac + arch: arm64 + # - os: windows-latest + # target: daemon-win + # arch: x64 + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + - name: Extract version from tag + id: version + run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + + # --- daemon-mac --- + - name: Install dependencies (macOS) + if: matrix.target == 'daemon-mac' + run: brew install libwebsockets cjson openssl@3 + + - name: Build daemon (macOS) + if: matrix.target == 'daemon-mac' + working-directory: host/linkctl-daemon + run: | + mkdir -p build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + make -j$(sysctl -n hw.ncpu) + + - name: Package release (macOS) + if: matrix.target == 'daemon-mac' + id: package + run: | + STAGING="linkctl-daemon-${{ steps.version.outputs.VERSION }}-macos-${{ matrix.arch }}" + mkdir -p "$STAGING" + cp host/linkctl-daemon/build/linkctl-daemon "$STAGING/" + cp host/linkctl-daemon/install.sh "$STAGING/" + chmod +x "$STAGING/install.sh" + cp README.md "$STAGING/" + tar czf "${STAGING}.tar.gz" "$STAGING" + echo "ARCHIVE=${STAGING}.tar.gz" >> "$GITHUB_OUTPUT" + + - name: Upload release artifact + uses: actions/upload-artifact@v4 + with: + name: release-${{ matrix.target }} + path: ${{ steps.package.outputs.ARCHIVE }} + + create-release: + needs: build-release + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Download all release artifacts + uses: actions/download-artifact@v4 + with: + pattern: release-* + merge-multiple: true + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: "*.tar.gz" + generate_release_notes: true diff --git a/host/linkctl-daemon/install.sh b/host/linkctl-daemon/install.sh new file mode 100755 index 0000000..e464f27 --- /dev/null +++ b/host/linkctl-daemon/install.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# install.sh — Install linkctl-daemon from a release archive. +# Run from inside the extracted release directory. +set -euo pipefail + +INSTALL_DIR="/usr/local/bin" +BINARY="linkctl-daemon" +LABEL="com.linkctl.daemon" +DOMAIN="gui/$(id -u)" +PLIST_DIR="$HOME/Library/LaunchAgents" +PLIST="$PLIST_DIR/$LABEL.plist" +LOG_FILE="$HOME/Library/Logs/linkctl-daemon.log" + +echo "linkctl-daemon installer" +echo "========================" +echo + +# Check we're on macOS +if [ "$(uname)" != "Darwin" ]; then + echo "Error: this installer is for macOS only." + exit 1 +fi + +# Check the binary is present +if [ ! -f "$BINARY" ]; then + echo "Error: $BINARY not found in current directory." + echo "Run this script from inside the extracted release archive." + exit 1 +fi + +# Check Homebrew dependencies +echo "Checking dependencies..." +MISSING=() +for dep in libwebsockets cjson openssl@3; do + if ! brew list --formula "$dep" &>/dev/null; then + MISSING+=("$dep") + fi +done + +if [ ${#MISSING[@]} -gt 0 ]; then + echo "Missing Homebrew packages: ${MISSING[*]}" + echo "Install with: brew install ${MISSING[*]}" + exit 1 +fi +echo " All dependencies found." + +# Install binary +echo "Installing $BINARY to $INSTALL_DIR..." +sudo install -m 755 "$BINARY" "$INSTALL_DIR/$BINARY" +echo " Done." + +# Create launchd plist +echo "Setting up launchd service..." +mkdir -p "$PLIST_DIR" +cat > "$PLIST" < + + + + Label + $LABEL + ProgramArguments + + $INSTALL_DIR/$BINARY + + RunAtLoad + + KeepAlive + + ThrottleInterval + 10 + StandardOutPath + $LOG_FILE + StandardErrorPath + $LOG_FILE + + +EOF + +# Load service (unload first if already running) +launchctl bootout "$DOMAIN/$LABEL" 2>/dev/null || true +launchctl bootstrap "$DOMAIN" "$PLIST" +echo " Service started." + +echo +echo "Installation complete!" +echo " Binary: $INSTALL_DIR/$BINARY" +echo " Service: $LABEL (running)" +echo " Logs: $LOG_FILE" +echo +echo "To uninstall:" +echo " launchctl bootout $DOMAIN/$LABEL" +echo " sudo rm $INSTALL_DIR/$BINARY" +echo " rm $PLIST" diff --git a/src/serial_cli.h b/src/serial_cli.h index f019200..b4ac01a 100644 --- a/src/serial_cli.h +++ b/src/serial_cli.h @@ -26,6 +26,7 @@ class SerialCLI { // Config setters (used by captive portal) void setWifiConfig(const String& ssid, const String& password); void setServerConfig(const String& host, uint16_t port); + void setJoyCenter(int centerX, int centerY); // Request portal start bool portalRequested() const { return _portalRequested; }