Compare commits

...

61 Commits

Author SHA1 Message Date
elseif
c54b46c760 Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-26 13:42:03 +08:00
elseif
8603498d39 Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-26 13:29:16 +08:00
elseif
d3fa483c4b Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-26 13:26:27 +08:00
elseif
e338ddb7b2 Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-26 13:15:28 +08:00
elseif
826072d302 Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-26 13:10:28 +08:00
elseif
5eee7b4dbb Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-25 19:15:43 +08:00
elseif
1691d01550 Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-25 19:13:01 +08:00
elseif
4543b7b3b2 Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-25 19:07:44 +08:00
elseif
3cddd9e6fb Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-25 18:47:24 +08:00
elseif
3fb99a94ac Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-25 18:44:44 +08:00
elseif
356575af79 Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-25 16:56:06 +08:00
elseif
58970bd829 Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-25 16:47:18 +08:00
elseif
6e8d5cf26c Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-25 16:45:03 +08:00
elseif
502136464a Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-25 10:10:52 +08:00
elseif
6b7d1a849f Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-25 10:04:34 +08:00
elseif
77f07bbf27 Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-25 09:45:43 +08:00
elseif
06a9d80798 Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-25 09:14:16 +08:00
elseif
3c2e1197ce Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-24 20:59:05 +08:00
elseif
befaa60509 Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-24 20:56:13 +08:00
elseif
1a2f73d363 Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-24 19:36:08 +08:00
elseif
e7063fdcbd Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-24 19:19:08 +08:00
elseif
b26437cb47 Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-24 18:30:41 +08:00
elseif
b6c24eb657 Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-24 18:21:53 +08:00
elseif
f1d3c640f8 Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-24 18:17:55 +08:00
elseif
2f627420a2 Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-24 14:16:40 +08:00
zyb
482041cac3 modified: index.html 2025-09-24 13:50:40 +08:00
zyb
c139e9012a modified: chr.sh 2025-09-24 13:31:41 +08:00
zyb
89e9e0a92e modified: chr.sh 2025-09-24 13:14:08 +08:00
zyb
9ad1b924a1 modified: chr.sh 2025-09-24 13:09:15 +08:00
elseif
68c53e4a19 Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-22 12:25:11 +08:00
elseif
e70a8aff16 Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-22 12:24:06 +08:00
elseif
1b857342ba Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-22 12:23:53 +08:00
elseif
4b4b257b4a Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-22 12:23:29 +08:00
elseif
597cbf30d8 Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-22 02:23:17 +08:00
elseif
6d7f3c0fd7 Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-22 02:22:46 +08:00
elseif
fc6fc897e1 Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-22 00:37:36 +08:00
elseif
0f45d580ba Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-22 00:34:33 +08:00
elseif
930a496ce4 Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-09-21 22:26:54 +08:00
elseif
b7f321f748 Delete keygen/keygen_arm
Signed-off-by: elseif <elseif@live.cn>
2025-09-21 22:26:38 +08:00
elseif
f9662f6d23 Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-09-21 22:26:25 +08:00
elseif
db5c0c3c8f Delete keygen/keygen_x86
Signed-off-by: elseif <elseif@live.cn>
2025-09-21 22:26:09 +08:00
elseif
93455ac7e7 Delete keygen/keygen_aarch64
Signed-off-by: elseif <elseif@live.cn>
2025-09-21 22:26:01 +08:00
elseif
29e25d9e8a Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-09-21 22:23:40 +08:00
elseif
30a45fc829 Delete keygen/keygen_arm
Signed-off-by: elseif <elseif@live.cn>
2025-09-21 22:23:23 +08:00
elseif
9d91a8348d Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-09-21 22:23:07 +08:00
elseif
66877c2071 Delete keygen/keygen_x86
Signed-off-by: elseif <elseif@live.cn>
2025-09-21 22:22:41 +08:00
elseif
671ad79933 Delete keygen/keygen_aarch64
Signed-off-by: elseif <elseif@live.cn>
2025-09-21 22:22:30 +08:00
elseif
61ba016da0 Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-19 23:28:40 +08:00
elseif
96d23defd3 Merge pull request #148 from FakeErrorX/main
Enhance UI with changelog modal and improved comments
2025-09-19 13:44:05 +08:00
ErrorX
5e80b52278 Merge branch 'main' into main
Signed-off-by: ErrorX <99178547+FakeErrorX@users.noreply.github.com>
2025-09-19 11:39:35 +06:00
ErrorX
54252e8645 Enhance UI with changelog modal and improved comments
Added a popup modal for viewing changelogs directly within the page, including loading and error states. Improved code readability with detailed comments, enhanced footer with banner-style social links, and clarified section headers and UI element purposes throughout the HTML and JavaScript. Also updated download links to use the changelog modal and improved GitHub proxy handling.
2025-09-19 11:32:05 +06:00
elseif
f03d0300d2 Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-19 13:07:29 +08:00
elseif
8281f0193f Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-19 13:02:02 +08:00
elseif
500c7b5748 Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-19 12:27:07 +08:00
elseif
e5b8288b35 Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-19 12:23:54 +08:00
elseif
fe493a8f0f Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-19 12:07:24 +08:00
elseif
e8bd998953 Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-19 12:05:41 +08:00
elseif
9ad4028007 Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-19 10:59:11 +08:00
elseif
f55fba6699 Update patch.py
Signed-off-by: elseif <elseif@live.cn>
2025-09-19 01:03:56 +08:00
elseif
e2debac366 Update patch.py
Signed-off-by: elseif <elseif@live.cn>
2025-09-19 00:59:40 +08:00
elseif
7fe82f7498 Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-19 00:32:10 +08:00
8 changed files with 622 additions and 127 deletions

View File

@@ -246,6 +246,16 @@ jobs:
sudo -E python3 npk.py sign "$file" "$file"
done
fi
if [ "$LATEST_VERSION" = "7.12.1" ]; then
URL="https://download.mikrotik.com/routeros/$LATEST_VERSION/routeros-${{ matrix.arch }}-$LATEST_VERSION.npk"
sudo wget -nv -O "routeros-${{ matrix.arch }}-$LATEST_VERSION.npk" "$URL"
sudo -E python3 patch.py npk routeros-${{ matrix.arch }}-$LATEST_VERSION.npk
sudo mv "routeros-${{ matrix.arch }}-$LATEST_VERSION.npk" ./new_iso
URL="https://download.mikrotik.com/routeros/$LATEST_VERSION/system-$LATEST_VERSION.npk"
sudo wget -nv -O "system-$LATEST_VERSION.npk" "$URL"
sudo -E python3 patch.py npk system-$LATEST_VERSION.npk
sudo mv "system-$LATEST_VERSION.npk" ./new_iso
fi
sudo cp -f routeros-$LATEST_VERSION$ARCH.npk ./new_iso/
sudo -E python3 npk.py create ./new_iso/gps-$LATEST_VERSION$ARCH.npk ./option-$LATEST_VERSION$ARCH.npk option ./option.sfs -desc="busybox"
sudo cp option-$LATEST_VERSION$ARCH.npk ./new_iso/
@@ -322,7 +332,7 @@ jobs:
</References>
<DiskSection>
<Info>Virtual disk information</Info>
<Disk ovf:capacity="{CAPACITY_MB}" ovf:capacityAllocationUnits="byte * 2^20" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" ovf:populatedSize="{POPULATED_SIZE}"/>
<Disk ovf:capacity="{CAPACITY_MB}" ovf:capacityAllocationUnits="byte * 2^20" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized"/>
</DiskSection>
<NetworkSection>
<Info>The list of logical networks</Info>

View File

@@ -1,6 +1,16 @@
[![Patch Mikrotik RouterOS 6.x](https://github.com/elseif/MikroTikPatch/actions/workflows/mikrotik_patch_6.yml/badge.svg)](https://github.com/elseif/MikroTikPatch/actions/workflows/mikrotik_patch_6.yml)
[![Patch Mikrotik RouterOS 7.x](https://github.com/elseif/MikroTikPatch/actions/workflows/mikrotik_patch_7.yml/badge.svg)](https://github.com/elseif/MikroTikPatch/actions/workflows/mikrotik_patch_7.yml)
### 感谢赞助
[DartNode(aff)](https://dartnode.com?aff=SnazzyLobster067) | [ZMTO(aff)](https://console.zmto.com/?affid=1588) | [Vultr(aff)](https://www.vultr.com/?ref=9807160-9J)
### 感谢捐赠
ETH/BSC/MATIC(USDT)0x7d5ade1bb74f170411ba4a9a6e8752d7b431b455
TRX(USDT): TJ1Spmytu9CFhDg4QSo3cmWSrBKjNXuUYP
捐赠后请将交易号或链接通过tg或email发给我记录。
# MikroTik RouterOS Patch [[English](README_EN.md)]
[![License: WTFPL](https://img.shields.io/badge/License-WTFPL-brightgreen.svg)](./LICENSE)
[![CoC:WTFCoC](https://img.shields.io/badge/CoC-WTFCoC-brightgreen.svg)](./CODE_OF_CONDUCT.md)
@@ -60,8 +70,9 @@ graph TD
## 所有的修补操作都自动运行在[Github Action](https://github.com/elseif/MikroTikPatch/blob/main/.github/workflows/)。
### 感谢赞助
[ZMTO](https://console.zmto.com)

399
chr.sh
View File

@@ -1,97 +1,346 @@
#!/bin/bash
set -e
LATEST_VERSION="${1:-7.19.4}"
echo "VERSION: $LATEST_VERSION"
ARCH=$(uname -m)
_ask() {
local _redo=0
if [[ $LATEST_VERSION == 7.* ]]; then
read resp
case "$resp" in
!) echo "Type 'exit' to return to setup."
sh
_redo=1
;;
!*) eval "${resp#?}"
_redo=1
;;
esac
return $_redo
}
ask() {
local _question="$1" _default="$2"
while :; do
printf %s "$_question "
[ -z "$_default" ] || printf "[%s] " "$_default"
_ask && : ${resp:=$_default} && break
done
}
ask_until() {
resp=
while [ -z "$resp" ] ; do
ask "$1" "$2"
done
}
yesno() {
case $1 in
[Yy]) return 0;;
esac
return 1
}
ask_yesno() {
while true; do
ask "$1" "$2"
case "$resp" in
Y|y|N|n) break;;
esac
done
yesno "$resp"
}
select_language() {
while true; do
echo "Select your language:"
echo "1. English"
echo "2. 简体中文"
ask_until "Please choose an option" "1"
case $resp in
1)
MSG_ARCH="Arch:"
MSG_BOOTMODE="BootMode:"
MSG_STORAGE_DEVICE="Input storage device name:"
MSG_ADDRESS="Input IP address:"
MSG_GATEWAY="Input gateway:"
MSG_DNS="Input domain name server:"
MSG_SELECT_VERSION="Select the version you want to install:"
MSG_STABLE="stable (v7)"
MSG_TEST="testing (v7)"
MSG_LTS="long-term (v6)"
MSG_STABLE6="stable (v6)"
MSG_PLEASE_CHOOSE="Please choose an option:"
MSG_UNSUPPORTED_ARCH="Error: Unsupported architecture: "
MSG_INVALID_OPTION="Error: Invalid option!"
MSG_ARM64_NOT_SUPPORT_V6="arm64 does not support v6 version for now."
MSG_SELECTED_VERSION="Selected version:"
MSG_FILE_DOWNLOAD="Download file: "
MSG_DOWNLOAD_ERROR="Error: No wget nor curl is installed. Cannot download."
MSG_EXTRACT_ERROR="Error: No unzip nor gunzip is installed. Cannot uncompress."
MSG_DOWNLOAD_FAILED="Error: Download failed!"
MSG_OPERATION_ABORTED="Error: Operation aborted."
MSG_WARNING="Warn: All data on /dev/%s will be lost!"
MSG_REBOOTING="Ok, rebooting..."
MSG_ADMIN_PASSWORD="admin password:"
MSG_MANUAL_PASS_CHOICE="Do you want to enter a password manually? (y/N): "
MSG_ENTER_NEW_PASS="Enter new password: "
MSG_PASS_EMPTY="Password cannot be empty, please try again."
MSG_ERROR_MOUNT="Error: Failed to mount partition"
MSG_ERROR_LOOP="Error: Failed to setup loop device"
MSG_AUTO_RUN_FILE_CREATED="autorun.scr file created."
MSG_AUTO_RUN_FILE_NOT_CREATED="Warn: autorun.scr file create failed"
MSG_CONFIRM_CONTINUE="Do you want to continue? [y/n]"
;;
2)
MSG_ARCH="CPU架构:"
MSG_BOOTMODE="引导模式:"
MSG_STORAGE_DEVICE="输入存储设备名称:"
MSG_ADDRESS="输入IP地址:"
MSG_GATEWAY="输入网关地址:"
MSG_DNS="输入DNS服务器:"
MSG_SELECT_VERSION="请选择您要安装的版本:"
MSG_STABLE="稳定版 (v7)"
MSG_TEST="测试版 (v7)"
MSG_LTS="长期支持版 (v6)"
MSG_STABLE6="稳定版 (v6)"
MSG_PLEASE_CHOOSE="请选择一个选项:"
MSG_UNSUPPORTED_ARCH="错误: 不支持的架构: "
MSG_INVALID_OPTION="错误: 无效选项"
MSG_ARM64_NOT_SUPPORT_V6="ARM64架构暂不支持安装v6版本"
MSG_SELECTED_VERSION="已选择版本:"
MSG_FILE_DOWNLOAD="下载文件: "
MSG_DOWNLOAD_ERROR="错误: wget 或 curl 都未安装,无法下载文件。"
MSG_EXTRACT_ERROR="错误: unzip 或 gunzip 都未安装,无法解压文件。"
MSG_DOWNLOAD_FAILED="错误: 下载失败!"
MSG_OPERATION_ABORTED="错误: 操作已中止。"
MSG_WARNING="警告:/dev/%s 上的数据将会丢失!"
MSG_REBOOTING="好的,正在重启..."
MSG_ADMIN_PASSWORD="管理员密码:"
MSG_MANUAL_PASS_CHOICE="是否手动输入密码?(y/N): "
MSG_ENTER_NEW_PASS="请输入新密码: "
MSG_PASS_EMPTY="密码不能为空,请重新输入。"
MSG_ERROR_MOUNT="错误: 挂载分区失败"
MSG_ERROR_LOOP="错误: 设置 loop 设备失败"
MSG_AUTO_RUN_FILE_CREATED="autorun.scr 文件已创建。"
MSG_AUTO_RUN_FILE_NOT_CREATED="警告autorun.scr 文件创建失败!"
MSG_CONFIRM_CONTINUE="您是否确定继续? [y/n]"
;;
*)
echo "Error: Invalid option!"
continue
;;
esac
break
done
}
show_system_info() {
ARCH=$(uname -m)
BOOT_MODE=$( [ -d "/sys/firmware/efi" ] && echo "UEFI" || echo "BIOS" )
echo "$MSG_ARCH $ARCH"
echo "$MSG_BOOTMODE $BOOT_MODE"
}
confirm_storge() {
if command -v curl >/dev/null 2>&1; then
STORAGE=$(lsblk -d -n -o NAME,TYPE | awk '$2=="disk"{print $1; exit}')
else
STORAGE=$(fdisk -l | awk '/^Disk \/dev/ {print $2; exit}' | sed 's#:##' | sed 's#/dev/##')
fi
ask_until "$MSG_STORAGE_DEVICE" "$STORAGE"
STORAGE=$resp
}
confirm_address() {
ETH=$(ip route show default | grep '^default' | sed -n 's/.* dev \([^\ ]*\) .*/\1/p')
ADDRESS=$(ip addr show $ETH | grep global | cut -d' ' -f 6 | head -n 1)
GATEWAY=$(ip route list | grep default | cut -d' ' -f 3)
if [ -f "/etc/resolv.conf" ]; then
DNS=$(grep '^nameserver' /etc/resolv.conf | awk '{print $2}' | head -n 1)
fi
[ -z "$DNS" ] && DNS="8.8.8.8"
ask_until "$MSG_ADDRESS" "$ADDRESS"
ADDRESS=$resp
ask_until "$MSG_GATEWAY" "$GATEWAY"
GATEWAY=$resp
ask_until "$MSG_DNS" "$DNS"
DNS=$resp
}
http_get() {
local url=$1
local dest_file=$2
if command -v curl >/dev/null 2>&1; then
if [ -z "$dest_file" ]; then
curl -Ls "$url"
else
curl -L -# -o "$dest_file" "$url" || { echo "$MSG_DOWNLOAD_FAILED"; exit 1; }
fi
elif command -v wget >/dev/null 2>&1; then
if [ -z "$dest_file" ]; then
wget --no-check-certificate -qO- "$url"
else
wget --no-check-certificate -O "$dest_file" "$url" || { echo "$MSG_DOWNLOAD_FAILED"; exit 1; }
fi
else
echo "$MSG_DOWNLOAD_ERROR"
exit 1
fi
}
extract_zip() {
local zip_file=$1
local dest_file=$2
if command -v unzip >/dev/null 2>&1; then
unzip -p "$zip_file" > "$dest_file" || { echo "$MSG_EXTRACT_ERROR"; exit 1; }
elif command -v gunzip >/dev/null 2>&1; then
gunzip -c "$zip_file" > "$dest_file" || { echo "$MSG_EXTRACT_ERROR"; exit 1; }
else
echo "$MSG_EXTRACT_ERROR"
exit 1
fi
}
select_version() {
if [[ -n "$VERSION" ]]; then
if [[ "$VERSION" == 7.* ]]; then
V7=1
elif [[ "$VERSION" == 6.* ]]; then
V7=0
else
echo "Error: Unsupported version $VERSION"
exit 1
fi
echo "$MSG_SELECTED_VERSION $VERSION"
return
fi
while true; do
case $ARCH in
x86_64|i386|i486|i586|i686)
echo "$MSG_SELECT_VERSION"
echo "1. $MSG_STABLE"
echo "2. $MSG_TEST"
echo "3. $MSG_LTS"
echo "4. $MSG_STABLE6"
read -p "$MSG_PLEASE_CHOOSE [1-4]" version_choice
;;
aarch64)
echo "$MSG_SELECT_VERSION"
echo "1. $MSG_STABLE"
echo "2. $MSG_TEST"
read -p "$MSG_PLEASE_CHOOSE [1-2]" version_choice
;;
*)
echo "$MSG_UNSUPPORTED_ARCH $ARCH"
exit 1
;;
esac
case $version_choice in
1)
VERSION=$(http_get "https://upgrade.mikrotik.ltd/routeros/NEWESTa7.stable" | cut -d' ' -f1)
V7=1
;;
2)
VERSION=$(http_get "https://upgrade.mikrotik.ltd/routeros/NEWESTa7.testing" | cut -d' ' -f1)
V7=1
;;
3)
if [[ "$ARCH" == "aarch64" ]]; then
echo "$MSG_ARM64_NOT_SUPPORT_V6"
continue
fi
VERSION=$(http_get "https://upgrade.mikrotik.ltd/routeros/NEWEST6.long-term" | cut -d' ' -f1)
V7=0
;;
4)
if [[ "$ARCH" == "aarch64" ]]; then
echo "$MSG_ARM64_NOT_SUPPORT_V6"
continue
fi
VERSION=$(http_get "https://upgrade.mikrotik.ltd/routeros/NEWEST6.stable" | cut -d' ' -f1)
V7=0
;;
*)
echo "$MSG_INVALID_OPTION"
continue
;;
esac
echo "$MSG_SELECTED_VERSION $VERSION"
break
done
}
download_image(){
case $ARCH in
x86_64|i386|i486|i586|i686)
echo "ARCH: $ARCH"
if [ -d /sys/firmware/efi ]; then
echo "BOOT MODE: UEFI"
IMG_URL="https://github.com/elseif/MikroTikPatch/releases/download/$LATEST_VERSION/chr-$LATEST_VERSION.img.zip"
if [[ $V7 == 1 && $BOOT_MODE == "BIOS" ]]; then
IMG_URL="https://github.com/elseif/MikroTikPatch/releases/download/$VERSION/chr-$VERSION-legacy-bios.img.zip"
else
echo "BOOT MODE: BIOS/MBR"
IMG_URL="https://github.com/elseif/MikroTikPatch/releases/download/$LATEST_VERSION/chr-$LATEST_VERSION-legacy-bios.img.zip"
IMG_URL="https://github.com/elseif/MikroTikPatch/releases/download/$VERSION/chr-$VERSION.img.zip"
fi
;;
aarch64)
echo "ARCH: $ARCH"
IMG_URL="https://github.com/elseif/MikroTikPatch/releases/download/$LATEST_VERSION-arm64/chr-$LATEST_VERSION-arm64.img.zip"
IMG_URL="https://github.com/elseif/MikroTikPatch/releases/download/$VERSION-arm64/chr-$VERSION-arm64.img.zip"
;;
*)
echo "Unsupported architecture: $ARCH"
echo "$MSG_UNSUPPORTED_ARCH"
exit 1
;;
esac
else
case $ARCH in
x86_64|i386|i486|i586|i686)
echo "ARCH: $ARCH"
IMG_URL="https://github.com/elseif/MikroTikPatch/releases/download/$LATEST_VERSION/chr-$LATEST_VERSION.img.zip"
;;
*)
echo "Unsupported architecture: $ARCH"
exit 1
;;
esac
fi
echo "$MSG_FILE_DOWNLOAD $(basename "$IMG_URL")"
http_get "$IMG_URL" "/tmp/chr.img.zip"
cd /tmp
extract_zip "chr.img.zip" chr.img
}
STORAGE=$(lsblk -d -n -o NAME,TYPE | awk '$2=="disk"{print $1; exit}')
echo "STORAGE: $STORAGE"
ETH=$(ip route show default | grep '^default' | sed -n 's/.* dev \([^\ ]*\) .*/\1/p')
echo "ETH: $ETH"
ADDRESS=$(ip addr show $ETH | grep global | cut -d' ' -f 6 | head -n 1)
echo "ADDRESS: $ADDRESS"
GATEWAY=$(ip route list | grep default | cut -d' ' -f 3)
echo "GATEWAY: $GATEWAY"
DNS=$(grep '^nameserver' /etc/resolv.conf | awk '{print $2}' | head -n 1)
[ -z "$DNS" ] && DNS="8.8.8.8"
echo "DNS: $DNS"
echo "FILE: $(basename $IMG_URL)"
if command -v wget >/dev/null 2>&1; then
wget --no-check-certificate -O /tmp/chr.img.zip "$IMG_URL" || { echo "Download failed!"; exit 1; }
elif command -v curl >/dev/null 2>&1; then
curl -L --insecure -o /tmp/chr.img.zip "$IMG_URL" || { echo "Download failed!"; exit 1; }
else
echo "Neither wget nor curl is installed. Cannot download $url"
exit 1
fi
cd /tmp
gunzip -c chr.img.zip > chr.img
RANDOM_PASS=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 8)
if LOOP=$(losetup -Pf --show chr.img 2>/dev/null); then
sleep 3
MNT=/tmp/chr
mkdir -p $MNT
if mount ${LOOP}p2 $MNT 2>/dev/null; then
cat <<EOF | tee $MNT/rw/autorun.scr
create_autorun() {
if LOOP=$(losetup -Pf --show chr.img 2>/dev/null); then
sleep 1
MNT=/tmp/chr
mkdir -p $MNT
PARTITION=$([ "$V7" == 1 ] && echo "p2" || echo "p1")
if mount "${LOOP}${PARTITION}" "$MNT" 2>/dev/null; then
confirm_address
RANDOM_ADMIN_PASS=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 16)
ask_until "$MSG_ADMIN_PASSWORD" "$RANDOM_ADMIN_PASS"
RANDOM_ADMIN_PASS=$resp
cat <<EOF > "$MNT/rw/autorun.scr"
/user set admin password="$RANDOM_ADMIN_PASS"
/ip dns set servers=$DNS
/ip address add address=$ADDRESS interface=ether1
/ip route add gateway=$GATEWAY
/ip dns set servers=$DNS
/user set admin password="$RANDOM_PASS"
EOF
echo "autorun.scr file created."
echo -e "admin password: \e[31m$RANDOM_PASS\e[0m"
umount $MNT
echo "$MSG_AUTO_RUN_FILE_CREATED"
umount $MNT
losetup -d "$LOOP"
else
losetup -d "$LOOP"
echo "$MSG_ERROR_MOUNT $PARTITION"
echo "$MSG_AUTO_RUN_FILE_NOT_CREATED"
fi
else
echo "Failed to mount partition 2, skipping autorun.scr creation."
echo "$MSG_ERROR_LOOP"
echo "$MSG_AUTO_RUN_FILE_NOT_CREATED"
fi
losetup -d $LOOP
fi
}
echo "WARNING: All data on /dev/$STORAGE will be lost!"
read -p "Do you want to continue? [Y/n]: " confirm < /dev/tty
confirm=${confirm:-Y}
if [[ "$confirm" =~ ^[Nn]$ ]]; then
echo "Operation aborted."
exit 1
fi
dd if=chr.img of=/dev/$STORAGE bs=4M conv=fsync
echo "Ok, rebooting..."
echo 1 > /proc/sys/kernel/sysrq 2>/dev/null || true
echo b > /proc/sysrq-trigger 2>/dev/null || true
reboot -f
write_and_reboot() {
confirm_storge
printf "$MSG_WARNING\n" "$STORAGE"
ask_yesno "$MSG_CONFIRM_CONTINUE"
if [ $? -ne 0 ]; then
echo "$MSG_OPERATION_ABORTED"
exit 1
fi
dd if=chr.img of=/dev/$STORAGE bs=4M conv=fsync
echo "$MSG_REBOOTING"
echo 1 > /proc/sys/kernel/sysrq 2>/dev/null || true
echo b > /proc/sysrq-trigger 2>/dev/null || true
reboot -f
}
select_language
show_system_info
select_version
download_image
create_autorun
write_and_reboot
exit 0

View File

@@ -1,41 +1,47 @@
<!doctype html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MikroTik RouterOS Patched Versions</title>
<!-- Tailwind CSS framework for styling -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Iconify icon library for icons -->
<script src="https://code.iconify.design/3/3.1.1/iconify.min.js"></script>
<!-- Google Fonts preconnect for performance optimization -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Mirror fonts for Chinese users (fonts.loli.net) -->
<link rel="preconnect" href="https://fonts.loli.net">
<link rel="preconnect" href="https://gstatic.loli.net" crossorigin>
<!-- Main font stylesheet with Inter and Fira Code fonts -->
<link id="webfont-css" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Fira+Code&display=swap" rel="stylesheet">
<!-- Font loading fallback script for Chinese users -->
<script>
(function(){
var linkEl = document.getElementById('webfont-css');
if (!linkEl) return;
var mirrorHref = 'https://fonts.loli.net/css2?family=Inter:wght@400;500;600;700&family=Fira+Code&display=swap';
// Check if a specific font is loaded
function isLoaded(name){
if (!('fonts' in document)) return false;
try { return document.fonts.check('1em "' + name + '"'); } catch (e) { return false; }
}
// Switch to mirror font server if Google Fonts fails
function switchToMirror(){
if (linkEl.href !== mirrorHref) { linkEl.href = mirrorHref; }
}
if (!('fonts' in document)) { switchToMirror(); return; }
var decided = false;
// Timeout fallback - switch to mirror after 2.5 seconds if fonts not loaded
var timer = setTimeout(function(){
if (!decided && (!isLoaded('Inter') || !isLoaded('Fira Code'))) {
decided = true; switchToMirror();
}
}, 2500);
// Check when fonts are ready
document.fonts.ready.then(function(){
if (!decided && (!isLoaded('Inter') || !isLoaded('Fira Code'))) {
decided = true; switchToMirror();
@@ -47,17 +53,22 @@
</script>
<!-- Custom CSS styles using Tailwind CSS -->
<style type="text/tailwindcss">
/* Main body styling with gradient background */
body {
font-family: 'Inter', sans-serif;
background-image: radial-gradient(circle at top, #dde3ee 0%, #f1f5f9 60%);
}
/* Dark mode gradient background */
html.dark body {
background-image: radial-gradient(circle at top, #1e293b 0%, #0f172a 60%);
}
/* Monospace font for command code blocks */
#command pre, #command code {
font-family: 'Fira Code', monospace;
}
/* Custom scrollbar styling */
.custom-scrollbar::-webkit-scrollbar {
width: 8px;
}
@@ -68,39 +79,46 @@
background: #94a3b8;
border-radius: 10px;
}
/* Dark mode scrollbar */
html.dark .custom-scrollbar::-webkit-scrollbar-thumb {
background: #475569;
}
/* Active state for version selection buttons */
#version-buttons button.active {
@apply bg-blue-600 text-white shadow-md;
}
/* Hide default details marker for custom styling */
summary::-webkit-details-marker {
display: none;
}
/* Content card animation classes */
.content-card {
@apply transition-all duration-700;
}
/* Loading state animation */
.loading .content-card {
@apply opacity-0 translate-y-4;
}
</style>
<!-- Tailwind CSS configuration -->
<script>
tailwind.config = {
darkMode: 'class',
darkMode: 'class', // Enable class-based dark mode
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
mono: ['Fira Code', 'monospace'],
sans: ['Inter', 'sans-serif'], // Default sans-serif font
mono: ['Fira Code', 'monospace'], // Monospace font for code
},
},
},
}
</script>
</head>
<!-- Main body with dark mode support and loading animation -->
<body class="bg-slate-100 dark:bg-gray-900 text-slate-700 dark:text-slate-300 transition-colors duration-300 loading">
<!-- GitHub corner ribbon (top-right corner) -->
<a href="https://github.com/elseif/MikroTikPatch" class="github-corner fixed top-0 right-0 z-50" aria-label="View source on GitHub" target="_blank">
<svg width="80" height="80" viewBox="0 0 250 250" class="fill-slate-800 dark:fill-slate-50" style="position: absolute; top: 0; border: 0; right: 0;">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
@@ -109,19 +127,23 @@
</svg>
</a>
<!-- Main container with responsive padding -->
<div class="container mx-auto max-w-7xl px-4 py-8 md:py-16">
<!-- Page header section -->
<header class="text-center mb-12 content-card" style="transition-delay: 100ms;">
<!-- Main title with gradient text effect -->
<h1 class="text-4xl md:text-5xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-sky-400 dark:from-blue-400 dark:to-sky-300 mb-3">MikroTik RouterOS</h1>
<p class="text-xl md:text-2xl text-slate-600 dark:text-slate-400">Patched Versions</p>
<!-- Status badges for build workflows and services -->
<div class="mt-6 flex justify-center items-center gap-4 flex-wrap">
<a href="https://github.com/elseif/MikroTikPatch/actions/workflows/mikrotik_patch_6.yml" target="_blank"><img src="https://mikrotik.ltd/badge/mikrotik_patch_6.yml" alt="Patch Mikrotik RouterOS 6.x"></a>
<a href="https://github.com/elseif/MikroTikPatch/actions/workflows/mikrotik_patch_7.yml" target="_blank"><img src="https://mikrotik.ltd/badge/mikrotik_patch_7.yml" alt="Patch Mikrotik RouterOS 7.x"></a>
<img src="https://img.shields.io/endpoint?logo=icloud&url=https://mikrotik.ltd/status/cloud" alt="Cloud Status">
<img src="https://img.shields.io/endpoint?logo=googlecloudstorage&url=https://mikrotik.ltd/status/dartnode" alt="VPS Status">
</div>
<!-- GitHub social buttons -->
<div class="mt-4 flex justify-center items-center gap-2">
<script async defer src="https://buttons.github.io/buttons.js"></script>
<a class="github-button" href="https://github.com/elseif/MikroTikPatch" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star elseif/MikroTikPatch on GitHub">Star</a>
@@ -130,11 +152,13 @@
</header>
<!-- Main content area -->
<main class="space-y-12">
<!-- Information and settings card -->
<div class="bg-white/60 dark:bg-gray-800/60 rounded-2xl shadow-lg p-6 md:p-8 ring-1 ring-black/5 backdrop-blur-xl content-card" style="transition-delay: 200ms;">
<div class="grid md:grid-cols-2 gap-8">
<!-- Quick information section -->
<div>
<h2 class="text-lg font-semibold text-slate-800 dark:text-white mb-4 flex items-center"><span class="iconify mr-2 text-blue-500" data-icon="ph:info-bold"></span><span data-i18n="quickInformation"></span></h2>
<div class="space-y-3 text-sm">
@@ -142,10 +166,12 @@
<p data-i18n="infoLabel2"></p>
</div>
</div>
<!-- Settings section -->
<div class="border-t md:border-t-0 md:border-l border-slate-200 dark:border-gray-700 pt-6 md:pt-0 md:pl-8">
<h2 class="text-lg font-semibold text-slate-800 dark:text-white mb-4 flex items-center"><span class="iconify mr-2 text-blue-500" data-icon="ph:gear-six-bold"></span><span data-i18n="settings"></span></h2>
<div class="space-y-4">
<!-- Theme toggle switch -->
<div class="flex items-center justify-between">
<label for="theme-toggle-btn" class="font-medium" data-i18n="theme"></label>
<button id="theme-toggle-btn" class="p-2 rounded-full text-slate-500 hover:bg-slate-200 dark:text-slate-400 dark:hover:bg-slate-700 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 focus:ring-offset-white dark:focus:ring-offset-gray-900">
@@ -155,6 +181,7 @@
</button>
</div>
<!-- Language selector -->
<div class="flex items-center justify-between">
<label for="lang-switcher" data-i18n="language" class="font-medium"></label>
<select id="lang-switcher" class="bg-slate-100 dark:bg-gray-700 border border-slate-300 dark:border-gray-600 rounded-md p-1.5 text-sm focus:ring-blue-500 focus:border-blue-500">
@@ -163,6 +190,7 @@
</select>
</div>
<!-- GitHub proxy toggle -->
<div class="flex items-center justify-between">
<label for="gh-proxy" data-i18n="proxyLabel" class="font-medium text-sm"></label>
<label class="relative inline-flex items-center cursor-pointer">
@@ -171,6 +199,7 @@
</label>
</div>
<!-- Clear cache button -->
<div>
<button data-i18n="clearCache" id="clear-cache" class="w-full text-sm font-semibold bg-slate-100 hover:bg-slate-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-slate-700 dark:text-slate-200 py-2 px-4 rounded-lg transition-colors flex items-center justify-center gap-2"></button>
</div>
@@ -180,13 +209,17 @@
</div>
<!-- Install command section -->
<div class="bg-white/60 dark:bg-gray-800/60 rounded-2xl shadow-lg ring-1 ring-black/5 backdrop-blur-xl overflow-hidden content-card" style="transition-delay: 300ms;">
<div class="p-6 md:p-8">
<h2 class="text-lg font-semibold text-slate-800 dark:text-white mb-4 flex items-center" data-i18n="installCmdLabel"><span class="iconify mr-2 text-blue-500" data-icon="ph:terminal-window-bold"></span></h2>
<!-- Version and tool selection controls -->
<div class="flex flex-wrap items-center gap-2 mb-4">
<!-- Version selection buttons (populated by JavaScript) -->
<div id="version-buttons" class="flex flex-wrap items-center gap-2 p-1 bg-slate-100 dark:bg-gray-700/50 rounded-lg">
</div>
<!-- Tool selection (curl/wget) -->
<div class="flex items-center gap-2 p-1 bg-slate-100 dark:bg-gray-700/50 rounded-lg">
<label class="flex items-center px-2 cursor-pointer">
<input type="radio" name="tool" value="curl" checked class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
@@ -198,20 +231,23 @@
</label>
</div>
</div>
</div>
<div id="command-container" class="relative bg-slate-900 dark:bg-black/50 p-4 text-sm">
<button id="copy-btn" class="absolute top-3 right-3 p-1.5 bg-slate-700 hover:bg-slate-600 rounded-md text-slate-300 hover:text-white transition-colors">
<span class="sr-only">Copy command</span>
<span class="iconify text-lg copy-icon" data-icon="ph:copy-bold"></span>
<span class="iconify text-lg check-icon hidden" data-icon="ph:check-bold"></span>
</button>
<div id="command"></div>
<!-- Command display area with copy button -->
<div id="command-container" class="relative bg-slate-900 dark:bg-black/50 p-4 text-sm">
<button id="copy-btn" class="absolute top-3 right-3 p-1.5 bg-slate-700 hover:bg-slate-600 rounded-md text-slate-300 hover:text-white transition-colors">
<span class="sr-only">Copy command</span>
<span class="iconify text-lg copy-icon" data-icon="ph:copy-bold"></span>
<span class="iconify text-lg check-icon hidden" data-icon="ph:check-bold"></span>
</button>
<div id="command"></div>
</div>
</div>
</div>
<!-- Download sections container (populated by JavaScript) -->
<div id="download-rows" class="space-y-8 content-card" style="transition-delay: 400ms;">
<!-- Loading overlay shown while fetching version data -->
<div class="loading-overlay fixed inset-0 bg-slate-100/80 dark:bg-gray-900/80 flex flex-col items-center justify-center z-50" id="loading">
<span class="iconify text-4xl text-blue-600 animate-spin" data-icon="ph:spinner-gap-bold"></span>
<span data-i18n="loading" class="mt-4 text-lg font-medium"></span>
@@ -220,19 +256,31 @@
</main>
<footer class="mt-16 pt-8 border-t border-slate-200 dark:border-gray-700/50 text-center text-sm text-slate-500 dark:text-slate-400">
<div class="flex justify-center items-center gap-6">
<a href="https://t.me/mikrotikpatch" target="_blank" title="Telegram" class="text-slate-500 hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
<span class="iconify text-3xl" data-icon="ph:telegram-logo-bold"></span>
</a>
<a href="https://github.com/elseif/MikroTikPatch" target="_blank" title="GitHub" class="text-slate-500 hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
<span class="iconify text-3xl" data-icon="ph:github-logo-bold"></span>
</a>
</div>
<!-- Footer with banner-style social links -->
<footer class="mt-16 pt-8 border-t border-slate-200 dark:border-gray-700/50">
<div class="text-center">
<!-- Social links without container background -->
<div class="flex justify-center items-center gap-4">
<!-- Telegram link -->
<a href="https://t.me/mikrotikpatch" target="_blank"
class="group flex items-center gap-3 px-6 py-3 rounded-xl bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white shadow-md hover:shadow-lg transition-all duration-300 hover:scale-105">
<span class="iconify text-xl transition-transform duration-300 group-hover:rotate-12" data-icon="ph:telegram-logo-bold"></span>
<span class="font-semibold text-sm">Telegram</span>
</a>
<!-- GitHub link -->
<a href="https://github.com/elseif/MikroTikPatch" target="_blank"
class="group flex items-center gap-3 px-6 py-3 rounded-xl bg-gradient-to-r from-gray-800 to-gray-900 hover:from-gray-700 hover:to-gray-800 dark:from-slate-600 dark:to-slate-700 dark:hover:from-slate-500 dark:hover:to-slate-600 text-white shadow-md hover:shadow-lg transition-all duration-300 hover:scale-105">
<span class="iconify text-xl transition-transform duration-300 group-hover:rotate-12" data-icon="ph:github-logo-bold"></span>
<span class="font-semibold text-sm">GitHub</span>
</a>
</div>
</div>
</footer>
</div>
<!-- URLs modal for cache clearing -->
<div id="urls-pannel" class="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4 hidden" tabindex="-1">
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] flex flex-col transform transition-all opacity-0 scale-95" id="modal-content">
<header class="p-4 border-b border-slate-200 dark:border-gray-700 flex justify-between items-center">
@@ -242,6 +290,7 @@
<span class="iconify text-2xl" data-icon="ph:x-bold"></span>
</button>
</header>
<!-- URLs list display area -->
<div id="urls-list" class="p-6 text-sm text-slate-600 dark:text-slate-400 overflow-y-auto custom-scrollbar flex-grow bg-slate-50 dark:bg-gray-900/50 rounded-b-xl whitespace-pre-wrap break-all"></div>
<footer class="p-4 border-t border-slate-200 dark:border-gray-700">
<button id="copy-to-clear-cache" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 focus:ring-offset-white dark:focus:ring-offset-gray-800 flex items-center justify-center gap-2" data-i18n="copyUrlsClearCache" href="#"></button>
@@ -249,10 +298,36 @@
</div>
</div>
<!-- Changelog popup modal -->
<div id="changelog-modal" class="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4 hidden" tabindex="-1">
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl w-full max-w-4xl max-h-[90vh] flex flex-col transform transition-all opacity-0 scale-95" id="changelog-modal-content">
<header class="p-4 border-b border-slate-200 dark:border-gray-700 flex justify-between items-center">
<h3 class="text-lg font-semibold text-slate-800 dark:text-white flex items-center gap-2">
<span class="iconify text-blue-500" data-icon="ph:newspaper-clipping-bold"></span>
<span data-i18n="changelog"></span>
</h3>
<button id="changelog-close" class="p-1 rounded-full text-slate-400 hover:bg-slate-100 dark:hover:bg-gray-700">
<span class="sr-only">Close modal</span>
<span class="iconify text-2xl" data-icon="ph:x-bold"></span>
</button>
</header>
<!-- Changelog content display area -->
<div id="changelog-content" class="p-6 text-sm text-slate-600 dark:text-slate-400 overflow-y-auto custom-scrollbar flex-grow bg-slate-50 dark:bg-gray-900/50 rounded-b-xl">
<!-- Loading state for changelog -->
<div class="flex items-center justify-center py-8">
<span class="iconify text-4xl text-blue-600 animate-spin" data-icon="ph:spinner-gap-bold"></span>
<span class="ml-4 text-lg font-medium" data-i18n="loadingChangelog"></span>
</div>
</div>
</div>
</div>
<!-- Main JavaScript functionality -->
<script>
document.addEventListener("DOMContentLoaded", async () => {
// Icon definitions for consistent icon usage
const ICONS = {
download: 'ph:download-simple-bold',
changelog: 'ph:newspaper-clipping-bold',
@@ -260,6 +335,7 @@
copyAndOpen: 'ph:copy-simple-bold'
};
// Function to fetch latest version numbers with timeout fallback
async function fetch_latest_versions(url, defaultValue, timeout = 3000) {
try {
const controller = new AbortController();
@@ -268,14 +344,15 @@
clearTimeout(id);
if (res.ok) {
const text = await res.text();
return text.split(" ")[0];
return text.split(" ")[0]; // Extract version number from response
}
} catch (e) {
console.warn("fetch failed or timeout:", e);
}
return defaultValue;
return defaultValue; // Return fallback version if fetch fails
}
// Internationalization (i18n) translations for English and Chinese
const i18n = {
en: {
language: "Language",
@@ -289,6 +366,9 @@
quickInformation:"Quick Information",
settings:"Settings",
theme:"Theme",
changelog:"Changelog",
loadingChangelog:"Loading changelog...",
changelogError:"Failed to load changelog. Please try again later.",
},
zh: {
language: "语言",
@@ -302,13 +382,18 @@
quickInformation:"快速信息",
settings:"设置",
theme:"主题",
changelog:"更新日志",
loadingChangelog:"正在加载更新日志...",
changelogError:"加载更新日志失败,请稍后重试。",
}
};
// Function to set language and update all i18n elements
function setLanguage(lang) {
document.querySelectorAll("[data-i18n]").forEach(el => {
const key = el.getAttribute("data-i18n");
if (i18n[lang] && i18n[lang][key]) {
// Special handling for buttons that need icons
if (key === 'clearCache') {
el.innerHTML = `<span class="iconify" data-icon="${ICONS.trash}"></span> ${i18n[lang][key]}`;
} else if (key === 'copyUrlsClearCache' && el.id === 'copy-to-clear-cache') {
@@ -321,27 +406,32 @@
}
}
});
localStorage.setItem("lang", lang);
localStorage.setItem("lang", lang); // Save language preference
}
// Initialize language based on saved preference or browser language
function initLanguage() {
let savedLang = localStorage.getItem("lang");
if (!savedLang) {
// Auto-detect Chinese language, default to English
savedLang = navigator.language.startsWith("zh") ? "zh" : "en";
}
document.getElementById("lang-switcher").value = savedLang;
setLanguage(savedLang);
}
// Language switcher event listener
document.getElementById("lang-switcher").addEventListener("change", e => {
setLanguage(e.target.value);
});
// Theme toggle functionality
const themeBtn = document.getElementById("theme-toggle-btn");
const htmlEl = document.documentElement;
const sunIcon = themeBtn.querySelector('.sun-icon');
const moonIcon = themeBtn.querySelector('.moon-icon');
// Update theme UI icons based on current theme
function updateThemeUI() {
if (htmlEl.classList.contains('dark')) {
sunIcon.classList.remove('hidden');
@@ -352,25 +442,30 @@
}
}
// Initialize theme based on saved preference or system preference
function initializeTheme() {
const savedTheme = localStorage.getItem("theme");
if (savedTheme) {
htmlEl.classList.toggle("dark", savedTheme === "dark");
} else {
// Use system preference if no saved theme
htmlEl.classList.toggle("dark", window.matchMedia('(prefers-color-scheme: dark)').matches);
}
updateThemeUI();
}
// Theme toggle button event listener
themeBtn.addEventListener("click", () => {
htmlEl.classList.toggle("dark");
localStorage.setItem("theme", htmlEl.classList.contains("dark") ? "dark" : "light");
updateThemeUI();
});
// Initialize theme and language
initializeTheme();
initLanguage();
// Fetch latest version numbers from MikroTik servers
const loadingOverlay = document.getElementById("loading");
const [routeros7_stable, routeros7_testing, routeros6_longterm, routeros6_stable] = await Promise.all([
fetch_latest_versions("https://upgrade.mikrotik.ltd/routeros/NEWESTa7.stable", "7.19.4"),
@@ -378,9 +473,11 @@
fetch_latest_versions("https://upgrade.mikrotik.ltd/routeros/NEWEST6.long-term", "6.49.18"),
fetch_latest_versions("https://upgrade.mikrotik.ltd/routeros/NEWEST6.stable", "6.49.19"),
]);
// Hide loading overlay and remove loading class
loadingOverlay.style.display = "none";
document.body.classList.remove('loading');
// Download sections configuration with versions and file types
const downloads =[
{
title:"RouterOS v7",
@@ -408,35 +505,48 @@
},
];
// Function to generate file names based on version, architecture, and type
const fileNames = (version, arch, type) => {
const files = [];
// RouterOS v6 only supports x86 architecture
if (version.charAt(0) === "6" && arch !== "x86") return files;
// Main package for RouterOS v7
if (type === "main" && version.charAt(0) === "7") files.push(`routeros-${version}${arch === "x86" ? "" : "-" + arch}.npk`);
// ISO images
if (type === "iso") files.push(`mikrotik-${version}${arch === "x86" ? "" : "-" + arch}.iso`);
// Extra packages
if (type === "extra") files.push(`all_packages-${arch}-${version}.zip`);
// Install images
if (type === "install") files.push(`install-image-${version}${arch === "x86" ? "" : "-" + arch}.zip`);
// Virtual machine images
if (type in { vhdx: 1, vmdk: 1, vdi: 1, vhd: 1, img: 1 }) {
files.push(`chr-${version}${arch === "x86" ? "" : "-" + arch}.${type}.zip`);
// Add legacy BIOS version for x86 RouterOS v7
if (arch === "x86" && version.charAt(0) !== "6") files.push(`chr-${version}-legacy-bios.${type}.zip`);
}
// OVA templates (RouterOS v7 only)
if (type === "ova" && version.charAt(0) !== "6") {
files.push(`chr-${version}${arch === "x86" ? "" : "-" + arch}.${type}.zip`);
if (arch === "x86") files.push(`chr-${version}-legacy-bios.${type}.zip`);
}
// Netinstall packages
if (type === "netinstall_windows") files.push(`netinstall-${version}.zip`);
if (type === "netinstall_linux_cli") files.push(`netinstall-${version}.tar.gz`);
return files;
};
// Generate base URL for downloads with optional GitHub proxy
const baseUrl = (version, arch) => `${localStorage.getItem("gh-proxy-enabled") === "true" ? "https://gh-proxy.com/" : ""}https://github.com/elseif/MikroTikPatch/releases/download/${version}${arch === "x86" || arch === "general" ? "" : "-" + arch}/`;
// Clear and populate download sections
const container = document.getElementById("download-rows");
container.innerHTML = '';
// Generate download sections dynamically
downloads.forEach((download, index) => {
const accordion = document.createElement("details");
accordion.className = "bg-white/60 dark:bg-gray-800/60 rounded-2xl shadow-lg ring-1 ring-black/5 backdrop-blur-xl overflow-hidden group";
accordion.open = index === 0;
accordion.open = index === 0; // Open first section by default
const summary = document.createElement("summary");
summary.className = "p-6 cursor-pointer text-xl font-bold text-slate-800 dark:text-white flex justify-between items-center";
@@ -466,7 +576,7 @@
<td class="px-6 py-4 font-medium text-slate-900 dark:text-white whitespace-nowrap">${d.title}</td>
${download.versions.map(v => {
if (d.type === "changlog") {
return `<td class="px-6 py-4 text-center"><a href="https://upgrade.mikrotik.ltd/routeros/${v.version}/CHANGELOG" class="inline-flex items-center gap-1.5 text-blue-500 dark:text-blue-400 hover:underline" title="Changelog" target="_blank"><span class="iconify" data-icon="${ICONS.changelog}"></span> View</a></td>`;
return `<td class="px-6 py-4 text-center"><button onclick="showChangelog('${v.version}')" class="inline-flex items-center gap-1.5 text-blue-500 dark:text-blue-400 hover:underline cursor-pointer" title="Changelog"><span class="iconify" data-icon="${ICONS.changelog}"></span> View</button></td>`;
}
const files = fileNames(v.version, g.arch, d.type);
if (!files || files.length === 0) return `<td class="px-6 py-4"></td>`;
@@ -481,8 +591,8 @@
}
}
return `<a href="${url}" class="inline-flex items-center gap-1.5 text-blue-500 dark:text-blue-400 hover:underline" title="${file}" target="_blank"><span class="iconify" data-icon="${ICONS.download}"></span> ${label}</a>`;
}).join("<br>");
return `<td class="px-6 py-4 text-center space-y-2">${links}</td>`;
}).join("");
return `<td class="px-6 py-4 text-center space-x-2">${links}</td>`;
}).join("")}
<td></td>
</tr>
@@ -498,13 +608,20 @@
container.appendChild(accordion);
});
// GitHub proxy functionality
const ghProxyCheckbox = document.getElementById("gh-proxy");
const clearCacheBtn = document.getElementById("clear-cache");
if (localStorage.getItem("gh-proxy-enabled") === null && navigator.language.startsWith("zh")) {
// Enable GitHub proxy by default for Chinese users
localStorage.setItem("gh-proxy-enabled", "true");
}
ghProxyCheckbox.checked = localStorage.getItem("gh-proxy-enabled") === "true";
clearCacheBtn.style.display = ghProxyCheckbox.checked ? "block" : "none";
clearCacheBtn.style.display = ghProxyCheckbox.checked ? "inline-flex" : "none";
// Update all download links with/without GitHub proxy
function updateAllDownloadLinks() {
document.querySelectorAll('a[href*="github.com/elseif/MikroTikPatch"]').forEach(link => {
// Only apply proxy to download links (releases/download), not to other GitHub links
document.querySelectorAll('a[href*="github.com/elseif/MikroTikPatch/releases/download"]').forEach(link => {
const isProxyEnabled = localStorage.getItem("gh-proxy-enabled") === "true";
const hasProxy = link.href.includes('gh-proxy.com');
if (isProxyEnabled && !hasProxy) {
@@ -513,16 +630,19 @@
link.href = link.href.replace("https://gh-proxy.com/", "");
}
});
// Update command display if a version is selected
const activeBtn = document.querySelector("#version-buttons button.active");
if (activeBtn) updateCommand(activeBtn.dataset.version);
activeBtn ? updateCommand(activeBtn.dataset.version) : updateCommand();
}
// GitHub proxy toggle event listener
ghProxyCheckbox.addEventListener("change", () => {
localStorage.setItem("gh-proxy-enabled", ghProxyCheckbox.checked ? "true" : "false");
clearCacheBtn.style.display = ghProxyCheckbox.checked ? "block" : "none";
clearCacheBtn.style.display = ghProxyCheckbox.checked ? "inline-flex" : "none";
updateAllDownloadLinks();
});
// Cache clearing modal functionality
const modal = document.getElementById("urls-pannel");
const modalContent = document.getElementById("modal-content");
clearCacheBtn.addEventListener("click", () => {
@@ -532,22 +652,111 @@
setTimeout(() => { modalContent.classList.add('opacity-100', 'scale-100'); modalContent.classList.remove('opacity-0', 'scale-95'); }, 10);
});
// Modal close functionality
document.getElementById("urls-close").addEventListener("click", () => {
modalContent.classList.remove('opacity-100', 'scale-100');
modalContent.classList.add('opacity-0', 'scale-95');
setTimeout(() => modal.classList.add('hidden'), 200);
});
// Copy URLs and open cache clearing page
document.getElementById("copy-to-clear-cache").addEventListener("click", () => {
navigator.clipboard.writeText(document.getElementById("urls-list").innerText);
window.open("https://cache.gh-proxy.com/", "_blank");
document.getElementById("urls-close").click();
});
// Changelog modal functionality
const changelogModal = document.getElementById("changelog-modal");
const changelogModalContent = document.getElementById("changelog-modal-content");
const changelogContent = document.getElementById("changelog-content");
const changelogClose = document.getElementById("changelog-close");
// Fetch changelog content from MikroTik servers
async function fetchChangelog(version) {
try {
const response = await fetch(`https://upgrade.mikrotik.ltd/routeros/${version}/CHANGELOG`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const text = await response.text();
return text;
} catch (error) {
console.error("Failed to fetch changelog:", error);
return null;
}
}
// Global function to show changelog modal
window.showChangelog = function(version) {
changelogModal.classList.remove('hidden');
changelogContent.innerHTML = `
<div class="flex items-center justify-center py-8">
<span class="iconify text-4xl text-blue-600 animate-spin" data-icon="ph:spinner-gap-bold"></span>
<span class="ml-4 text-lg font-medium" data-i18n="loadingChangelog"></span>
</div>
`;
setLanguage(document.getElementById("lang-switcher").value);
setTimeout(() => {
changelogModalContent.classList.add('opacity-100', 'scale-100');
changelogModalContent.classList.remove('opacity-0', 'scale-95');
}, 10);
// Fetch and display changelog content
fetchChangelog(version).then(content => {
if (content) {
changelogContent.innerHTML = `
<div class="prose prose-slate dark:prose-invert max-w-none">
<pre class="whitespace-pre-wrap text-sm leading-relaxed">${content}</pre>
</div>
`;
} else {
// Show error state with retry button
changelogContent.innerHTML = `
<div class="flex flex-col items-center justify-center py-8 text-center">
<span class="iconify text-6xl text-red-500 mb-4" data-icon="ph:warning-circle-bold"></span>
<p class="text-lg font-medium text-slate-700 dark:text-slate-300 mb-2" data-i18n="changelogError"></p>
<button onclick="showChangelog('${version}')" class="mt-4 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors">
<span class="iconify mr-2" data-icon="ph:arrow-clockwise-bold"></span>
Retry
</button>
</div>
`;
setLanguage(document.getElementById("lang-switcher").value);
}
});
};
// Hide changelog modal with animation
function hideChangelog() {
changelogModalContent.classList.remove('opacity-100', 'scale-100');
changelogModalContent.classList.add('opacity-0', 'scale-95');
setTimeout(() => changelogModal.classList.add('hidden'), 200);
}
changelogClose.addEventListener("click", hideChangelog);
// Close modal when clicking outside
changelogModal.addEventListener("click", (e) => {
if (e.target === changelogModal) {
hideChangelog();
}
});
// Close modal with Escape key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && !changelogModal.classList.contains("hidden")) {
hideChangelog();
}
});
// Command generation and version selection
const versionBtnsContainer = document.getElementById("version-buttons");
const commandDiv = document.getElementById("command");
const copyBtn = document.getElementById("copy-btn");
// Generate version selection buttons
versionBtnsContainer.innerHTML = `
<button data-version="${routeros7_stable}" class="px-3 py-1.5 text-sm font-medium rounded-md hover:bg-blue-200 dark:hover:bg-gray-600 transition-all">v7 Stable</button>
<button data-version="${routeros7_testing}" class="px-3 py-1.5 text-sm font-medium rounded-md hover:bg-blue-200 dark:hover:bg-gray-600 transition-all">v7 Testing</button>
@@ -555,33 +764,43 @@
<button data-version="${routeros6_stable}" class="px-3 py-1.5 text-sm font-medium rounded-md hover:bg-blue-200 dark:hover:bg-gray-600 transition-all">v6 Stable</button>
`;
// Update command display based on selected tool and version
function updateCommand(version) {
const tool = document.querySelector('input[name="tool"]:checked').value;
const proxy = localStorage.getItem("gh-proxy-enabled") === "true" ? " | sed 's#https://github.com#https://gh-proxy.com/https://github.com#g'" : "";
const _version = version===undefined?"": `<span class="text-green-500 font-semibold">VERSION</span>=<span class="text-yellow-400">${version}</span> `
const cmd = tool === "curl"
? `<pre class="text-slate-300"><code><span class="text-pink-400">curl</span> <span class="text-cyan-400">https://mikrotik.ltd/chr.sh</span>${proxy} | <span class="text-pink-400">bash</span> -s <span class="text-green-400">${version}</span></code></pre>`
: `<pre class="text-slate-300"><code><span class="text-pink-400">wget</span> -O - <span class="text-cyan-400">https://mikrotik.ltd/chr.sh</span>${proxy} | <span class="text-pink-400">bash</span> -s <span class="text-green-400">${version}</span></code></pre>`;
? `<pre class="text-slate-300"><code>${_version}<span class="text-blue-400 font-bold">bash</span> <span class="text-pink-400">&lt;(</span><span class="text-blue-400 font-bold">curl</span> <span class="text-teal-400">https://mikrotik.ltd/chr.sh</span><span class="text-pink-400">)</span></code></pre>`
: `<pre class="text-slate-300"><code>${_version}<span class="text-blue-400 font-bold">bash</span> <span class="text-pink-400">&lt;(</span><span class="text-blue-400 font-bold">wget -O - </span><span class="text-teal-400">https://mikrotik.ltd/chr.sh</span><span class="text-pink-400">)</span></code></pre>`;
commandDiv.innerHTML = cmd;
}
// Tool selection event listeners
document.querySelectorAll('input[name="tool"]').forEach(radio => {
radio.addEventListener("change", () => {
const activeBtn = document.querySelector("#version-buttons button.active");
if (activeBtn) updateCommand(activeBtn.dataset.version);
activeBtn ? updateCommand(activeBtn.dataset.version) : updateCommand();
});
});
// Version button event listeners
const buttons = document.querySelectorAll("#version-buttons button");
buttons.forEach(btn => {
btn.addEventListener("click", (e) => {
e.preventDefault();
updateCommand(btn.dataset.version);
buttons.forEach(b => b.classList.remove("active"));
btn.classList.add("active");
if (btn.classList.contains("active")) {
updateCommand();
btn.classList.remove("active");
}else{
updateCommand(btn.dataset.version);
buttons.forEach(b => b.classList.remove("active"));
btn.classList.add("active");
}
});
});
buttons[0].click();
updateCommand();
// Copy command to clipboard functionality
copyBtn.addEventListener("click", () => {
const code = commandDiv.querySelector("code").innerText;
navigator.clipboard.writeText(code).then(() => {

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -328,6 +328,12 @@ def patch_squashfs(path,key_dict):
if _file =='loader':
patch_loader(file)
continue
if _file =='BOOTX64.EFI':
print(f'patch {file} ...')
data = open(file,'rb').read()
data = patch_kernel(data,key_dict)
open(file,'wb').write(data)
continue
data = open(file,'rb').read()
for old_public_key,new_public_key in key_dict.items():
_data = replace_key(old_public_key,new_public_key,data,file)