Files
2026-09-04 20:01:34 +08:00

274 lines
9.2 KiB
YAML

name: Patch Mikrotik RouterOS
on:
schedule:
- cron: "25 * * * *" # Every hour at minute 25 (UTC)
workflow_dispatch:
inputs:
stable:
description: 'Build stable channel'
required: false
type: boolean
default: true
long_term:
description: 'Build long-term channel'
required: false
type: boolean
default: false
development:
description: 'Build development channel'
required: false
type: boolean
default: false
testing:
description: 'Build testing channel'
required: false
type: boolean
default: false
arch:
description: 'Architectures (x86, arm64)'
required: true
default: 'all'
type: choice
options:
- all
- x86
- arm64
major_version:
description: 'RouterOS major version'
required: true
default: 'all'
type: choice
options:
- all
- v6
- v7
version:
description: '(e.g. 7.xx.x), empty for latest'
required: false
default: ''
type: string
buildtime:
description: "build imestamp, leave empty for original"
required: false
default: ''
type: string
release:
description: 'Release'
required: false
default: false
type: boolean
force:
description: 'Force patch even if already patched'
required: false
default: false
type: boolean
jobs:
Prepare_Patch:
runs-on: ubuntu-latest
outputs:
upgrades_v6: ${{ steps.prepare.outputs.upgrades_v6 }}
upgrades_v7: ${{ steps.prepare.outputs.upgrades_v7 }}
patch_v6: ${{ steps.prepare.outputs.patch_v6 }}
patch_v7: ${{ steps.prepare.outputs.patch_v7 }}
archs: ${{ steps.prepare.outputs.archs }}
steps:
- name: Prepare Patch
id: prepare
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
channels=()
[[ "${{ inputs.stable }}" == "true" ]] && channels+=("stable")
[[ "${{ inputs.long_term }}" == "true" ]] && channels+=("long-term")
[[ "${{ inputs.development }}" == "true" ]] && channels+=("development")
[[ "${{ inputs.testing }}" == "true" ]] && channels+=("testing")
if [ ${#channels[@]} -eq 0 ]; then
echo "ERROR: At least one channel must be selected"
exit 1
fi
CHANNELS=$(printf '%s\n' "${channels[@]}" | jq -Rsc 'split("\n")[:-1]')
VERSION_INPUT='${{ inputs.version }}'
MAJOR_VERSION='${{ inputs.major_version }}'
FORCE="${{ inputs.force || 'false' }}"
if [[ -n "$VERSION_INPUT" && "$MAJOR_VERSION" != "all" ]]; then
case "$MAJOR_VERSION" in
v6)
if [[ ! "$VERSION_INPUT" =~ ^6\. ]]; then
echo "ERROR: $VERSION_INPUT is not RouterOS v6"
exit 1
fi
;;
v7)
if [[ ! "$VERSION_INPUT" =~ ^7\. ]]; then
echo "ERROR: $VERSION_INPUT is not RouterOS v7"
exit 1
fi
;;
esac
fi
else
CHANNELS='["stable","long-term","development","testing"]'
VERSION_INPUT=""
MAJOR_VERSION="all"
FORCE="false"
fi
ARCH_INPUT="${{ inputs.arch || 'all' }}"
if [[ "$ARCH_INPUT" == "all" ]]; then
if [[ "$MAJOR_VERSION" == "v6" ]]; then
ARCHS='["x86"]'
else
ARCHS='["x86","arm64"]'
fi
else
if [[ "$ARCH_INPUT" == "arm64" && "$MAJOR_VERSION" == "v6" ]]; then
echo "ERROR: RouterOS v6 does not support arm64"
exit 1
fi
ARCHS=$(jq -c -n --arg arch "$ARCH_INPUT" '[$arch]')
fi
echo "Major Version: $MAJOR_VERSION"
echo "Force: $FORCE"
echo "Architectures:"
echo "$ARCHS" | jq .
echo "Channels:"
echo "$CHANNELS" | jq .
if [[ -n "$VERSION_INPUT" ]]; then
echo "Using fixed version: $VERSION_INPUT"
CANDIDATES=$(jq -cn --arg version "$VERSION_INPUT" --argjson channels "$CHANNELS" '
[{ version:$version, channels:$channels}]
')
else
echo "Resolving version from channels"
OFFICIAL_UPGRADE=$(curl -fsSL https://upgrade.mikrotik.com/routeros/upgrade.json) || { echo "ERROR: Failed to fetch OFFICIAL_UPGRADE" >&2; exit 1; }
echo "$OFFICIAL_UPGRADE" | jq -e . >/dev/null 2>&1 || { echo "ERROR: OFFICIAL_UPGRADE is not valid JSON" >&2; exit 1; }
CANDIDATES=$( echo "$OFFICIAL_UPGRADE" |
jq -c --argjson channels "$CHANNELS" --arg major "$MAJOR_VERSION" '
[
$channels[] as $c |
if $major == "v6" then
{
version: .upgradePaths["NEWEST6."+ $c].version,
channel: $c
}
elif $major == "v7" then
{
version: .upgradePaths["NEWESTa7."+ $c].version,
channel: $c
}
else
[
{
version: .upgradePaths["NEWEST6."+ $c].version,
channel: $c
},
{
version: .upgradePaths["NEWESTa7."+ $c].version,
channel: $c
}
]
end
] |
flatten |
map(select(.version != null)) |
sort_by(.version) |
group_by(.version) |
map({
version: .[0].version,
channels: map(.channel)
})
'
)
fi
echo "Candidates:"
echo "$CANDIDATES" | jq .
if [[ "$FORCE" == "true" ]]; then
echo "Force mode enabled: skip patched version check"
else
echo "Checking patched versions"
PATCH_UPGRADE=$(curl -fsSL https://upgrade.mikrotik.ltd/routeros/upgrade.json) || { echo "ERROR: Failed to fetch PATCH_UPGRADE" >&2; exit 1; }
echo "$PATCH_UPGRADE" | jq -e . >/dev/null 2>&1 || { echo "ERROR: PATCH_UPGRADE is not valid JSON" >&2; exit 1; }
PATCH_VERSIONS=$(echo "$PATCH_UPGRADE" | jq -c '
[.upgradePaths[].version] | unique
')
ALREADY_PATCHED=$(echo "$CANDIDATES" | jq -c --argjson patched "$PATCH_VERSIONS" '
map(select(
(.version as $v | ($patched | index($v))) != null
))
')
echo "Already patched:"
echo "$ALREADY_PATCHED" | jq .
CANDIDATES=$(echo "$CANDIDATES" | jq -c --argjson patched "$PATCH_VERSIONS" '
map(select(
(.version as $v | ($patched | index($v))) == null
))
')
fi
V6_MATRIX=$(echo "$CANDIDATES" | jq -c 'map(select(.version | startswith("6.")))')
V7_MATRIX=$(echo "$CANDIDATES" | jq -c 'map(select(.version | startswith("7.")))')
echo "upgrades_v6=$V6_MATRIX" >> "$GITHUB_OUTPUT"
echo "upgrades_v7=$V7_MATRIX" >> "$GITHUB_OUTPUT"
echo "archs=$ARCHS" >> "$GITHUB_OUTPUT"
echo "patch_v6=$(jq 'length > 0' <<< "$V6_MATRIX")" >> "$GITHUB_OUTPUT"
echo "patch_v7=$(jq 'length > 0' <<< "$V7_MATRIX")" >> "$GITHUB_OUTPUT"
echo "=== Upgrades V6 ==="
echo "$V6_MATRIX" | jq .
echo "=== Upgrades V7 ==="
echo "$V7_MATRIX" | jq .
echo "=== Architectures ==="
echo "$ARCHS" | jq .
Patch_V6:
needs: Prepare_Patch
if: needs.Prepare_Patch.result == 'success' && needs.Prepare_Patch.outputs.patch_v6 == 'true'
uses: ./.github/workflows/patch_v6.yml
strategy:
matrix:
target: ${{ fromJson(needs.Prepare_Patch.outputs.upgrades_v6) }}
fail-fast: false
with:
version: ${{ matrix.target.version }}
channels: ${{ toJson(matrix.target.channels) }}
archs: ${{ needs.Prepare_Patch.outputs.archs }}
buildtime: ${{ inputs.buildtime }}
release: ${{ github.event_name != 'workflow_dispatch' || inputs.release }}
secrets: inherit
Patch_V7:
needs: Prepare_Patch
if: needs.Prepare_Patch.result == 'success' && needs.Prepare_Patch.outputs.patch_v7 == 'true'
uses: ./.github/workflows/patch_v7.yml
strategy:
matrix:
target: ${{ fromJson(needs.Prepare_Patch.outputs.upgrades_v7) }}
fail-fast: false
with:
version: ${{ matrix.target.version }}
channels: ${{ toJson(matrix.target.channels) }}
archs: ${{ needs.Prepare_Patch.outputs.archs }}
buildtime: ${{ inputs.buildtime }}
release: ${{ github.event_name != 'workflow_dispatch' || inputs.release }}
secrets: inherit