name: Patch RouterOS v7 on: workflow_call: inputs: version: required: true type: string channels: required: true type: string archs: required: true type: string release: required: true type: boolean buildtime: required: false type: string default: '' env: CUSTOM_LICENSE_PRIVATE_KEY: ${{ secrets.CUSTOM_LICENSE_PRIVATE_KEY }} CUSTOM_LICENSE_PUBLIC_KEY: ${{ secrets.CUSTOM_LICENSE_PUBLIC_KEY }} CUSTOM_NPK_SIGN_PRIVATE_KEY: ${{ secrets.CUSTOM_NPK_SIGN_PRIVATE_KEY }} CUSTOM_NPK_SIGN_PUBLIC_KEY: ${{ secrets.CUSTOM_NPK_SIGN_PUBLIC_KEY }} CUSTOM_CLOUD_PUBLIC_KEY: ${{ secrets.CUSTOM_CLOUD_PUBLIC_KEY }} MIKRO_LICENSE_PUBLIC_KEY: ${{ secrets.MIKRO_LICENSE_PUBLIC_KEY }} MIKRO_NPK_SIGN_PUBLIC_KEY: ${{ secrets.MIKRO_NPK_SIGN_PUBLIC_KEY }} MIKRO_CLOUD_PUBLIC_KEY: ${{ secrets.MIKRO_CLOUD_PUBLIC_KEY }} MIKRO_LICENCE_URL: ${{ secrets.MIKRO_LICENCE_URL }} CUSTOM_LICENCE_URL: ${{ secrets.CUSTOM_LICENCE_URL }} MIKRO_UPGRADE_URL: ${{ secrets.MIKRO_UPGRADE_URL }} CUSTOM_UPGRADE_URL: ${{ secrets.CUSTOM_UPGRADE_URL }} MIKRO_RENEW_URL: ${{ secrets.MIKRO_RENEW_URL }} CUSTOM_RENEW_URL: ${{ secrets.CUSTOM_RENEW_URL }} MIKRO_CLOUD_URL: ${{ secrets.MIKRO_CLOUD_URL }} CUSTOM_CLOUD_URL: ${{ secrets.CUSTOM_CLOUD_URL }} MIKRO_CLOUD2_URL: ${{ secrets.MIKRO_CLOUD2_URL }} CUSTOM_CLOUD2_URL: ${{ secrets.CUSTOM_CLOUD2_URL }} jobs: Patch_RouterOS: runs-on: ubuntu-24.04 strategy: matrix: arch: ${{ fromJson( inputs.archs ) }} fail-fast: false env: TZ: 'Asia/Shanghai' CHANNELS: ${{ inputs.channels }} ARCH: ${{ matrix.arch }} VERSION: ${{ inputs.version }} BUILD_TIME: ${{ inputs.buildtime }} RELEASE: ${{ inputs.release }} BUILD_DIR: "/tmp/build/${{ inputs.version }}" PUBLISH_DIR: "${{ github.workspace }}/publish/${{ inputs.version }}" ARCH_EXT: "" PACKAGES: "" steps: - uses: actions/checkout@v6 - name: Setup environment run: | sudo apt-get update > /dev/null sudo apt-get install -y zip unzip squashfs-tools jq > /dev/null sudo apt-get install -y python3-crcmod python3-pefile python3-pyelftools > /dev/null if [ "$ARCH" == "x86" ]; then ARCH_EXT="" else ARCH_EXT="-$ARCH" fi echo "ARCH_EXT=$ARCH_EXT" >> $GITHUB_ENV echo "ARCH_EXT: $ARCH_EXT" rm -rf "$BUILD_DIR" mkdir -p "$BUILD_DIR" echo "BUILD_DIR: $BUILD_DIR" rm -rf "$PUBLISH_DIR" mkdir -p "$PUBLISH_DIR" echo "PUBLISH_DIR: $PUBLISH_DIR" wget -nv -O $PUBLISH_DIR/CHANGELOG "https://${{ env.MIKRO_UPGRADE_URL }}/routeros/$VERSION/CHANGELOG" cat $PUBLISH_DIR/CHANGELOG wget -nv -O $BUILD_DIR/packages.csv "https://${{ env.MIKRO_UPGRADE_URL }}/routeros/$VERSION/packages.csv" PACKAGES=$(grep "^$ARCH," $BUILD_DIR/packages.csv | cut -d',' -f2 | tr '\n' ' ') rm $BUILD_DIR/packages.csv echo "PACKAGES=$PACKAGES" >> $GITHUB_ENV echo "PACKAGES: $PACKAGES" - name: Build Squashfs for Option and Python Package run: | echo "Creating Option Package Squashfs for arch: $ARCH" mkdir -p $BUILD_DIR/option-root/bin/ install -m 755 ./busybox/busybox_$ARCH $BUILD_DIR/option-root/bin/busybox install -m 755 ./keygen/keygen_$ARCH $BUILD_DIR/option-root/bin/keygen chmod +x ./busybox/busybox_x86 COMMANDS=$(./busybox/busybox_x86 --list) for cmd in $COMMANDS; do ln -sf /pckg/option/bin/busybox $BUILD_DIR/option-root/bin/$cmd done mkdir -p $BUILD_DIR/option-root/home/web/webfig/ BUSYBOX=$(./busybox/busybox_x86 | head -1 | cut -d' ' -f2) tee $BUILD_DIR/option.jg > /dev/null << EOF [ { name:'Patch About', title:'Patch About', icon: 30, prio: 2000, c:[ { title:'Patch About', type:'item', path:[24, 2], autorefresh: 1000, ro: 1, c: [ {type:'gridcell'}, {name:'Patch',type:'tab',inline: 1}, {name:'Author',type:'string',def:'elseif@live.cn'}, {name:'GitHub',type:'string',def:'https://github.com/elseif/MikroTikPatch', url:1}, {name:'Telegram',type:'string',def:'https://telegram.me/mikrotikpatch', url:1}, {name:'Homepage',type:'string',def:'https://mikrotik.ltd', url:1}, {name:'Busybox',type:'string',def:'$BUSYBOX'}, {name:'System',type:'tab',inline: 1}, {name:'Uptime',type:'interval',id:'u1'}, {name:'Architecture Name',type:'string',id:'s1a'}, {name:'Board Name',type:'string',id:'s2c'}, {name:'Version',type:'version',id:'s16'}, {name:'Build Time',type:'string',id:'s27'}, {type:'separator'}, {type:'toolbar', c: [ {name:'License',type:'contextmenu',autostart: 1,group:'System',link: [],open:'License',tab:'License'}, {name:'Check For Updates',type:'contextmenu',autostart: 1,group:'System',link: [],open:'Packages',tab:'Check For Updates'} ] } ] } ] } ] EOF HASH=$(python3 -E < /dev/null << EOF { crc: $CRC, size: $SIZE, name: "option.jg", unique: "option-$SHA1_PREFIX.jg",version: "$VERSION" }, EOF ln -sf option-$SHA1_PREFIX.jg.gz $BUILD_DIR/option-root/home/web/webfig/option.jg.gz mksquashfs $BUILD_DIR/option-root $BUILD_DIR/option.sfs -no-recovery -noappend -exit-on-error -quiet -comp xz -no-xattrs -b 256k -all-root rm -rf $BUILD_DIR/option-root echo "✅ Option Package Squashfs created: $BUILD_DIR/option.sfs" ls -la "$BUILD_DIR/option.sfs" if [ "$ARCH" == "x86" ]; then wget -nv -O $BUILD_DIR/cpython.tar.gz -nv https://github.com/astral-sh/python-build-standalone/releases/download/20241206/cpython-3.11.11+20241206-x86_64-unknown-linux-musl-install_only_stripped.tar.gz elif [ "$ARCH" == "arm64" ]; then wget -nv -O $BUILD_DIR/cpython.tar.gz -nv https://github.com/indygreg/python-build-standalone/releases/download/20241206/cpython-3.11.11+20241206-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz fi tar -xzf $BUILD_DIR/cpython.tar.gz -C $BUILD_DIR rm $BUILD_DIR/cpython.tar.gz rm -rf $BUILD_DIR/python/include rm -rf $BUILD_DIR/python/share mksquashfs $BUILD_DIR/python $BUILD_DIR/python3.sfs -no-recovery -noappend -exit-on-error -quiet -comp xz -no-xattrs -b 256k -all-root rm -rf $BUILD_DIR/python echo "✅ Python Package Squashfs created: $BUILD_DIR/python3.sfs" ls -la "$BUILD_DIR/python3.sfs" - name: Cache mikrotik-${{ env.VERSION }}${{ env.ARCH_EXT }}.iso id: cache-mikrotik-iso uses: actions/cache@v5 with: path: ${{ env.BUILD_DIR }}/mikrotik-${{ env.VERSION }}${{ env.ARCH_EXT }}.iso key: mikrotik-${{ env.VERSION }}${{ env.ARCH_EXT }}.iso - name: Get mikrotik-${{ env.VERSION }}${{ env.ARCH_EXT }}.iso if: steps.cache-mikrotik-iso.outputs.cache-hit != 'true' run: | wget -nv -O $BUILD_DIR/mikrotik-$VERSION$ARCH_EXT.iso \ https://download.mikrotik.com/routeros/$VERSION/mikrotik-$VERSION$ARCH_EXT.iso - name: Unpack ISO run: | mkdir -p $BUILD_DIR/all_packages mkdir -p $BUILD_DIR/iso mkdir -p $BUILD_DIR/new_iso sudo mount -o loop,ro $BUILD_DIR/mikrotik-$VERSION$ARCH_EXT.iso $BUILD_DIR/iso cp -v $BUILD_DIR/iso/*.npk $BUILD_DIR/all_packages/ mv $BUILD_DIR/all_packages/routeros-$VERSION$ARCH_EXT.npk $BUILD_DIR/ cp -v $BUILD_DIR/iso/efiboot.img $BUILD_DIR/new_iso/ cp -v $BUILD_DIR/iso/defpacks $BUILD_DIR/new_iso/ sudo umount $BUILD_DIR/iso rm -rf $BUILD_DIR/iso - name: Check and download missing packages run: | for pkg in $PACKAGES; do PKG_FILE="${pkg}-$VERSION$ARCH_EXT.npk" PKG_PATH="$BUILD_DIR/all_packages/$PKG_FILE" if [ ! -f "$PKG_PATH" ]; then wget -nv -O "$PKG_PATH" "https://download.mikrotik.com/routeros/$VERSION/$PKG_FILE" echo "✅ Package downloaded: $PKG_FILE" fi done - name: Get loader patch files run: | wget -nv -O loader.7z https://${{ env.CUSTOM_UPGRADE_URL }}/routeros/loader.7z sudo apt-get install -y p7zip-full > /dev/null 7z x -p"${{ secrets.LOADER_7Z_PASSWORD }}" loader.7z >> /dev/null rm loader.7z - name: Patch NPK files run: | sudo apt-get install build-essential binutils-arm-linux-gnueabi -y > /dev/null chmod +w $BUILD_DIR/all_packages/*.npk NPK_FILES=$(find $BUILD_DIR/all_packages/*.npk) for file in $NPK_FILES; do python3 npk.py sign $file $file done python3 npk.py create $BUILD_DIR/all_packages/gps-$VERSION$ARCH_EXT.npk $BUILD_DIR/all_packages/option-$VERSION$ARCH_EXT.npk option $BUILD_DIR/option.sfs -desc="busybox" rm -rf $BUILD_DIR/option.sfs python3 npk.py create $BUILD_DIR/all_packages/gps-$VERSION$ARCH_EXT.npk $BUILD_DIR/all_packages/python3-$VERSION$ARCH_EXT.npk python3 $BUILD_DIR/python3.sfs -desc="python 3.11.13" rm -rf $BUILD_DIR/python3.sfs echo "✅ all package npk files already signed: " ls -la $BUILD_DIR/all_packages/*.npk (cd $BUILD_DIR/all_packages && zip -r $PUBLISH_DIR/all_packages-$ARCH-$VERSION.zip .) mv $BUILD_DIR/all_packages/*.npk $PUBLISH_DIR/ python3 patch.py npk $BUILD_DIR/routeros-$VERSION$ARCH_EXT.npk -O $PUBLISH_DIR/routeros-$VERSION$ARCH_EXT.npk echo "✅ Main package npk file already patched: " ls -la $PUBLISH_DIR/routeros-$VERSION$ARCH_EXT.npk rm -rf $BUILD_DIR/all_packages - name: Cache NetInstall ${{ env.VERSION }} if: matrix.arch == 'x86' && env.RELEASE == 'true' id: cache-netinstall uses: actions/cache@v5 with: path: ${{ env.BUILD_DIR }}/netinstall-${{ env.VERSION }}.zip key: netinstall-${{ env.VERSION }}.zip - name: Get netinstall ${{ env.VERSION }} if: | steps.cache-netinstall.outputs.cache-hit != 'true' && matrix.arch == 'x86' && env.RELEASE == 'true' run: | wget -nv -O $BUILD_DIR/netinstall-$VERSION.zip https://download.mikrotik.com/routeros/$VERSION/netinstall-$VERSION.zip - name: Patch netinstall ${{ env.VERSION }} if: matrix.arch == 'x86' && env.RELEASE == 'true' run: | unzip $BUILD_DIR/netinstall-$VERSION.zip -d $BUILD_DIR/ python3 patch.py netinstall $BUILD_DIR/netinstall.exe (cd $BUILD_DIR && zip -r $PUBLISH_DIR/netinstall-$VERSION.zip netinstall.exe) rm $BUILD_DIR/netinstall.exe - name: Cache refind if: matrix.arch == 'x86' id: cache-refind uses: actions/cache@v5 with: path: ${{ env.BUILD_DIR }}/refind-bin-0.14.2.zip key: refind-bin-0.14.2.zip - name: Get refind if: matrix.arch == 'x86' && steps.cache-refind.outputs.cache-hit != 'true' run: wget -nv -O $BUILD_DIR/refind-bin-0.14.2.zip https://downloads.sourceforge.net/project/refind/0.14.2/refind-bin-0.14.2.zip - name: Build Alpine LiveCD if: matrix.arch == 'x86' run: | set -e YAML_URL="https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/x86_64/latest-releases.yaml" ROOTFS_FILE=$(curl -sL "$YAML_URL" | grep -m1 'alpine-minirootfs.*\.tar\.gz' | awk '{print $NF}' | tr -d '"') echo "$ROOTFS_FILE" ROOTFS_URL="https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/x86_64/$ROOTFS_FILE" ROOTFS_DIR=$BUILD_DIR/rootfs mkdir -p $ROOTFS_DIR curl -# -L "$ROOTFS_URL" | tar -xzC $ROOTFS_DIR sudo mount -v --bind /dev $ROOTFS_DIR/dev sudo mount -vt sysfs sysfs $ROOTFS_DIR/sys sudo mount -vt proc proc $ROOTFS_DIR/proc cat << 'EOF' > $ROOTFS_DIR/etc/network/interfaces auto lo iface lo inet loopback auto eth0 iface eth0 inet dhcp EOF cat << 'EOF' > $ROOTFS_DIR/etc/inittab # /etc/inittab ::sysinit:/sbin/openrc sysinit ::sysinit:/sbin/openrc boot ::wait:/sbin/openrc default tty1::respawn:/sbin/agetty --autologin root tty1 linux tty2::respawn:/sbin/agetty tty2 linux tty3::respawn:/sbin/agetty tty3 linux tty4::respawn:/sbin/agetty tty4 linux ttyS0::respawn:/sbin/agetty ttyS0 linux ::ctrlaltdel:/sbin/reboot ::shutdown:/sbin/openrc shutdown EOF install -m 755 ./setup-routeros.sh $ROOTFS_DIR/usr/local/bin/setup-routeros install -m 755 ./npkextract/npkextract $ROOTFS_DIR/usr/local/bin/npkextract cat << 'EOF' > $ROOTFS_DIR/root/.zprofile if command -v setup-routeros >/dev/null 2>&1; then setup-routeros fi EOF cat << 'EOF' | sudo chroot $ROOTFS_DIR /usr/bin/env PATH=/usr/bin:/bin:/usr/sbin:/sbin /bin/sh set -e echo "nameserver 1.1.1.1" > /etc/resolv.conf apk update apk upgrade apk add alpine-base openrc busybox-mdev-openrc dhcpcd curl ca-certificates linux-lts linux-firmware-none apk add bash dialog sgdisk dosfstools e2fsprogs util-linux parted apk add zsh zsh-syntax-highlighting zsh-autosuggestions coreutils ncurses iproute2 rc-update add devfs sysinit rc-update add dmesg sysinit rc-update add mdev sysinit rc-update add hwdrivers sysinit rc-update add acpid boot rc-update add hostname boot rc-update add hwclock boot rc-update add modules boot rc-update add sysctl boot rc-update add bootmisc boot rc-update add dhcpcd boot rc-update add networking default curl -o /root/.zshrc https://gitlab.com/kalilinux/packages/kali-defaults/-/raw/kali/master/etc/skel/.zshrc sed -i 's/^PROMPT_ALTERNATIVE=twoline/PROMPT_ALTERNATIVE=oneline/' /root/.zshrc sed -i 's/^NEWLINE_BEFORE_PROMPT=yes/NEWLINE_BEFORE_PROMPT=no/' /root/.zshrc sed -i 's|/usr/share/zsh-syntax-highlighting|/usr/share/zsh/plugins/zsh-syntax-highlighting|g' /root/.zshrc sed -i 's|/usr/share/zsh-autosuggestions|/usr/share/zsh/plugins/zsh-autosuggestions|g' /root/.zshrc sed -i 's|^root:\(.*\):/bin/sh$|root:\1:/bin/zsh|' /etc/passwd sed -i 's/^root:.*/root:::0:::::/' /etc/shadow printf 'Welcome to Alpine!\n\nTo start the RouterOS installation wizard, run: setup-routeros\n\n' > /etc/motd sed -i 's/^features=.*/features="ata base cdrom network scsi nvme ext2 usb virtio"/' /etc/mkinitfs/mkinitfs.conf KERNEL_VERSION=$(ls /lib/modules | head -1) mkinitfs -k -t /tmp "$KERNEL_VERSION" rm -rf /lib/modules mv /tmp/lib/modules /lib depmod "$KERNEL_VERSION" ln -sf sbin/init /init apk cache clean EOF sudo umount -v $ROOTFS_DIR/dev sudo umount -v $ROOTFS_DIR/sys sudo umount -v $ROOTFS_DIR/proc cp $ROOTFS_DIR/boot/vmlinuz-* $BUILD_DIR/vmlinuz sudo rm -rf $ROOTFS_DIR/tmp/* sudo rm -rf $ROOTFS_DIR/boot/* sudo rm -rf $ROOTFS_DIR/var/cache/* (cd "$ROOTFS_DIR" && sudo find . -print0 | sudo cpio --owner=root:root --null -H newc -o 2>/dev/null | xz -C crc32 -v9eT0 > $BUILD_DIR/initramfs.xz) sudo rm -rf $ROOTFS_DIR size=$(stat -c %s "$BUILD_DIR/vmlinuz") printf "vmlinuz size : %d bytes (%.1f MB)\n" "$size" "$(echo "scale=1; $size/1024/1024" | bc)" size=$(stat -c %s "$BUILD_DIR/initramfs.xz") printf "initramfs size : %d bytes (%.1f MB)\n" "$size" "$(echo "scale=1; $size/1024/1024" | bc)" - name: Build ISO & INSTALL image files run: | set -e sudo modprobe nbd sudo apt-get install isolinux syslinux syslinux-common xorriso dosfstools qemu-utils -y > /dev/null cp -v $PUBLISH_DIR/routeros-$VERSION$ARCH_EXT.npk $BUILD_DIR/new_iso/ unzip $PUBLISH_DIR/all_packages-$ARCH-$VERSION.zip -d $BUILD_DIR/new_iso/ mkdir $BUILD_DIR/efiboot if [ "$ARCH" == "x86" ]; then echo "Creating x86 ISO..." mkdir -p $BUILD_DIR/new_iso/isolinux cp -v /usr/lib/ISOLINUX/isolinux.bin $BUILD_DIR/new_iso/isolinux/ cp -v /usr/lib/syslinux/modules/bios/ldlinux.c32 $BUILD_DIR/new_iso/isolinux/ cp -v /usr/lib/syslinux/modules/bios/libutil.c32 $BUILD_DIR/new_iso/isolinux/ cp -v /usr/lib/syslinux/modules/bios/menu.c32 $BUILD_DIR/new_iso/isolinux/ tee $BUILD_DIR/new_iso/isolinux/isolinux.cfg > /dev/null << 'EOF' UI menu.c32 MENU TITLE MikroTik RouterOS Install CDROM MENU CLEAR DEFAULT system TIMEOUT 0 LABEL system MENU LABEL Install RouterOS KERNEL linux APPEND load_ramdisk=1 root=/dev/ram0 -install -cdrom LABEL alpine MENU LABEL Boot Alpine Linux KERNEL vmlinuz APPEND initrd=initramfs.xz EOF cp -v $BUILD_DIR/vmlinuz $BUILD_DIR/new_iso/isolinux/vmlinuz cp -v $BUILD_DIR/initramfs.xz $BUILD_DIR/new_iso/isolinux/initramfs.xz sudo mount -o loop $BUILD_DIR/new_iso/efiboot.img $BUILD_DIR/efiboot python3 patch.py kernel $BUILD_DIR/efiboot/linux.x86_64 -O $BUILD_DIR/new_iso/isolinux/linux sudo umount $BUILD_DIR/efiboot rm -f $BUILD_DIR/new_iso/efiboot.img truncate --size 64M $BUILD_DIR/new_iso/efiboot.img mkfs.vfat -n "Boot" $BUILD_DIR/new_iso/efiboot.img sudo mount -o loop,uid=$(id -u),gid=$(id -g) $BUILD_DIR/new_iso/efiboot.img $BUILD_DIR/efiboot mkdir -p $BUILD_DIR/efiboot/EFI/BOOT unzip -j $BUILD_DIR/refind-bin-0.14.2.zip "refind-bin-0.14.2/refind/refind_x64.efi" -d $BUILD_DIR/efiboot/EFI/BOOT/ mv $BUILD_DIR/efiboot/EFI/BOOT/refind_x64.efi $BUILD_DIR/efiboot/EFI/BOOT/BOOTX64.EFI tee $BUILD_DIR/efiboot/EFI/BOOT/refind.conf > /dev/null << 'EOF' timeout 0 textonly textmode 0 showtools shutdown, reboot, exit menuentry "Install RouterOS" { loader /linux.x86_64 options "load_ramdisk=1 root=/dev/ram0 -install -cdrom debug" } menuentry "Alpine Linux" { loader /vmlinuz initrd /initramfs.xz } default_selection /EFI/BOOT/BOOTX64.EFI EOF cp -v $BUILD_DIR/new_iso/isolinux/linux $BUILD_DIR/efiboot/linux.x86_64 cp -v $BUILD_DIR/vmlinuz $BUILD_DIR/efiboot/vmlinuz cp -v $BUILD_DIR/initramfs.xz $BUILD_DIR/efiboot/initramfs.xz sync sudo umount $BUILD_DIR/efiboot xorriso -as mkisofs -o $PUBLISH_DIR/mikrotik-$VERSION$ARCH_EXT.iso \ -V 'MikroTik' \ -publisher "WWW.MIKROTIK.COM" \ -preparer "MIKROTIK" \ -A "MIKROTIK_ROUTER" \ -sysid "" \ -b 'isolinux/isolinux.bin' \ -c 'isolinux/boot.cat' \ -no-emul-boot \ -boot-load-size 4 \ -eltorito-alt-boot \ -e efiboot.img \ -no-emul-boot \ -boot-load-size 2048 \ -R -J \ -input-charset utf-8 \ -iso-level 3 \ $BUILD_DIR/new_iso echo "✅ ISO created: " ls -la $PUBLISH_DIR/mikrotik-$VERSION$ARCH_EXT.iso echo "Creating x86 install..." truncate --size 128M $BUILD_DIR/install-image-$VERSION.img mkfs.vfat -I -F 32 -n "Install" $BUILD_DIR/install-image-$VERSION.img LOOP=$(sudo losetup -f --show $BUILD_DIR/install-image-$VERSION.img) sudo syslinux --install $LOOP mkdir -p $BUILD_DIR/install sudo mount -o loop,uid=$(id -u),gid=$(id -g),umask=000 $LOOP $BUILD_DIR/install cp -vf $BUILD_DIR/new_iso/isolinux/linux $BUILD_DIR/install/ cp -vf /usr/lib/syslinux/modules/bios/ldlinux.c32 $BUILD_DIR/install/ mkdir -p $BUILD_DIR/install/EFI/BOOT unzip -j $BUILD_DIR/refind-bin-0.14.2.zip "refind-bin-0.14.2/refind/refind_x64.efi" -d $BUILD_DIR/install/EFI/BOOT mv $BUILD_DIR/install/EFI/BOOT/refind_x64.efi $BUILD_DIR/install/EFI/BOOT/BOOTX64.EFI tee $BUILD_DIR/install/EFI/BOOT/refind.conf > /dev/null << 'EOF' timeout 0 textonly textmode 0 showtools shutdown, reboot, exit menuentry "Install RouterOS" { loader /linux options "load_ramdisk=1 root=/dev/ram0 -install -hdd" } default_selection /EFI/BOOT/BOOTX64.EFI EOF tee $BUILD_DIR/install/syslinux.cfg > /dev/null << 'EOF' default system label system kernel linux append load_ramdisk=1 -install -hdd EOF NPK_FILES=($(find $BUILD_DIR/new_iso/*.npk)) for ((i=1; i<=${#NPK_FILES[@]}; i++)) do echo "${NPK_FILES[$i-1]}=>$i.npk" cp ${NPK_FILES[$i-1]} $BUILD_DIR/install/$i.npk done touch $BUILD_DIR/install/CHOOSE touch $BUILD_DIR/install/autorun.scr sync sudo umount $BUILD_DIR/install sudo losetup -d $LOOP rm -rf $BUILD_DIR/install echo "✅ install imgage created: " ls -la $BUILD_DIR/install-image-$VERSION.img (cd $BUILD_DIR && zip $PUBLISH_DIR/install-image-$VERSION.zip install-image-$VERSION.img) rm $BUILD_DIR/install-image-$VERSION.img elif [ "$ARCH" == "arm64" ]; then echo "Creating arm64 ISO..." sudo mount -o loop $BUILD_DIR/new_iso/efiboot.img $BUILD_DIR/efiboot python3 patch.py kernel $BUILD_DIR/efiboot/EFI/BOOT/BOOTAA64.EFI -O $BUILD_DIR/BOOTAA64.EFI sudo umount $BUILD_DIR/efiboot #修改为16M,减小镜像体积 rm -f $BUILD_DIR/new_iso/efiboot.img truncate --size 16M $BUILD_DIR/new_iso/efiboot.img mkfs.vfat -n "Boot" $BUILD_DIR/new_iso/efiboot.img sudo mount -o loop,uid=$(id -u),gid=$(id -g) $BUILD_DIR/new_iso/efiboot.img $BUILD_DIR/efiboot mkdir -p $BUILD_DIR/efiboot/EFI/BOOT mv $BUILD_DIR/BOOTAA64.EFI $BUILD_DIR/efiboot/EFI/BOOT/BOOTAA64.EFI sync sudo umount $BUILD_DIR/efiboot xorriso -as mkisofs -o $PUBLISH_DIR/mikrotik-$VERSION$ARCH_EXT.iso \ -V 'MikroTik' \ -publisher "WWW.MIKROTIK.COM" \ -preparer "MIKROTIK" \ -A "MIKROTIK_ROUTER" \ -sysid "" \ -e '/efiboot.img' \ -no-emul-boot \ -boot-load-size 2048 \ -R -J \ -input-charset utf-8 \ -iso-level 3 \ $BUILD_DIR/new_iso echo "✅ arm64 ISO created: " ls -la $PUBLISH_DIR/mikrotik-$VERSION$ARCH_EXT.iso fi rm -rf $BUILD_DIR/new_iso rm -rf $BUILD_DIR/efiboot - name: Build CHR image files run: | set -e OVF_TEMPLATE=' Virtual disk information The list of logical networks The VM Network network A virtual machine MikroTik_RouterOS_CHR The kind of installed guest operating system Virtual hardware requirements Virtual Hardware Family 0 MikroTik_RouterOS_CHR vmx-10 hertz * 10^6 Number of Virtual CPUs 1 virtual CPU(s) 1 3 1 byte * 2^20 Memory Size 1024MB of memory 2 4 1024 0 IDE Controller ideController0 3 5 0 disk0 ovf:/disk/vmdisk1 4 3 17 1 true VM Network VmxNet3 ethernet adapter on "VM Network" ethernet0 5 VmxNet3 10 false video 6 24 false vmci 7 vmware.vmci 1 A human-readable annotation MikroTik RouterOS CHR ' sudo apt-get install -y parted > /dev/null mkdir -p $BUILD_DIR/chr/{boot,routeros} prepare_image(){ local IMAGE="$1" local BOOT_MODE="$2" truncate --size 128M "$IMAGE" local END=$(( ($(stat -c '%s' "$IMAGE") / 512 - 4096) / 8 * 8 - 1 )) sgdisk --set-alignment=1 \ --new=1:34:65535 --typecode=1:EF00 --change-name=1:"RouterOS Boot" \ --new=2:65536:$END --typecode=2:8300 --change-name=2:"RouterOS" \ --hybrid=1:2:EE \ "$IMAGE" # Active CHR mode printf '\x01' | dd of="$IMAGE" bs=1 seek=336 conv=notrunc status=none # Active boot partition printf '\x80' | dd of="$IMAGE" bs=1 seek=446 conv=notrunc status=none # Must be 0x83, otherwise "ERROR: could not find disk!" printf '\x83' | dd of="$IMAGE" bs=1 seek=450 conv=notrunc status=none if [[ "$BOOT_MODE" == "bios" ]]; then echo "FA31C08ED0BC007C89E65007501FFBFCBF0006B90001F2A5EA1D060000BEBE07B304803C807423803C00750983C610FECB75EFCD18BE9B06AC3C00740B56BB0700B40ECD105EEBF0EBFE8B148B4C0289F5BF0500BB007CB8010257CD135F730C31C0CD134F75EDBE7C06EBCCBFFE7D813D55AA75C289EEEA007C00004572726F72206C6F6164696E67206F7065726174696E672073797374656D004D697373696E67206F7065726174696E672073797374656D0000000000" | xxd -r -p | sudo dd of="$IMAGE" bs=1 conv=notrunc status=none fi } build_image() { local IMAGE="$1" local BOOT_MODE="$2" prepare_image $IMAGE $BOOT_MODE sudo qemu-nbd -d /dev/nbd0 2>/dev/null || true sudo qemu-nbd -c /dev/nbd0 -f raw "$IMAGE" sudo partprobe /dev/nbd0 # Boot partition if [[ "$BOOT_MODE" == "bios" ]]; then sudo mkfs.ext2 -F -m 0 -L "RouterOS Boot" /dev/nbd0p1 elif [[ "$BOOT_MODE" == "efi" ]]; then sudo mkfs.vfat -F 16 -n "BOOT" /dev/nbd0p1 else echo "ERROR: unsupported boot mode: $BOOT_MODE" >&2 return 1 fi sudo mount /dev/nbd0p1 "$BUILD_DIR/chr/boot" sudo mkdir -p "$BUILD_DIR/chr/boot/EFI/BOOT" if [ "$ARCH" == "x86" ]; then sudo cp -vf "$BUILD_DIR/chr/BOOTX64.EFI" "$BUILD_DIR/chr/boot/EFI/BOOT/BOOTX64.EFI" elif [ "$ARCH" == "arm64" ]; then sudo cp -vf $BUILD_DIR/chr/BOOTAA64.EFI $BUILD_DIR/chr/boot/EFI/BOOT/BOOTAA64.EFI fi if [[ "$BOOT_MODE" == "bios" ]]; then sudo "$BUILD_DIR/chr/milo" "$BUILD_DIR/chr/boot" fi sudo umount "$BUILD_DIR/chr/boot" # RouterOS partition sudo mkfs.ext4 -F -m 0 -L "RouterOS" /dev/nbd0p2 sudo mount /dev/nbd0p2 "$BUILD_DIR/chr/routeros" sudo mkdir -p $BUILD_DIR/chr/routeros/{var/pdb/{system,option},boot,bin,rw/disk} sudo install -m 0755 "$BUILD_DIR/chr/milo" "$BUILD_DIR/chr/routeros/bin/milo" sudo install -m 0755 "$BUILD_DIR/chr/bash" "$BUILD_DIR/chr/routeros/bin/bash" sudo cp -v $PUBLISH_DIR/option-$VERSION$ARCH_EXT.npk $BUILD_DIR/chr/routeros/var/pdb/option/image sudo cp -v $PUBLISH_DIR/routeros-$VERSION$ARCH_EXT.npk $BUILD_DIR/chr/routeros/var/pdb/system/image printf '%s\n' \ '#!/bin/sh' \ '# This script will be executed *before* RouterOS *loader* start.' \ '# You can put your own initialization stuff in here' | sudo tee "$BUILD_DIR/chr/routeros/rw/disk/rc.local" >/dev/null sudo umount /dev/nbd0p2 sudo qemu-nbd -d /dev/nbd0 echo "Image built: $IMAGE" local IMAGE_PREFIX="${IMAGE%.img}" qemu-img convert -f raw -O qcow2 $IMAGE $IMAGE_PREFIX.qcow2 qemu-img convert -f raw -O vmdk $IMAGE $IMAGE_PREFIX.vmdk qemu-img convert -f raw -O vpc $IMAGE $IMAGE_PREFIX.vhd qemu-img convert -f raw -O vhdx $IMAGE $IMAGE_PREFIX.vhdx qemu-img convert -f raw -O vdi $IMAGE $IMAGE_PREFIX.vdi local IMAGE_NAME="${IMAGE_PREFIX##*/}" local VMDK_FILE="$IMAGE_NAME-disk1.vmdk" local OVF_FILE="$IMAGE_NAME.ovf" local OVA_FILE="$IMAGE_NAME.ova" qemu-img convert -f raw -O vmdk -o subformat=streamOptimized "$IMAGE" "$BUILD_DIR/$VMDK_FILE" local FILE_SIZE=$(stat -c %s "$BUILD_DIR/$VMDK_FILE") echo "$OVF_TEMPLATE" | sed -e "s|{VMDK_FILE}|$VMDK_FILE|g" \ -e "s|{FILE_SIZE}|$FILE_SIZE|g" \ -e "s|{FIRMWARE_TYPE}|$BOOT_MODE|g" \ > "$BUILD_DIR/$OVF_FILE" (cd $BUILD_DIR && tar -cvf "$OVA_FILE" "$OVF_FILE" "$VMDK_FILE") rm -f "$BUILD_DIR/$VMDK_FILE" rm -f "$BUILD_DIR/$OVF_FILE" echo "✅ Generated images:" ls -lh "$IMAGE_PREFIX".* (cd $BUILD_DIR && \ zip $PUBLISH_DIR/$IMAGE_NAME.ova.zip $IMAGE_NAME.ova && \ zip $PUBLISH_DIR/$IMAGE_NAME.qcow2.zip $IMAGE_NAME.qcow2 && \ zip $PUBLISH_DIR/$IMAGE_NAME.vmdk.zip $IMAGE_NAME.vmdk && \ zip $PUBLISH_DIR/$IMAGE_NAME.vhd.zip $IMAGE_NAME.vhd && \ zip $PUBLISH_DIR/$IMAGE_NAME.vhdx.zip $IMAGE_NAME.vhdx && \ zip $PUBLISH_DIR/$IMAGE_NAME.vdi.zip $IMAGE_NAME.vdi && \ zip $PUBLISH_DIR/$IMAGE_NAME.img.zip $IMAGE_NAME.img ) rm -f "$IMAGE_PREFIX".* } if [ "$ARCH" == "x86" ]; then echo "🚀 Creating CHR image for x86..." python3 npk.py extract $PUBLISH_DIR/routeros-$VERSION$ARCH_EXT.npk boot/EFI/BOOT/BOOTX64.EFI $BUILD_DIR/chr/BOOTX64.EFI python3 npk.py extract $PUBLISH_DIR/routeros-$VERSION$ARCH_EXT.npk bin/milo $BUILD_DIR/chr/milo python3 npk.py extract $PUBLISH_DIR/routeros-$VERSION$ARCH_EXT.npk bin/bash $BUILD_DIR/chr/bash chmod +x $BUILD_DIR/chr/milo echo "🔄 Creating CHR image for x86 UEFI ..." if ! build_image "$BUILD_DIR/chr-$VERSION$ARCH_EXT.img" efi; then echo "❌ failed to build x86 UEFI image" exit 1 fi echo "Creating CHR image for x86 legacy BIOS ..." if ! build_image "$BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img" bios; then echo "❌ failed to build x86 legacy BIOS image" exit 1 fi elif [ "$ARCH" == "arm64" ]; then echo "🚀 Creating CHR image for arm64..." python3 npk.py extract $PUBLISH_DIR/routeros-$VERSION$ARCH_EXT.npk boot/kernel $BUILD_DIR/chr/kernel python3 patch.py buildefi $BUILD_DIR/chr/kernel $BUILD_DIR/chr/BOOTAA64.EFI rm -r $BUILD_DIR/chr/kernel echo "🔄Creating CHR image for arm64 UEFI ..." if ! build_image "$BUILD_DIR/chr-$VERSION$ARCH_EXT.img" efi; then echo "❌ failed to build arm64 UEFI image" exit 1 fi fi rm -rf $BUILD_DIR/chr - name: Upload Files as Artifact (No Release) if: env.RELEASE == 'false' uses: actions/upload-artifact@v7 with: name: mikrotik-${{ env.VERSION }}${{ env.ARCH_EXT }} path: | ${{ env.PUBLISH_DIR }}/*.zip ${{ env.PUBLISH_DIR }}/*.iso - name: Generate SHA256 checksum files if: env.RELEASE == 'true' run: | ( cd $PUBLISH_DIR for file in *.zip *.iso *.npk; do if [ -f "$file" ]; then sha256sum "$file" > "$file.sha256" cat "$file.sha256" fi done ) - name: Delete Release tag ${{ env.VERSION }}${{ env.ARCH_EXT }} if: env.RELEASE == 'true' run: | HEADER="Authorization: token ${{ secrets.GITHUB_TOKEN }}" RELEASE_INFO=$(curl -s -H "$HEADER" https://api.github.com/repos/${{ github.repository }}/releases/tags/$VERSION$ARCH_EXT) RELEASE_ID=$(echo $RELEASE_INFO | jq -r '.id') echo "Release ID: $RELEASE_ID" if [ "$RELEASE_ID" != "null" ]; then curl -X DELETE -H "$HEADER" https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$VERSION$ARCH_EXT echo "Tag $VERSION$ARCH_EXT deleted successfully." curl -X DELETE -H "$HEADER" https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID echo "Release with tag $VERSION$ARCH_EXT deleted successfully." # Wait for a moment to ensure the deletion is complete sleep 3 else echo "Release not found for tag: $VERSION$ARCH_EXT)" fi - name: Generate Release Body if: env.RELEASE == 'true' run: | RELEASE_BODY="$BUILD_DIR/RELEASE_BODY.md" BASE_URL="https://github.com/${{ github.repository }}/releases/download/$VERSION$ARCH_EXT" BUILD_TIME_UTC=$(date -u -d "@$BUILD_TIME" "+%Y-%m-%d %H:%M:%S UTC" 2>/dev/null || echo "Invalid timestamp") link_file() { local file="$1" if [ -f "$file" ]; then local basename=$(basename "$file") echo "[$basename]($BASE_URL/$basename)" else echo "N/A" fi } cat > "$RELEASE_BODY" << EOF **Build Time**: $BUILD_TIME_UTC ($BUILD_TIME) **Architecture**: $ARCH **Channel**: $CHANNELS
RouterOS | Type | File | |------|------| EOF echo "| Main package | $(link_file "$PUBLISH_DIR/routeros-${VERSION}${ARCH_EXT}.npk") |" >> "$RELEASE_BODY" echo "| Extra packages | $(link_file "$PUBLISH_DIR/all_packages-$ARCH-$VERSION.zip") |" >> "$RELEASE_BODY" if [ "$ARCH" == "x86" ] || [ "$ARCH" == "arm64" ]; then echo "| ISO Image | $(link_file "$PUBLISH_DIR/mikrotik-${VERSION}${ARCH_EXT}.iso") |" >> "$RELEASE_BODY" fi if [ "$ARCH" == "x86" ]; then echo "| Install Image | $(link_file "$PUBLISH_DIR/install-image-$VERSION.zip") |" >> "$RELEASE_BODY" echo "| Netinstall | $(link_file "$PUBLISH_DIR/netinstall-$VERSION.zip") |" >> "$RELEASE_BODY" fi echo "
" >> "$RELEASE_BODY" echo "" >> "$RELEASE_BODY" if [ "$ARCH" == "x86" ] || [ "$ARCH" == "arm64" ]; then echo "
Cloud Hosted Router (CHR)" >> "$RELEASE_BODY" echo "" >> "$RELEASE_BODY" if [ "$ARCH" == "x86" ]; then echo "| Platform | UEFI | Legacy BIOS |" >> "$RELEASE_BODY" echo "|--------|------|-------------|" >> "$RELEASE_BODY" else echo "| Platform | UEFI |" >> "$RELEASE_BODY" echo "|--------|------|" >> "$RELEASE_BODY" fi formats=("img" "ova" "qcow2" "vdi" "vhd" "vhdx" "vmdk") declare -A FORMAT_PLATFORM=( ["img"]="Raw disk image" ["ova"]="Open Virtual Appliance" ["qcow2"]="QEMU/KVM" ["vdi"]="VirtualBox" ["vhd"]="Hyper-V (legacy)" ["vhdx"]="Hyper-V" ["vmdk"]="VMware" ) for fmt in "${formats[@]}"; do platform="${FORMAT_PLATFORM[$fmt]}" uefi_link=$(link_file "$PUBLISH_DIR/chr-$VERSION$ARCH_EXT.$fmt.zip") if [ "$ARCH" == "x86" ]; then legacy_link=$(link_file "$PUBLISH_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.$fmt.zip") echo "| $platform | $uefi_link | $legacy_link |" >> "$RELEASE_BODY" else echo "| $platform | $uefi_link |" >> "$RELEASE_BODY" fi done echo "
" >> "$RELEASE_BODY" echo "" >> "$RELEASE_BODY" fi echo "
Changelog" >> "$RELEASE_BODY" echo "" >> "$RELEASE_BODY" echo "
" >> "$RELEASE_BODY"
        cat "$PUBLISH_DIR/CHANGELOG" >> "$RELEASE_BODY"
        echo "
" >> "$RELEASE_BODY" echo "
" >> "$RELEASE_BODY" echo "📄 Release body content:" cat "$RELEASE_BODY" - name: Create Release tag ${{ env.VERSION }}${{ env.ARCH_EXT }} if: env.RELEASE == 'true' uses: softprops/action-gh-release@v3 with: name: "RouterOS ${{ env.VERSION }}${{ env.ARCH_EXT }}" repository: ${{ github.repository }} token: ${{ secrets.GITHUB_TOKEN }} body_path: ${{env.BUILD_DIR}}/RELEASE_BODY.md tag_name: ${{ env.VERSION }}${{ env.ARCH_EXT }} make_latest: ${{ contains(env.CHANNELS, 'stable') && matrix.arch == 'x86' }} prerelease: ${{ contains(env.CHANNELS,'development') || contains(env.CHANNELS,'testing') }} files: | ${{env.PUBLISH_DIR}}/*.zip ${{env.PUBLISH_DIR}}/*.zip.sha256 ${{env.PUBLISH_DIR}}/*.iso ${{env.PUBLISH_DIR}}/*.iso.sha256 ${{env.PUBLISH_DIR}}/routeros*.npk ${{env.PUBLISH_DIR}}/routeros*.npk.sha256 - name: Upload Files if: env.RELEASE == 'true' run: | sudo apt-get install -y knockd lftp > /dev/null knock -4 ${{ secrets.SSH_SERVER }} ${{ secrets.SSH_KNOCK_PORT }} echo "Knocking on server..." for i in 1 2 3; do if knock -4 ${{ secrets.SSH_SERVER }} ${{ secrets.SSH_KNOCK_PORT }} ; then echo "Knock successful" break else echo "Knock attempt $i failed, retrying..." sleep 2 fi done echo "Waiting for firewall to open SSH port..." sleep 3 rm -f $PUBLISH_DIR/*.zip rm -f $PUBLISH_DIR/*.iso rm -f $PUBLISH_DIR/*.zip.sha256 rm -f $PUBLISH_DIR/*.iso.sha256 echo $VERSION $BUILD_TIME | tee $PUBLISH_DIR/info echo "$CHANNELS" | jq -r '.[]' | while read -r CHANNEL do touch "$PUBLISH_DIR/$CHANNEL" done REMOTE_PATH=${{ secrets.SSH_DIRECTORY }}/$VERSION SERVER=${{ secrets.SSH_SERVER }} USER=${{ secrets.SSH_USERNAME }} PASS=${{ secrets.SSH_PASSWORD }} PORT=${{ secrets.SSH_PORT }} lftp -u "$USER","$PASS" sftp://$SERVER:$PORT < /dev/null knock -4 ${{ secrets.SSH_SERVER }} ${{ secrets.SSH_KNOCK_PORT }} echo "Knocking on server..." for i in 1 2 3; do if knock -4 ${{ secrets.SSH_SERVER }} ${{ secrets.SSH_KNOCK_PORT }} ; then echo "Knock successful" break else echo "Knock attempt $i failed, retrying..." sleep 2 fi done echo "Waiting for firewall to open SSH port..." sleep 3 REMOTE_PATH=${{ secrets.SSH_DIRECTORY }} SERVER=${{ secrets.SSH_SERVER }} USER=${{ secrets.SSH_USERNAME }} PASS=${{ secrets.SSH_PASSWORD }} PORT=${{ secrets.SSH_PORT }} REMOTE_CMD=" set -e bash /rw/disk/$REMOTE_PATH/packages.sh /rw/disk/$REMOTE_PATH/$VERSION " while read -r CHANNEL do REMOTE_CMD="$REMOTE_CMD ln -sfn /rw/disk/$REMOTE_PATH/$VERSION/info /rw/disk/$REMOTE_PATH/NEWESTa7.$CHANNEL" done < <(echo "$CHANNELS" | jq -r '.[]') REMOTE_CMD="$REMOTE_CMD chown -R 32768:32768 /rw/disk/$REMOTE_PATH " sshpass -p "$PASS" ssh -o StrictHostKeyChecking=no -p $PORT $USER@$SERVER "$REMOTE_CMD" echo "✅ Updated: packages.csv"