name: Update Nix Packages with nix-update on: schedule: - cron: "0 2,14 * * *" # Every 12 hours at 2 AM and 2 PM workflow_dispatch: inputs: package: description: "Specific package to update (optional)" required: false type: string concurrency: group: nix-update-${{ github.ref }} cancel-in-progress: true env: GIT_AUTHOR_NAME: "nix-update bot" GIT_AUTHOR_EMAIL: "bot@m3ta.dev" GIT_COMMITTER_NAME: "nix-update bot" GIT_COMMITTER_EMAIL: "bot@m3ta.dev" REPO_DIR: "/tmp/nixpkgs" # Nix configuration NIX_PATH: "nixpkgs=channel:nixos-unstable" NIX_CONFIG: "experimental-features = nix-command flakes" # Non-interactive mode DEBIAN_FRONTEND: "noninteractive" GIT_TERMINAL_PROMPT: "0" jobs: nix-update: runs-on: nixos timeout-minutes: 180 steps: - name: Setup Environment and Authenticate run: | if [ -d "$REPO_DIR" ]; then rm -rf "$REPO_DIR"; fi git config --global credential.helper store echo "https://m3tam3re:${{ secrets.NIX_UPDATE_TOKEN }}@code.m3ta.dev" > ~/.git-credentials chmod 600 ~/.git-credentials git config --global user.name "$GIT_AUTHOR_NAME" git config --global user.email "$GIT_AUTHOR_EMAIL" git config --global init.defaultBranch master - name: Checkout Repository run: | git clone --no-single-branch \ "https://m3tam3re@code.m3ta.dev/m3tam3re/nixpkgs.git" \ "$REPO_DIR" - name: Update opencode Flake Input id: update-opencode run: | cd "$REPO_DIR" echo "::group::Checking for opencode updates" # Get latest release from GitHub API (strip v prefix for comparison) LATEST_RELEASE=$(curl -s "https://api.github.com/repos/anomalyco/opencode/releases/latest" | jq -r '.tag_name' | sed 's/^v//') # Extract current version from flake.nix CURRENT_VERSION=$(grep -oP 'opencode\.url = "github:anomalyco/opencode/v\K[^"]+' flake.nix) echo "Current opencode version: $CURRENT_VERSION" echo "Latest opencode version: $LATEST_RELEASE" # Check if update is needed if [ "$LATEST_RELEASE" != "$CURRENT_VERSION" ]; then echo "🔄 Updating opencode from $CURRENT_VERSION to $LATEST_RELEASE" # Update flake.nix with new version sed -i 's|opencode\.url = "github:anomalyco/opencode/v.*"|opencode.url = "github:anomalyco/opencode/v'"$LATEST_RELEASE"'"|' flake.nix # Update flake lock to fetch new revision nix flake update opencode # Format with alejandra nix fmt flake.nix # Verify the update echo "::endgroup::" echo "::group::Verifying opencode update" # Run flake check if ! nix flake check; then echo "❌ Flake check failed after opencode update" git checkout flake.nix flake.lock exit 1 fi # Build opencode package if ! nix build .#opencode 2>&1 | tee /tmp/opencode-build.log; then echo "❌ Build failed for opencode" git checkout flake.nix flake.lock exit 1 fi echo "✅ Flake check passed" echo "✅ Build successful for opencode" echo "::endgroup::" # Commit the change echo "::group::Committing opencode update" git add flake.nix flake.lock git commit -m "chore: update opencode flake input to $LATEST_RELEASE" echo "opencode_update=true" >> $GITHUB_OUTPUT echo "opencode_version=${LATEST_RELEASE}" >> $GITHUB_OUTPUT echo "::endgroup::" echo "✅ Updated opencode to $LATEST_RELEASE" else echo "✓ opencode is already up to date" echo "opencode_update=false" >> $GITHUB_OUTPUT echo "opencode_version=${CURRENT_VERSION}" >> $GITHUB_OUTPUT echo "::endgroup::" fi - name: Check Prerequisites id: check run: | cd "$REPO_DIR" if [ ! -d "pkgs" ]; then echo "❌ Error: 'pkgs' directory not found." exit 1 fi if [ -f "flake.nix" ]; then echo "has_flake=true" >> $GITHUB_OUTPUT else echo "has_flake=false" >> $GITHUB_OUTPUT fi - name: Update Packages id: update run: | cd "$REPO_DIR" set -e git checkout master UPDATES_FOUND=false UPDATED_PACKAGES="" check_commit() { [ "$1" != "$(git rev-parse HEAD)" ] && echo "true" || echo "false" } has_update_script() { local pkg=$1 # Check if package has passthru.updateScript attribute nix eval .#${pkg}.passthru.updateScript --json >/dev/null 2>&1 } run_update() { local pkg=$1 local before_hash=$(git rev-parse HEAD) echo "::group::Updating $pkg" local args=("--flake" "--commit" "--use-github-releases") args+=("$pkg") if nix-update "${args[@]}" 2>&1 | tee /tmp/update-${pkg}.log; then if [ "$(check_commit "$before_hash")" = "true" ]; then echo "✅ Updated $pkg" echo "::endgroup::" return 0 fi fi echo "::endgroup::" if ! grep -q "already up to date\|No new version found" /tmp/update-${pkg}.log; then echo "âš ī¸ Update failed for $pkg" fi return 1 } if [ -n "${{ inputs.package }}" ]; then pkg="${{ inputs.package }}" if [ -d "pkgs/$pkg" ]; then if run_update "$pkg"; then UPDATES_FOUND=true UPDATED_PACKAGES="$pkg" fi else echo "❌ Package 'pkgs/$pkg' not found" fi else # Dynamically discover packages with updateScript attribute echo "🔍 Discovering packages with passthru.updateScript..." # Get all packages and filter those with updateScript ALL_PACKAGES=$(find pkgs -mindepth 1 -maxdepth 1 -type d -exec basename {} \; 2>/dev/null | sort) UPDATABLE_PACKAGES="" if [ -z "$ALL_PACKAGES" ]; then echo "No packages found in pkgs/" exit 0 fi for pkg in $ALL_PACKAGES; do if has_update_script "$pkg"; then echo " ✓ $pkg (has updateScript)" UPDATABLE_PACKAGES="$UPDATABLE_PACKAGES $pkg" else echo " ⊘ $pkg (no updateScript - skipping)" fi done if [ -z "$UPDATABLE_PACKAGES" ]; then echo "â„šī¸ No packages with updateScript found." exit 0 fi echo "" echo "đŸ“Ļ Found $(echo $UPDATABLE_PACKAGES | wc -w) updatable packages" echo "" # Parallel updates with 4 concurrent jobs MAX_JOBS=4 JOB_COUNT=0 SUCCESS_LIST=() for pkg in $UPDATABLE_PACKAGES; do (run_update "$pkg" && echo "$pkg" >> /tmp/success.txt || true) & JOB_COUNT=$((JOB_COUNT + 1)) # Wait if we hit max concurrent jobs if [ $JOB_COUNT -ge $MAX_JOBS ]; then wait JOB_COUNT=0 fi done # Wait for remaining jobs wait # Parse results if [ -f /tmp/success.txt ]; then SUCCESS_LIST=$(cat /tmp/success.txt | tr '\n' ' ') UPDATED_PACKAGES=$(echo "$SUCCESS_LIST" | sed 's/ /, /g' | sed 's/, $//') UPDATES_FOUND=true fi rm -f /tmp/success.txt fi COMMIT_COUNT=$(git rev-list --count origin/master..HEAD) if [ "$COMMIT_COUNT" -gt 0 ]; then echo "✅ $COMMIT_COUNT updates committed locally." echo "has_updates=true" >> $GITHUB_OUTPUT echo "updated_packages=${UPDATED_PACKAGES}" >> $GITHUB_OUTPUT else echo "â„šī¸ No updates found." echo "has_updates=false" >> $GITHUB_OUTPUT fi - name: Verify Builds if: steps.update.outputs.has_updates == 'true' || steps.update-opencode.outputs.opencode_update == 'true' run: | cd "$REPO_DIR" echo "::group::Running flake check" if ! nix flake check; then echo "❌ Flake check failed" exit 1 fi echo "✅ Flake check passed" echo "::endgroup::" IFS=', ' read -ra PKGS <<< "${{ steps.update.outputs.updated_packages }}" FAILED_PACKAGES=() SUCCESSFUL_PACKAGES=() for pkg in "${PKGS[@]}"; do echo "::group::Building $pkg" if nix build .#$pkg 2>&1 | tee /tmp/build-${pkg}.log; then echo "✅ Build successful for $pkg" SUCCESSFUL_PACKAGES+=("$pkg") else echo "❌ Build failed for $pkg" FAILED_PACKAGES+=("$pkg") fi echo "::endgroup::" done if [ ${#FAILED_PACKAGES[@]} -gt 0 ]; then echo "" echo "❌ Failed packages: ${FAILED_PACKAGES[*]}" echo "✅ Successful packages: ${SUCCESSFUL_PACKAGES[*]}" echo "" # Upload logs as artifacts for debugging echo "## Build Failure Logs" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY for pkg in "${FAILED_PACKAGES[@]}"; do echo "### $pkg" >> $GITHUB_STEP_SUMMARY echo '```bash' >> $GITHUB_STEP_SUMMARY cat /tmp/build-${pkg}.log >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY done exit 1 fi echo "" echo "✅ All packages built successfully: ${SUCCESSFUL_PACKAGES[*]}" - name: Push Changes if: steps.update.outputs.has_updates == 'true' || steps.update-opencode.outputs.opencode_update == 'true' run: | cd "$REPO_DIR" PACKAGES="${{ steps.update.outputs.updated_packages }}" # Add opencode to packages list if it was updated if [ "${{ steps.update-opencode.outputs.opencode_update }}" = "true" ]; then if [ -n "$PACKAGES" ]; then PACKAGES="$PACKAGES, opencode" else PACKAGES="opencode" fi fi echo "::group::Git Operations" echo "Current commit: $(git rev-parse HEAD)" echo "Pending commits: $(git rev-list --count origin/master..HEAD)" echo "" echo "Pulling latest changes (rebase)..." if git pull --rebase origin master; then echo "✅ Rebase successful" else echo "âš ī¸ Rebase failed, attempting force push..." git reset --hard origin/master git push --force-with-lease origin master echo "✓ Force push completed" exit 0 fi echo "" echo "Pushing changes to master..." git push origin master echo "" echo "✅ Successfully pushed updates for: $PACKAGES" echo "::endgroup::" - name: Upload Build Logs if: failure() uses: actions/upload-artifact@v4 with: name: build-logs-${{ github.run_number }} path: | /tmp/update-*.log /tmp/build-*.log /tmp/opencode-build.log retention-days: 7 - name: Cleanup if: always() run: | # Remove git credentials securely rm -f ~/.git-credentials git config --global --unset credential.helper 2>/dev/null || true # Remove temporary directory rm -rf "$REPO_DIR" # Remove all log files rm -f /tmp/update-*.log /tmp/build-*.log /tmp/opencode-build.log /tmp/update-log.txt /tmp/success-packages.txt # Clear sensitive environment variables unset GIT_AUTHOR_EMAIL GIT_COMMITTER_EMAIL - name: Summary if: always() run: | HAS_UPDATES="false" if [ "${{ steps.update.outputs.has_updates }}" = "true" ]; then HAS_UPDATES="true" echo "# ✅ Update Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "## Updated Packages" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "\`${{ steps.update.outputs.updated_packages }}\`" >> $GITHUB_STEP_SUMMARY fi if [ "${{ steps.update-opencode.outputs.opencode_update }}" = "true" ]; then HAS_UPDATES="true" echo "" >> $GITHUB_STEP_SUMMARY echo "## Updated Flake Input" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- **opencode**: \`v${{ steps.update-opencode.outputs.opencode_version }}\`" >> $GITHUB_STEP_SUMMARY fi if [ "$HAS_UPDATES" = "true" ]; then echo "" >> $GITHUB_STEP_SUMMARY echo "## Status" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- ✅ All updates validated with \`nix flake check\`" >> $GITHUB_STEP_SUMMARY echo "- ✅ All builds successful" >> $GITHUB_STEP_SUMMARY echo "- ✅ Changes pushed to master" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "## Workflow Performance" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- Started: ${{ github.event.head_commit.timestamp }}" >> $GITHUB_STEP_SUMMARY echo "- Completed: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY echo "- Workflow Run: [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY else echo "# â„šī¸ No Updates Required" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "No updates found this run. All packages and flake inputs are up to date." >> $GITHUB_STEP_SUMMARY fi