Compare commits

..

391 Commits

Author SHA1 Message Date
elseif
c4aa017ec2
Simplify Docker run command in README
Updated Docker run command by removing memory, CPU, and disk parameters.

Signed-off-by: elseif <elseif@live.cn>
2026-07-19 19:46:44 +08:00
elseif
ec3fc1d8b0
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2026-07-19 19:46:02 +08:00
elseif
9ff5bc4d49
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2026-07-19 19:43:41 +08:00
elseif
2daa51ba67 modified: README.md 2026-07-18 17:02:34 +08:00
elseif
cebf02d5f6 modified: README.md 2026-07-18 17:00:15 +08:00
elseif
85582304cb modified: README.md 2026-07-18 16:56:24 +08:00
elseif
d4287e92a3 modified: README.md 2026-07-18 16:53:58 +08:00
elseif
4de5fd88c0 modified: README.md 2026-07-18 16:51:51 +08:00
elseif
d970a10e08 modified: README.md 2026-07-18 16:51:07 +08:00
elseif
88b7f61b42 modified: index.html 2026-07-18 16:23:42 +08:00
elseif
630e9be8e6 modified: README.md 2026-07-17 09:13:17 +08:00
elseif
b3398d2341 Merge branch 'main' of github.com:elseif/MikroTikPatch 2026-07-17 09:10:23 +08:00
elseif
7aabc6ffcd modified: README.md 2026-07-17 09:08:16 +08:00
elseif
17b6db1acc modified: README.md
modified:   README.md

Changes not staged for commit:
	modified:   README.md
2026-07-17 09:08:09 +08:00
elseif
a6e8bb3c87 modified: README.md 2026-07-17 09:06:37 +08:00
elseif
50b4c1036e modified: README.md 2026-07-17 09:04:42 +08:00
elseif
5e4e7eead0 deleted: README_EN.md
deleted:    README_PT.md
	deleted:    image/arm.png
	deleted:    image/install.png
	deleted:    image/mips.png
	deleted:    image/renew.png
	deleted:    image/renew_v6.png
	deleted:    image/routeros.png
2026-07-17 09:03:58 +08:00
elseif
37ba5451aa modified: README.md 2026-07-17 09:01:41 +08:00
elseif
e07ec52d34 modified: .github/workflows/build_docker.yml 2026-07-16 14:45:52 +08:00
elseif
bf73ab201b modified: .github/workflows/build_docker.yml 2026-07-16 14:42:38 +08:00
elseif
ae7943de83 modified: .github/workflows/build_docker.yml 2026-07-16 14:26:58 +08:00
elseif
3b42f1c5fb modified: .github/workflows/build_docker.yml 2026-07-16 14:22:52 +08:00
elseif
7b273f0464 modified: .github/workflows/build_docker.yml 2026-07-16 14:01:06 +08:00
elseif
5033270988 modified: .github/workflows/build_docker.yml 2026-07-16 13:54:42 +08:00
elseif
063cd7785f modified: .github/workflows/build_docker.yml 2026-07-16 13:28:07 +08:00
elseif
b628eaadd2 modified: .github/workflows/build_docker.yml 2026-07-16 13:25:36 +08:00
elseif
4d32f80f8b modified: .github/workflows/build_docker.yml 2026-07-16 13:19:44 +08:00
elseif
e576f75398 modified: .github/workflows/build_docker.yml 2026-07-16 13:15:39 +08:00
elseif
c7eba4be0c modified: .github/workflows/build_docker.yml 2026-07-16 13:14:00 +08:00
elseif
78a2720c82 modified: .github/workflows/build_docker.yml 2026-07-16 13:10:22 +08:00
elseif
9cc32db4d2 modified: .github/workflows/build_docker.yml 2026-07-16 12:47:25 +08:00
elseif
4f3f8b5e40 modified: .github/workflows/build_docker.yml 2026-07-16 12:43:14 +08:00
elseif
68e9f1f5c2 modified: .github/workflows/build_docker.yml 2026-07-16 12:14:25 +08:00
elseif
a46286d886 modified: .github/workflows/build_docker.yml 2026-07-16 10:03:43 +08:00
elseif
e44ea1504b modified: .github/workflows/build_docker.yml 2026-07-16 09:59:39 +08:00
elseif
57f2724d69 modified: .github/workflows/build_docker.yml 2026-07-16 09:57:55 +08:00
elseif
672c225645 modified: .github/workflows/build_docker.yml 2026-07-16 09:47:00 +08:00
elseif
ad036099ce modified: .github/workflows/build_docker.yml 2026-07-16 09:42:18 +08:00
elseif
723969a497 modified: .github/workflows/build_docker.yml 2026-07-16 09:31:54 +08:00
elseif
ae84c4ecde modified: .github/workflows/build_docker.yml 2026-07-16 09:12:32 +08:00
elseif
54c562f054
Simplify multi arch manifest creation
Removed unnecessary output of JSON file contents before creating the multi-architecture manifest.

Signed-off-by: elseif <elseif@live.cn>
2026-07-16 07:00:23 +08:00
elseif
ddb8fe368e
Update Docker build workflow to use QEMU setup
Signed-off-by: elseif <elseif@live.cn>
2026-07-16 06:57:58 +08:00
elseif
e4138453e1
Add IMAGE variable for Docker build process
Signed-off-by: elseif <elseif@live.cn>
2026-07-16 06:55:37 +08:00
elseif
db0273d518
Output digests before creating Docker manifest
Add commands to output digests from JSON files before creating a multi-architecture Docker manifest.

Signed-off-by: elseif <elseif@live.cn>
2026-07-16 06:45:05 +08:00
elseif
c4cada441a
Add permissions for package writing in workflow
Signed-off-by: elseif <elseif@live.cn>
2026-07-16 06:33:59 +08:00
elseif
89ea52c7df
Refactor Docker build workflow and update inputs
Signed-off-by: elseif <elseif@live.cn>
2026-07-16 06:26:37 +08:00
elseif
0670bf0818
Add Patch_Development_V7 workflow for version v7
Signed-off-by: elseif <elseif@live.cn>
2026-07-16 03:20:46 +08:00
elseif
ea433daaf8
Add 'development' option to channel input
Signed-off-by: elseif <elseif@live.cn>
2026-07-16 03:20:05 +08:00
elseif
18fd42ae68
Rename jobs for V7 in GitHub Actions workflow
Signed-off-by: elseif <elseif@live.cn>
2026-07-16 01:16:56 +08:00
elseif
2e1198189c
Fix duplicate routeros*.npk entry in patch workflow
Removed duplicate entry for routeros*.npk in file upload section.

Signed-off-by: elseif <elseif@live.cn>
2026-07-16 00:48:44 +08:00
elseif
b8f382bc34
Update RouterOS v6 package details in index.html
Signed-off-by: elseif <elseif@live.cn>
2026-07-16 00:47:17 +08:00
elseif
e7a8f2bffb
Add support for routeros NPK files in workflow
Signed-off-by: elseif <elseif@live.cn>
2026-07-16 00:41:06 +08:00
elseif
79a24e44eb
Add support for routeros NPK files in workflow
Signed-off-by: elseif <elseif@live.cn>
2026-07-16 00:38:47 +08:00
elseif
d546633361 modified: .github/workflows/patch_v7.yml 2026-07-15 18:16:55 +08:00
elseif
d5701f354c modified: .github/workflows/patch_v7.yml 2026-07-15 18:14:41 +08:00
elseif
d90a121e42 modified: .github/workflows/patch_v7.yml 2026-07-15 18:10:15 +08:00
elseif
a7af632dc5 modified: .github/workflows/patch_v7.yml 2026-07-15 18:07:26 +08:00
elseif
21147e34f2 modified: .github/workflows/main.yml 2026-07-15 18:02:53 +08:00
elseif
63f495d811 modified: .github/workflows/patch_v7.yml 2026-07-15 17:59:49 +08:00
elseif
0b3d6862ad modified: .github/workflows/main.yml 2026-07-15 11:00:32 +08:00
elseif
399cc879ed modified: .github/workflows/main.yml 2026-07-15 10:58:51 +08:00
elseif
a5601a7320 modified: .github/workflows/main.yml 2026-07-15 10:55:37 +08:00
elseif
067327e43e modified: .github/workflows/main.yml 2026-07-15 10:54:26 +08:00
elseif
2b6ed9e615 modified: .github/workflows/patch_v6.yml
modified:   .github/workflows/patch_v7.yml
2026-07-15 10:48:02 +08:00
elseif
2750b4502c modified: README.md
modified:   README_EN.md
	modified:   README_PT.md
2026-07-15 09:43:32 +08:00
elseif
30c069ac9e modified: index.html
modified:   README.md
	modified:   README_EN.md
	modified:   README_PT.md
2026-07-15 09:43:20 +08:00
elseif
22fefaf3f4 modified: patch.py 2026-07-15 01:57:33 +08:00
elseif
dc5e2e0ac8 modified: .github/workflows/patch_v6.yml
modified:   .github/workflows/patch_v7.yml
2026-07-15 01:46:19 +08:00
elseif
1f552fcd84 modified: .github/workflows/patch_v6.yml
modified:   .github/workflows/patch_v7.yml
2026-07-15 01:34:24 +08:00
elseif
73c149696f modified: .github/workflows/main.yml
modified:   .github/workflows/patch_v6.yml
	modified:   .github/workflows/patch_v7.yml
2026-07-15 01:25:35 +08:00
elseif
8955a366fb modified: .github/workflows/patch_v6.yml
modified:   .github/workflows/patch_v7.yml
2026-07-15 01:22:31 +08:00
elseif
eb5926ac6f modified: .github/workflows/patch_v6.yml 2026-07-15 01:19:59 +08:00
elseif
5bfb3ce1d0 modified: .github/workflows/patch_v6.yml 2026-07-15 00:56:49 +08:00
elseif
99573e837d modified: .github/workflows/patch_v6.yml 2026-07-15 00:11:04 +08:00
elseif
b797695ea3 modified: .github/workflows/patch_v6.yml 2026-07-15 00:04:01 +08:00
elseif
01aa897fda modified: .github/workflows/patch_v6.yml 2026-07-14 23:51:31 +08:00
elseif
c80e3ddcb4 modified: .github/workflows/patch_v6.yml 2026-07-14 23:18:12 +08:00
elseif
312a340f1f modified: patch.py 2026-07-14 23:04:43 +08:00
elseif
93429aa0d9 modified: .github/workflows/patch_v6.yml 2026-07-14 23:02:45 +08:00
elseif
0d4e77dc05 modified: .github/workflows/patch_v6.yml
modified:   npk.py
2026-07-14 22:56:32 +08:00
elseif
ef8acaa55b modified: patch.py 2026-07-14 22:29:34 +08:00
elseif
20c4031ae6 modified: .github/workflows/patch_v6.yml 2026-07-14 22:12:59 +08:00
elseif
fd52dcf9ec modified: .github/workflows/patch_v6.yml 2026-07-14 21:51:31 +08:00
elseif
8eaa652e3c modified: .github/workflows/patch_v6.yml 2026-07-14 21:15:28 +08:00
elseif
1f76f9ea4c modified: .github/workflows/patch_v6.yml 2026-07-14 21:08:29 +08:00
elseif
b782e4bd65 modified: .github/workflows/patch_v6.yml 2026-07-14 21:01:17 +08:00
elseif
2e6862590e modified: .github/workflows/patch_v7.yml 2026-07-14 20:56:28 +08:00
elseif
7b6f03adc5 modified: .github/workflows/patch_v6.yml
modified:   .github/workflows/patch_v7.yml
2026-07-14 20:42:28 +08:00
elseif
a9b8746917 modified: .github/workflows/patch_v7.yml 2026-07-14 20:40:04 +08:00
elseif
3a6289c70a modified: .github/workflows/patch_v6.yml
modified:   .github/workflows/patch_v7.yml
2026-07-14 20:33:28 +08:00
elseif
68a45aeae2 modified: patch.py 2026-07-14 20:28:10 +08:00
elseif
ecf7d9b5ea modified: .github/workflows/patch_v6.yml
modified:   .github/workflows/patch_v7.yml
2026-07-14 19:55:47 +08:00
elseif
74a89f9af6 deleted: .github/workflows/mikrotik_patch_6.yml
deleted:    .github/workflows/mikrotik_patch_7.yml
	modified:   README.md
	modified:   README_EN.md
	modified:   README_PT.md
2026-07-14 19:49:29 +08:00
elseif
d95c1523f1 modified: .github/workflows/main.yml
renamed:    .github/workflows/build_v6.yml -> .github/workflows/patch_v6.yml
	renamed:    .github/workflows/build_v7.yml -> .github/workflows/patch_v7.yml
2026-07-14 18:26:29 +08:00
elseif
b7ef8c8304 new file: .github/workflows/build_v6.yml
modified:   .github/workflows/main.yml
	modified:   .gitignore
2026-07-14 18:19:22 +08:00
elseif
c8ca24f00f modified: .github/workflows/build_v7.yml 2026-07-14 16:13:12 +08:00
elseif
6014d957f4 modified: .github/workflows/build_v7.yml
modified:   toyecc/ShortWeierstrassCurve.py
2026-07-14 16:05:39 +08:00
elseif
f9d2554725 modified: .github/workflows/build_v7.yml
deleted:    package.py
	modified:   patch.py
2026-07-14 16:02:44 +08:00
elseif
fbdba025ba modified: patch.py 2026-07-14 15:55:04 +08:00
elseif
e01697e0bf modified: .github/workflows/build_v7.yml
modified:   patch.py
2026-07-14 15:53:35 +08:00
elseif
1bc1d82048 modified: .github/workflows/build_v7.yml 2026-07-14 15:37:36 +08:00
elseif
cdc8f8df65 renamed: keygen/keygen_aarch64 -> keygen/keygen_arm64 2026-07-14 15:30:18 +08:00
elseif
dcfa022145 modified: .github/workflows/build_v7.yml 2026-07-14 15:28:34 +08:00
elseif
ab2e4463b6 modified: .github/workflows/build_v7.yml 2026-07-14 14:58:32 +08:00
elseif
43a30fddb0 new file: .github/workflows/build_v7.yml
new file:   .github/workflows/main.yml
	modified:   .github/workflows/mikrotik_patch_7.yml
	renamed:    busybox/busybox_aarch64 -> busybox/busybox_arm64
	modified:   npk.py
	modified:   patch.py
2026-07-14 14:52:29 +08:00
elseif
d0dc0620f9 modified: patch.py 2026-07-14 13:59:01 +08:00
elseif
55783f7975 modified: chr.sh 2026-07-10 11:39:29 +08:00
elseif
81e4c63ceb modified: .github/workflows/mikrotik_patch_7.yml 2026-07-09 16:38:13 +08:00
elseif
c756852094 modified: chr.sh 2026-07-09 12:37:54 +08:00
elseif
b7ee8aaa1a modified: chr.sh 2026-07-09 12:33:14 +08:00
elseif
1a5647bd98 modified: chr.sh 2026-07-09 12:31:45 +08:00
elseif
60709c4fd6 modified: README.md
modified:   README_EN.md
	modified:   README_PT.md
	deleted:    image/chr.png
	deleted:    image/x86.png
2026-07-09 12:28:10 +08:00
elseif
91f4eed086 modified: chr.sh 2026-07-09 11:59:57 +08:00
elseif
497ed531f3 modified: chr.sh 2026-07-09 11:45:01 +08:00
elseif
3bde30196c modified: chr.sh 2026-07-09 11:39:52 +08:00
elseif
8991f41f0b modified: chr.sh 2026-07-09 11:37:47 +08:00
elseif
dc20f46a94 modified: chr.sh 2026-07-09 11:33:26 +08:00
elseif
447f7990ae modified: chr.sh 2026-07-09 11:19:43 +08:00
elseif
969c4fb369 modified: README.md
modified:   README_EN.md
2026-07-01 12:52:02 +08:00
elseif
bbf8ed23e3 modified: .github/workflows/mikrotik_patch_7.yml 2026-06-28 05:22:27 +08:00
elseif
7039d76825 update busybox to v1.38.0 2026-06-27 10:52:21 +08:00
elseif
5e9340c741 modified: .github/workflows/mikrotik_patch_7.yml 2026-06-27 10:27:23 +08:00
elseif
ac2cfdeafd modified: .github/workflows/mikrotik_patch_7.yml 2026-06-27 01:17:47 +08:00
elseif
e728aec84c modified: .github/workflows/mikrotik_patch_7.yml 2026-06-27 01:15:51 +08:00
elseif
1d9971b387 modified: .github/workflows/mikrotik_patch_7.yml 2026-06-27 01:13:22 +08:00
elseif
1cfb6dba3d modified: .github/workflows/mikrotik_patch_7.yml 2026-06-27 01:10:16 +08:00
elseif
ba0ffe3cd7 modified: README.md 2026-05-26 06:55:31 +08:00
elseif
7cf469dc0c modified: .github/workflows/mikrotik_patch_7.yml 2026-05-25 09:34:19 +08:00
elseif
30d6ac5fe6 modified: .github/workflows/mikrotik_patch_7.yml
modified:   patch.py
2026-05-19 23:52:12 +08:00
elseif
7fd1b6c563 modified: .github/workflows/mikrotik_patch_7.yml 2026-05-19 23:17:13 +08:00
elseif
ed3dcd0ab1 modified: patch.py 2026-05-19 23:06:47 +08:00
elseif
0a270a1108
Upgrade actions/cache and action-gh-release versions
Signed-off-by: elseif <elseif@live.cn>
2026-04-20 16:00:24 +08:00
elseif
7a6fdb6323
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2026-04-18 01:09:01 +08:00
elseif
44bc4346c6
Merge pull request #185 from LucasStone98/main
Fix sourceforge link for building images
2026-04-18 01:07:38 +08:00
Daniel Samuels
98d6af79f7
Update mikrotik_patch_7.yml
Fix sourceforge link for refind

Signed-off-by: Daniel Samuels <47313460+LucasStone98@users.noreply.github.com>
2026-04-17 12:41:21 +02:00
elseif
11eec19e7d
Upgrade GitHub Actions to checkout@v6 and setup-python@v6
Signed-off-by: elseif <elseif@live.cn>
2026-04-02 10:28:05 +08:00
elseif
9e1d7a5239
Upgrade GitHub Actions to checkout@v6 and setup-python@v6
Signed-off-by: elseif <elseif@live.cn>
2026-04-02 10:27:38 +08:00
elseif
569e0b5749
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2026-03-30 08:29:36 +08:00
elseif
5d9cc6d745
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2026-03-30 08:09:57 +08:00
elseif
0f62f5de4e
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2026-03-30 07:55:25 +08:00
elseif
7f2120dca8
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2026-03-30 04:06:04 +08:00
elseif
9be7cdc6ec
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2026-03-30 04:01:33 +08:00
elseif
b7eccac7c5
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2026-03-30 03:58:29 +08:00
elseif
e0afc29cca
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2026-03-30 03:53:34 +08:00
elseif
98dfe256e6
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2026-03-30 03:50:34 +08:00
elseif
f179b74d3e
Update wget command to include no-check-certificate option
Signed-off-by: elseif <elseif@live.cn>
2026-03-25 13:06:51 +08:00
elseif
c71a8ff9c1
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2026-01-10 12:42:37 +08:00
elseif
b35ce475ab
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2026-01-10 12:29:19 +08:00
elseif
976e562e83
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2026-01-10 11:41:38 +08:00
elseif
86e11b647d
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-10-22 14:12:58 +08:00
elseif
07c6617805
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-10-22 14:09:03 +08:00
elseif
bf2865921a
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-10-21 18:14:25 +08:00
elseif
604631539c
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-10-21 17:53:20 +08:00
elseif
fc7bb4f206
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-10-21 17:51:22 +08:00
elseif
410c19d2ce
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-10-09 11:25:48 +08:00
elseif
d5eea3df4b
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-29 11:22:03 +08:00
elseif
73559ce979
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-28 00:21:06 +08:00
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
elseif
e7a08895f5
Update mikrotik_patch_6.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-18 23:35:16 +08:00
elseif
b436141665
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-18 23:29:20 +08:00
elseif
86e2727c56
Update mikrotik_patch_6.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-18 23:15:40 +08:00
elseif
96670cbe60
Update npk.py
Signed-off-by: elseif <elseif@live.cn>
2025-09-18 23:04:30 +08:00
elseif
f91f8e15d0
Merge pull request #145 from FakeErrorX/main
Redesign index.html
2025-09-18 16:49:01 +08:00
elseif
c4dd46205b
Update mikrotik_patch_6.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-18 16:35:11 +08:00
elseif
4f9d2133fd
Update npk.py
Signed-off-by: elseif <elseif@live.cn>
2025-09-18 16:33:57 +08:00
ErrorX
b6721ffd7d Redesign index.html
Replaces legacy CSS and table-based layout with modernizes the UI with responsive cards, details/summary accordions, and improved theme/language switching. Adds dynamic loading, new download tables, and enhanced accessibility. Removes old inline styles and scripts, streamlining the code for maintainability and better user experience.
2025-09-16 15:08:43 +06:00
elseif
6cbdbffbcb
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-09-13 03:43:55 +08:00
elseif
6d27075389
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-13 03:39:43 +08:00
elseif
9b92fc3fa6
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-11 13:21:58 +08:00
elseif
0c92a04b58
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-11 13:21:30 +08:00
elseif
296db5b558
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-11 13:20:35 +08:00
elseif
49bbcb1983
Update mikrotik_patch_6.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-11 10:04:24 +08:00
elseif
f750442687
Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-09-11 09:45:56 +08:00
elseif
06bd69f331
Delete keygen/keygen_aarch64
Signed-off-by: elseif <elseif@live.cn>
2025-09-11 09:45:42 +08:00
elseif
e35cf5f74e
Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-09-11 09:45:27 +08:00
elseif
e12e1aa67b
Delete keygen/keygen_arm
Signed-off-by: elseif <elseif@live.cn>
2025-09-11 09:45:06 +08:00
elseif
6a8aaa89d8
Delete keygen/keygen_x86
Signed-off-by: elseif <elseif@live.cn>
2025-09-11 09:44:53 +08:00
elseif
495927d0de
Update mikrotik_patch_6.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-11 08:57:23 +08:00
elseif
c73c6bacc8
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-11 08:56:31 +08:00
elseif
8e7134e0a6
Update mikrotik_patch_6.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-10 17:27:26 +08:00
elseif
f4cc728789
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-10 17:26:58 +08:00
elseif
8c3278f477
Update npk.py
Signed-off-by: elseif <elseif@live.cn>
2025-09-10 17:07:20 +08:00
elseif
04a831b463
Update mikrotik_patch_6.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-10 16:30:01 +08:00
elseif
c75dd48261
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-10 15:36:11 +08:00
elseif
6519ab2e12
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-10 15:26:20 +08:00
elseif
ec1c0f1719
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-10 15:16:03 +08:00
elseif
3de34ef556
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-10 15:09:47 +08:00
elseif
8499e4591a
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-10 12:23:18 +08:00
elseif
f520ff799c
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-10 12:12:45 +08:00
elseif
ccb4220a61
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-10 12:10:02 +08:00
elseif
8159018e3c
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-09-10 12:09:07 +08:00
elseif
dc30d89afb
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-10 07:47:39 +08:00
elseif
4472649719
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-08 10:23:13 +08:00
elseif
fc469087be
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-08 10:22:15 +08:00
elseif
477fd66fe0
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-08 10:04:22 +08:00
elseif
cd32348c52
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-09-08 08:37:29 +08:00
elseif
37f92c3def
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-01 01:39:38 +08:00
elseif
2bb452b300
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-01 00:16:44 +08:00
elseif
710921ef23
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-01 00:13:21 +08:00
elseif
8bfcdfe5a8
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-01 00:12:51 +08:00
elseif
2baf264aec
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-01 00:11:15 +08:00
elseif
fd6f36d484
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-01 00:08:06 +08:00
elseif
2ab01e5328
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-09-01 00:06:52 +08:00
elseif
16854dadf9
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-08-31 23:56:16 +08:00
elseif
d981a4b137
Delete keygen/tmp
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 21:53:58 +08:00
elseif
378c9009b8
Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 21:53:34 +08:00
elseif
73f9ba0506
Create tmp
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 21:52:28 +08:00
elseif
646f479290
Delete keygen/keygen_x86
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 21:49:53 +08:00
elseif
e3b46ed7c3
Delete keygen/keygen_arm
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 21:49:41 +08:00
elseif
1a66685cc4
Delete keygen/keygen_aarch64
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 21:49:29 +08:00
elseif
cbd6ffa50c
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 19:38:57 +08:00
elseif
73d90b27e0
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 19:31:24 +08:00
elseif
60728d6fe2
Update index.html
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 19:27:34 +08:00
elseif
556cc81474
Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 18:35:01 +08:00
elseif
2286063b65
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 17:29:36 +08:00
elseif
907b335d3a
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 17:29:10 +08:00
elseif
567819bec4
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 17:19:41 +08:00
elseif
bbc6b854cd
Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 16:53:43 +08:00
elseif
6f4c9b43fc
Delete keygen/keygen_arm
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 16:52:57 +08:00
elseif
659de9d659
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 16:34:23 +08:00
elseif
78707050a0
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 16:31:09 +08:00
elseif
cdb378b45d
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 16:25:09 +08:00
elseif
9b40d04e38
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 16:17:43 +08:00
elseif
110cda779c
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 16:12:13 +08:00
elseif
36be15859e
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-27 15:56:02 +08:00
elseif
c40954115b
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-26 17:22:44 +08:00
elseif
2babd89f27
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-26 17:19:46 +08:00
elseif
7a6e538834
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-26 14:10:29 +08:00
elseif
a2f9af37ad
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-26 14:05:54 +08:00
elseif
6e630b852a
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-26 00:49:44 +08:00
elseif
4987501168
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-26 00:35:49 +08:00
elseif
c2ec6d439c
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-26 00:27:00 +08:00
elseif
c5712825ba
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-26 00:22:40 +08:00
elseif
072417bf1b
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-26 00:17:09 +08:00
elseif
f2d18e32f6
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-26 00:10:04 +08:00
elseif
7758330905
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-25 22:25:45 +08:00
elseif
0ff47ed53e
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-25 22:24:17 +08:00
elseif
90dfb10d32
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-25 22:21:53 +08:00
elseif
a217ae8df0
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-25 22:18:07 +08:00
elseif
fa9c53bef1
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-25 22:07:27 +08:00
elseif
18b6449701
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-25 22:05:07 +08:00
elseif
d7517cb9f4
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-25 21:55:55 +08:00
elseif
3a17176568
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-25 21:54:18 +08:00
elseif
da3653fcb7
更新 mikrotik_patch_7.yml 2025-08-25 15:21:45 +08:00
elseif
48bd5e9601
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-25 14:26:08 +08:00
elseif
c0f53a242a
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-25 13:05:25 +08:00
elseif
8d18551f5b
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-25 12:54:44 +08:00
elseif
f29d91d1a4
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-25 12:39:16 +08:00
elseif
89c13ae6fa
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-25 12:34:02 +08:00
elseif
e028fdd880
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 05:14:47 +08:00
elseif
88ff1f7130
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 05:07:35 +08:00
elseif
92f26c270e
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 04:59:06 +08:00
elseif
c1163894f3
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 04:45:03 +08:00
elseif
5196f20043
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 04:43:38 +08:00
elseif
bba676a2d6
Rename chr_busybox.sh to chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 04:40:23 +08:00
elseif
259051906c
Update chr_busybox.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 04:40:09 +08:00
elseif
2162e76e64
Update chr_busybox.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 04:19:30 +08:00
elseif
7c22877423
Update chr_busybox.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 04:14:22 +08:00
elseif
6a486adfc6
Update chr_busybox.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 04:11:51 +08:00
elseif
49608606f2
Update and rename chr.sh to chr_busybox.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 04:08:09 +08:00
elseif
85a2526bcd
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 03:58:06 +08:00
elseif
b5049c0ebb
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 03:56:41 +08:00
elseif
15273578e8
Update chr.sh
Signed-off-by: elseif <elseif@live.cn>
2025-08-23 03:31:43 +08:00
elseif
a85cbf1548
Update npk.py
Signed-off-by: elseif <elseif@live.cn>
2025-08-22 00:26:03 +08:00
elseif
7195dae015
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-21 21:47:12 +08:00
elseif
5ef845c148
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-21 21:35:32 +08:00
elseif
00a510fc1e
Update npk.py
Signed-off-by: elseif <elseif@live.cn>
2025-08-21 21:01:44 +08:00
elseif
3d491261f3
Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-08-21 20:58:57 +08:00
elseif
4ba3cc3f11
Delete keygen/keygen_x86
Signed-off-by: elseif <elseif@live.cn>
2025-08-21 20:57:56 +08:00
elseif
b5904ff5ae
Delete keygen/keygen_aarch64
Signed-off-by: elseif <elseif@live.cn>
2025-08-21 20:57:46 +08:00
elseif
5a8e9d19b3
Update npk.py
Signed-off-by: elseif <elseif@live.cn>
2025-08-21 20:56:41 +08:00
elseif
3c5ed64380
Update npk.py
Signed-off-by: elseif <elseif@live.cn>
2025-08-21 20:52:04 +08:00
elseif
808b637ebd
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-19 17:35:33 +08:00
elseif
ced9e23040
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-19 16:40:31 +08:00
elseif
4623e18824
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-19 16:39:59 +08:00
elseif
87e7644916
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-19 16:39:34 +08:00
elseif
40676b67af
Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-08-19 16:10:20 +08:00
elseif
e674ef263a
Delete keygen/keygen_x86
Signed-off-by: elseif <elseif@live.cn>
2025-08-19 16:09:39 +08:00
elseif
9cd8640e0a
Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-08-19 15:55:17 +08:00
elseif
fcf7fddd2e
Delete keygen/keygen_x86
Signed-off-by: elseif <elseif@live.cn>
2025-08-19 15:54:36 +08:00
elseif
bdb3de0777
Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-08-19 07:36:05 +08:00
elseif
923225902b
Delete keygen/keygen_x86
Signed-off-by: elseif <elseif@live.cn>
2025-08-19 07:35:48 +08:00
elseif
3b4391d582
Delete keygen/rc.local
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 19:04:13 +08:00
elseif
6c6899386b
Update rc.local
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 19:03:54 +08:00
elseif
5220cb9d8e
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 19:02:20 +08:00
elseif
cfe551fdda
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 18:59:43 +08:00
elseif
241c81356b
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 17:13:20 +08:00
elseif
9cc2f9063d
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 17:08:29 +08:00
elseif
399c670504
Create rc.local
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 15:40:09 +08:00
elseif
ee6d299e9f
Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 15:38:31 +08:00
elseif
0234cf6f75
Delete busybox/busybox_x86
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 15:38:09 +08:00
elseif
9df0184656
Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 15:37:46 +08:00
elseif
5623d2215c
Delete keygen_x86
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 15:37:17 +08:00
elseif
7239cd4cdf
Add files via upload
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 15:36:27 +08:00
elseif
b60cab8b45
Delete keygen/keygen_x86
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 15:34:19 +08:00
elseif
e8b9bef9aa
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-18 15:31:09 +08:00
elseif
dc959f9b16
Update mikrotik_patch_6.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 21:19:16 +08:00
elseif
2ea2d3fbdf
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 20:46:46 +08:00
elseif
3d3f3fee33
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 20:46:03 +08:00
elseif
8d411a43c6
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 20:44:59 +08:00
elseif
d483639d46
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 20:44:47 +08:00
elseif
4d09aa8aab
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 20:41:00 +08:00
elseif
8f59b90921
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 20:40:34 +08:00
elseif
5d08e5308d
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 19:31:22 +08:00
elseif
18e256ff41
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 19:30:34 +08:00
elseif
855fbc85d1
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 19:30:00 +08:00
elseif
9715f13b6a
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 19:14:57 +08:00
elseif
b2893b91b7
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 18:57:48 +08:00
elseif
988ded50c3
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 18:56:30 +08:00
elseif
950505e0ab
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 18:50:20 +08:00
elseif
96c0fd251e
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 18:49:17 +08:00
elseif
d4dcf6dfeb
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 18:48:04 +08:00
elseif
65b7c57616
Update mikrotik_patch_6.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 14:15:12 +08:00
elseif
edb40667ef
Update mikrotik_patch_6.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 14:04:49 +08:00
elseif
f98f599cb7
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 07:08:18 +08:00
elseif
f28acb89ae
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 07:05:08 +08:00
elseif
259fb4bf2a
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-08-13 06:54:12 +08:00
elseif
8f15749897
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-07-29 11:52:44 +08:00
elseif
9712d7d464
Update CODE_OF_CONDUCT.md
Signed-off-by: elseif <elseif@live.cn>
2025-07-28 20:59:12 +08:00
elseif
7bebf605a0
Update LICENSE
Signed-off-by: elseif <elseif@live.cn>
2025-07-28 20:58:30 +08:00
elseif
f018e0de99
Update LICENSE
Signed-off-by: elseif <elseif@live.cn>
2025-07-28 20:58:05 +08:00
elseif
7ef69a1e31
Update README_PT.md
Signed-off-by: elseif <elseif@live.cn>
2025-07-28 20:56:07 +08:00
elseif
1ba7057fc7
Update README_EN.md
Signed-off-by: elseif <elseif@live.cn>
2025-07-28 20:55:53 +08:00
elseif
8a52ef8df4
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-07-28 20:55:38 +08:00
elseif
7292ad7a80
Update README_PT.md
Signed-off-by: elseif <elseif@live.cn>
2025-07-28 20:47:06 +08:00
elseif
88de7162b7
Update README_EN.md
Signed-off-by: elseif <elseif@live.cn>
2025-07-28 20:46:51 +08:00
elseif
f3497a9a4b
Update README.md
Signed-off-by: elseif <elseif@live.cn>
2025-07-28 20:46:34 +08:00
elseif
702efdb828
Update CODE_OF_CONDUCT.md
Signed-off-by: elseif <elseif@live.cn>
2025-07-28 20:43:37 +08:00
elseif
a2038ece24
Update CODE_OF_CONDUCT.md
Signed-off-by: elseif <elseif@live.cn>
2025-07-28 20:40:04 +08:00
elseif
0b3fd16ba7
Update LICENSE
Signed-off-by: elseif <elseif@live.cn>
2025-07-28 20:35:14 +08:00
elseif
8367df6ef0
Update mikrotik_patch_7.yml
Signed-off-by: elseif <elseif@live.cn>
2025-07-17 22:07:56 +08:00
34 changed files with 3364 additions and 1989 deletions

246
.github/workflows/build_docker.yml vendored Normal file
View File

@ -0,0 +1,246 @@
name: Build CHR Docker
on:
workflow_dispatch:
inputs:
version:
description: "Specify version (e.g., 7.19.2), empty for latest"
required: false
default: ''
type: string
permissions:
packages: write
jobs:
Prepare:
runs-on: ubuntu-24.04
outputs:
MATRIX: ${{ steps.get_versions.outputs.MATRIX }}
steps:
- name: Get versions and generate matrix
id: get_versions
run: |
VERSION="${{ inputs.version }}"
if [ -n "$VERSION" ]; then
MATRIX=$(jq -c -n '
[
{ "version": $VERSION, "channel": ""}
]
' --arg VERSION "$VERSION")
else
TIMESTAMP=$(date +%s)
LONG_TERM=$(wget -nv -O - "https://upgrade.mikrotik.ltd/routeros/NEWESTa7.long-term?t=$TIMESTAMP" | cut -d ' ' -f1)
STABLE=$(wget -nv -O - "https://upgrade.mikrotik.ltd/routeros/NEWESTa7.stable?t=$TIMESTAMP" | cut -d ' ' -f1)
echo "Long-term version: $LONG_TERM"
echo "Stable version: $STABLE"
if [ -z "$LONG_TERM" ]; then
echo "Failed to get long-term version"
exit 1
fi
if [ -z "$STABLE" ]; then
echo "Failed to get stable version"
exit 1
fi
MATRIX=$(jq -c -n '
[
{version: $LONG_TERM, channel: "long-term"},
{version: $STABLE, channel: "stable"}
]
' --arg LONG_TERM "$LONG_TERM" --arg STABLE "$STABLE")
fi
echo "Generated matrix: $MATRIX"
echo "MATRIX=$MATRIX" >> $GITHUB_OUTPUT
Build:
runs-on: ubuntu-24.04
needs: Prepare
strategy:
max-parallel: 1
matrix:
include: ${{ fromJson(needs.Prepare.outputs.MATRIX) }}
env:
TZ: 'Asia/Shanghai'
VERSION: ${{ matrix.version }}
CHANNEL: ${{ matrix.channel }}
IMAGE: "ghcr.io/${{ github.repository_owner }}/chr"
DOCKER_BUILD_SUMMARY: 'false'
DOCKER_BUILD_RECORD_UPLOAD: 'false'
steps:
- name: Show build info
run: |
echo "Version: ${{ env.VERSION }}"
echo "Channel: ${{ env.CHANNEL }}"
- name: Download CHR image
run: |
# x86 架构
mkdir -p "amd64"
wget -nv -O "amd64/chr-$VERSION.img.zip" \
"https://github.com/elseif/MikroTikPatch/releases/download/$VERSION/chr-$VERSION.img.zip"
unzip "amd64/chr-$VERSION.img.zip" -d "amd64/"
rm "amd64/chr-$VERSION.img.zip"
# arm64 架构
mkdir -p "arm64"
wget -nv -O "arm64/chr-$VERSION-arm64.img.zip" \
"https://github.com/elseif/MikroTikPatch/releases/download/$VERSION-arm64/chr-$VERSION-arm64.img.zip"
unzip "arm64/chr-$VERSION-arm64.img.zip" -d "arm64/"
rm "arm64/chr-$VERSION-arm64.img.zip"
ls -la amd64/ arm64/
- name: Create start.sh
run: |
cat <<'EOF' > start.sh
#!/bin/sh
set -e
MEM="${MEM:=512m}"
DISK="${DISK:=128M}"
CPUS="${CPUS:=1}"
bridge_index=0
get_mac_address() {
ifname="$1"
ip link show "$ifname" 2>/dev/null | awk '/link\/ether/ {print $2}'
}
increment_mac() {
mac=$1
mac_no_colons=$(echo "$mac" | tr -d ':' | tr '[:lower:]' '[:upper:]')
mac_dec=$((16#${mac_no_colons}))
mac_dec=$((mac_dec + 1))
mac_hex=$(printf "%012X" "$mac_dec")
echo "$mac_hex" | sed 's/.\{2\}/&:/g;s/:$//'
}
echo "" > /etc/qemu/bridge.conf
qemu="qemu-system-x86_64"
if [[ `uname -m` == "aarch64" ]]; then
qemu="qemu-system-aarch64 -pflash /usr/share/qemu/edk2-aarch64-code.fd -M virt"
fi
if [ -e /dev/kvm ]; then
qemu="$qemu -enable-kvm -cpu host"
else
if [[ `uname -m` == "aarch64" ]]; then
qemu="$qemu -cpu cortex-a57"
fi
fi
img=`ls /diskimage/*.img`
qemu="$qemu -nographic -m $MEM -smp cpus=$CPUS -drive file=$img,format=raw"
truncate -s $DISK $img
while read -r line; do
iface=$(echo "$line" | sed "s/ *\([a-z0-9]*\):.*/\1/")
if [[ "$iface" =~ " " ]]; then
continue
fi
if [[ "$iface" =~ ^br ]]; then
continue
fi
if [[ "$iface" == "lo" ]]; then
continue
fi
if [[ "$iface" == "" ]]; then
continue
fi
echo "iface: $iface"
bridge="br$bridge_index"
ip link add name "$bridge" type bridge
ip link set dev "$bridge" up
ip link set "$iface" master "$bridge"
echo "allow $bridge" >> /etc/qemu/bridge.conf
veth_mac=$(get_mac_address "$iface")
tap_mac=$(increment_mac "$veth_mac")
bridge_index=$((bridge_index + 1))
qemu="$qemu -netdev bridge,id=$bridge,br=$bridge -device virtio-net,netdev=$bridge,mac=$tap_mac "
done < /proc/net/dev
echo "qemu: $qemu"
exec $qemu
EOF
chmod +x start.sh
- name: Create Dockerfile
run: |
cat > Dockerfile << 'EOF'
FROM alpine:latest
ARG TARGETARCH
ARG VERSION
RUN case "$TARGETARCH" in \
amd64) apk add --no-cache qemu-system-x86_64 ;; \
arm64) apk add --no-cache qemu-system-aarch64 ;; \
esac \
&& rm -rf /var/cache/apk/*
RUN mkdir -p /diskimage
COPY ${TARGETARCH}/*.img /diskimage/
COPY start.sh /start.sh
ENTRYPOINT ["/start.sh"]
EOF
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build and Push
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.IMAGE }}:${{ env.VERSION }}
build-args: VERSION=${{ env.VERSION }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: false
sbom: false
outputs: >
type=image,
annotation-index.org.opencontainers.image.title=MikroTik CHR RouterOS,
annotation-index.org.opencontainers.image.description=MikroTik CHR (Cloud Hosted Router) running on QEMU with Docker,
annotation-index.org.opencontainers.image.version=${{ env.VERSION }},
annotation-index.org.opencontainers.image.url=https://mikrotik.ltd
- name: Update channel tags
if: env.CHANNEL != ''
run: |
TAGS="-t ${{ env.IMAGE }}:${{ env.CHANNEL }}"
if [ "${{ env.CHANNEL }}" = "stable" ]; then
TAGS="$TAGS -t ${{ env.IMAGE }}:latest"
fi
echo "Creating tags: $TAGS"
docker buildx imagetools create $TAGS ${{ env.IMAGE }}:${{ env.VERSION }}
- name: Save Docker Image as tar
run: |
mkdir -p artifacts
echo "Exporting amd64 tar..."
docker pull --platform linux/amd64 ${{ env.IMAGE }}:${{ env.VERSION }}
docker save ${{ env.IMAGE }}:${{ env.VERSION }} -o artifacts/chr-${{ env.VERSION }}-amd64.tar
echo "Exporting arm64 tar..."
docker pull --platform linux/arm64 ${{ env.IMAGE }}:${{ env.VERSION }}
docker save ${{ env.IMAGE }}:${{ env.VERSION }} -o artifacts/chr-${{ env.VERSION }}-arm64.tar
ls -lh artifacts/
- name: Upload tar artifact
uses: actions/upload-artifact@v7
with:
name: chr-${{ env.VERSION }}-docker-images
path: artifacts/
retention-days: 30

76
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,76 @@
name: Patch Mikrotik RouterOS
on:
schedule:
- cron: "00 10 * * *" # At UTC 10:00 (Beijing 18:00) on every day
workflow_dispatch:
inputs:
version:
description: 'Select Version (v6/v7/all)'
required: true
default: 'all'
type: choice
options:
- all
- v6
- v7
jobs:
Patch_Stable_V7:
if: |
(github.event_name == 'schedule') ||
(github.event_name == 'workflow_dispatch' && (github.event.inputs.version == 'all' || github.event.inputs.version == 'v7'))
uses: ./.github/workflows/patch_v7.yml
with:
channel: stable
release: true
secrets: inherit
Patch_LongTerm_V7:
if: |
(github.event_name == 'schedule') ||
(github.event_name == 'workflow_dispatch' && (github.event.inputs.version == 'all' || github.event.inputs.version == 'v7'))
uses: ./.github/workflows/patch_v7.yml
with:
channel: long-term
release: true
secrets: inherit
Patch_Testing_V7:
if: |
(github.event_name == 'schedule') ||
(github.event_name == 'workflow_dispatch' && (github.event.inputs.version == 'all' || github.event.inputs.version == 'v7'))
uses: ./.github/workflows/patch_v7.yml
with:
channel: testing
release: true
secrets: inherit
Patch_Development_V7:
if: |
(github.event_name == 'schedule') ||
(github.event_name == 'workflow_dispatch' && (github.event.inputs.version == 'all' || github.event.inputs.version == 'v7'))
uses: ./.github/workflows/patch_v7.yml
with:
channel: development
release: true
secrets: inherit
Patch_Stable_V6:
if: |
(github.event_name == 'schedule') ||
(github.event_name == 'workflow_dispatch' && (github.event.inputs.version == 'all' || github.event.inputs.version == 'v6'))
uses: ./.github/workflows/patch_v6.yml
with:
channel: stable
release: true
secrets: inherit
Patch_LongTerm_V6:
if: |
(github.event_name == 'schedule') ||
(github.event_name == 'workflow_dispatch' && (github.event.inputs.version == 'all' || github.event.inputs.version == 'v6'))
uses: ./.github/workflows/patch_v6.yml
with:
channel: long-term
release: true
secrets: inherit

View File

@ -1,359 +0,0 @@
name: Patch Mikrotik RouterOS 6.x
on:
# push:
# branches: [ "main" ]
# schedule:
# - cron: "0 0 * * *"
workflow_dispatch:
inputs:
channel:
description: 'Channel (stable, long-term)'
required: true
default: 'stable'
type: choice
options:
- stable
- long-term
version:
description: "Version ('', 6.49.17, 6.49.18,...)"
required: false
default: ''
type: string
buildtime:
description: "Build Time"
required: false
default: ''
type: string
permissions:
contents: write
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 }}
jobs:
Set_BuildTime:
runs-on: ubuntu-22.04
outputs:
BUILD_TIME: ${{ steps.set_buildtime.outputs.BUILD_TIME }}
steps:
- name: Set build time
id: set_buildtime
run: |
_BUILD_TIME=${{ github.event.inputs.buildtime }}
if [ -z "$_BUILD_TIME" ]; then
echo "BUILD_TIME=$(date +'%s')" >> $GITHUB_OUTPUT
else
echo "BUILD_TIME=$_BUILD_TIME" >> $GITHUB_OUTPUT
fi
Patch_RouterOS:
needs: Set_BuildTime
runs-on: ubuntu-22.04
strategy:
matrix:
channel: ${{ fromJSON(format('["{0}"]', github.event.inputs.channel || 'stable')) }}
env:
TZ: 'Asia/Shanghai'
LATEST_VERSION: ""
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Get latest routeros version
id: get_latest
run: |
echo $(uname -a)
LATEST_VERSION=${{ github.event.inputs.version }}
if [ -z "$_LATEST_VERSION" ]; then
LATEST_VERSION=$(wget -nv -O - https://${{ env.MIKRO_UPGRADE_URL }}/routeros/NEWEST6.${{ matrix.channel }} | cut -d ' ' -f1)
fi
echo Latest Version:$LATEST_VERSION
if [ "${{ github.event_name }}" == "schedule" ]; then
_LATEST_VERSION=$(wget -nv -O - https://${{ env.CUSTOM_UPGRADE_URL }}/routeros/NEWEST6.${{ matrix.channel }} | cut -d ' ' -f1)
if [ "$_LATEST_VERSION" == "$LATEST_VERSION" ]; then
echo "No new version found"
echo "has_new_version=false" >> $GITHUB_OUTPUT
exit 0
fi
fi
echo "has_new_version=true" >> $GITHUB_OUTPUT
BUILD_TIME=${{ needs.Set_BuildTime.outputs.BUILD_TIME }}
echo Build Time:$BUILD_TIME
wget -nv -O CHANGELOG https://${{ env.MIKRO_UPGRADE_URL }}/routeros/$LATEST_VERSION/CHANGELOG
cat CHANGELOG
echo "LATEST_VERSION=${LATEST_VERSION}" >> $GITHUB_ENV
echo "BUILD_TIME=${BUILD_TIME}" >> $GITHUB_ENV
has_patched_npk=false
if wget -nv --spider https://${{ env.CUSTOM_UPGRADE_URL }}/routeros/$LATEST_VERSION/routeros-x86-$LATEST_VERSION.npk; then
echo "RouterOS NPK file exists, attempting to download..."
if wget -nv -O routeros-x86-$LATEST_VERSION.npk https://${{ env.CUSTOM_UPGRADE_URL }}/routeros/$LATEST_VERSION/routeros-x86-$LATEST_VERSION.npk; then
echo "RouterOS NPK file downloaded successfully."
if wget -nv --spider https://${{ env.CUSTOM_UPGRADE_URL }}/routeros/$LATEST_VERSION/system-$LATEST_VERSION.npk; then
echo "System NPK file exists, attempting to download..."
if wget -nv -O system-$LATEST_VERSION.npk https://${{ env.CUSTOM_UPGRADE_URL }}/routeros/$LATEST_VERSION/system-$LATEST_VERSION.npk; then
has_patched_npk=true
fi
fi
fi
fi
echo "has_patched_npk=$has_patched_npk" >> $GITHUB_OUTPUT
- name: Cache Squashfs
if: steps.get_latest.outputs.has_new_version == 'true'
id: cache-squashfs
uses: actions/cache@v4
with:
path: |
option.sfs
key: busybox-squashfs
- name: Create Squashfs for option
if: steps.get_latest.outputs.has_new_version == 'true' && steps.cache-squashfs.outputs.cache-hit != 'true'
run: |
sudo mkdir -p ./option-root/bin/
sudo cp busybox/busybox_x86 ./option-root/bin/busybox
sudo chmod +x ./option-root/bin/busybox
sudo cp keygen/keygen_x86 ./option-root/bin/keygen
sudo chmod +x ./option-root/bin/keygen
sudo chmod +x ./busybox/busybox_x86
COMMANDS=$(./busybox/busybox_x86 --list)
for cmd in $COMMANDS; do
sudo ln -sf /pckg/option/bin/busybox ./option-root/bin/$cmd
done
sudo mksquashfs option-root option.sfs -quiet -comp xz -no-xattrs -b 256k
sudo rm -rf option-root
- name: Cache mikrotik-${{ env.LATEST_VERSION }}.iso
if: steps.get_latest.outputs.has_new_version == 'true'
id: cache-mikrotik
uses: actions/cache@v4
with:
path: |
mikrotik.iso
key: mikrotik-${{ env.LATEST_VERSION }}
- name: Get mikrotik-${{ env.LATEST_VERSION }}.iso
if: steps.get_latest.outputs.has_new_version == 'true' && steps.cache-mikrotik.outputs.cache-hit != 'true'
run: |
sudo wget -nv -O mikrotik.iso https://download.mikrotik.com/routeros/$LATEST_VERSION/mikrotik-$LATEST_VERSION.iso
- name: Patch mikrotik-${{ env.LATEST_VERSION }}.iso
if: steps.get_latest.outputs.has_new_version == 'true'
run: |
sudo apt-get install -y mkisofs xorriso > /dev/null
sudo mkdir ./iso
sudo mount -o loop,ro mikrotik.iso ./iso
sudo mkdir ./new_iso
sudo cp -r ./iso/* ./new_iso/
sudo rsync -a ./iso/ ./new_iso/
sudo umount ./iso
sudo rm -rf ./iso
if [ "${{ steps.get_latest.outputs.has_patched_npk }}" == "false" ]; then
sudo mv ./new_iso/system-$LATEST_VERSION.npk ./
sudo -E python3 patch.py npk system-$LATEST_VERSION.npk
else
sudo -E python3 npk.py sign system-$LATEST_VERSION.npk system-$LATEST_VERSION.npk
fi
sudo -E python3 patch.py kernel ./new_iso/isolinux/initrd.rgz
NPK_FILES=$(find ./new_iso/*.npk)
for file in $NPK_FILES; do
sudo -E python3 npk.py sign $file $file
done
sudo cp system-$LATEST_VERSION.npk ./new_iso/
#sudo -E python3 npk.py create ./new_iso/gps-$LATEST_VERSION.npk ./option-$LATEST_VERSION.npk option ./option.sfs -desc="busybox"
#sudo cp option-$LATEST_VERSION.npk ./new_iso/
sudo mkisofs -o mikrotik-$LATEST_VERSION.iso \
-V "MikroTik $LATEST_VERSION" \
-sysid "" -preparer "MiKroTiK" \
-publisher "" -A "MiKroTiK RouterOS" \
-input-charset utf-8 \
-b isolinux/isolinux.bin \
-c isolinux/boot.cat \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
-R -J \
./new_iso
sudo mkdir ./all_packages
sudo cp ./new_iso/*.npk ./all_packages/
sudo rm -rf ./new_iso
- name: Create install-image-${{ env.LATEST_VERSION }}.img
if: steps.get_latest.outputs.has_new_version == 'true' && steps.get_latest.outputs.has_patched_npk == 'true'
run: |
sudo modprobe nbd
sudo apt-get update
sudo apt-get install -y qemu-utils extlinux > /dev/null
truncate --size 64M install-image-$LATEST_VERSION.img
sudo qemu-nbd -c /dev/nbd0 -f raw install-image-$LATEST_VERSION.img
sudo mkfs.vfat -n "Install" /dev/nbd0
sudo mkdir ./install
sudo mount /dev/nbd0 ./install
sudo extlinux --install -H 64 -S 32 ./install/
echo -e 'default system\nLABEL system\n\tKERNEL linux\n\tAPPEND load_ramdisk=1 initrd=initrd.rgz -hdd-install' \
> syslinux.cfg
sudo cp syslinux.cfg ./install/
sudo rm syslinux.cfg
NPK_FILES=($(find ./all_packages/*.npk))
for ((i=1; i<=${#NPK_FILES[@]}; i++))
do
echo "${NPK_FILES[$i-1]}=>$i.npk"
sudo cp ${NPK_FILES[$i-1]} ./install/$i.npk
done
sudo touch ./install/CHOOSE
sudo touch ./install/autorun.scr
sudo umount /dev/nbd0
sudo qemu-nbd -d /dev/nbd0
sudo rm -rf ./install
sudo zip install-image-$LATEST_VERSION.zip ./install-image-$LATEST_VERSION.img
sudo rm ./install-image-$LATEST_VERSION.img
- name: Cache chr-${{ env.LATEST_VERSION }}.img.zip
if: steps.get_latest.outputs.has_new_version == 'true'
id: cache-chr-img
uses: actions/cache@v4
with:
path: |
chr.img
key: chr-${{ env.LATEST_VERSION }}.img
- name: Get chr-${{ env.LATEST_VERSION }}.img
if: steps.get_latest.outputs.has_new_version == 'true' && steps.cache-chr-img.outputs.cache-hit != 'true'
run: |
sudo wget -nv -O chr.img.zip https://download.mikrotik.com/routeros/$LATEST_VERSION/chr-$LATEST_VERSION.img.zip
sudo unzip chr.img.zip
sudo rm chr.img.zip
sudo mv chr-$LATEST_VERSION.img chr.img
- name: Create chr-${{ env.LATEST_VERSION }}.img
if: steps.get_latest.outputs.has_new_version == 'true'
run: |
sudo modprobe nbd
sudo apt-get install -y qemu-utils extlinux > /dev/null
sudo mkdir ./chr
sudo cp chr.img chr-$LATEST_VERSION.img
sudo qemu-nbd -c /dev/nbd0 -f raw chr-$LATEST_VERSION.img
sudo -E python3 patch.py block /dev/nbd0p1 boot/initrd.rgz
sudo mount /dev/nbd0p1 ./chr
sudo cp ./all_packages/dude-$LATEST_VERSION.npk ./chr/var/pdb/dude/image
if [ "${{ steps.get_latest.outputs.has_patched_npk }}" == "false" ]; then
sudo -E python3 patch.py npk ./chr/var/pdb/routeros-x86/image
sudo cp ./chr/var/pdb/routeros-x86/image ./all_packages/routeros-x86-$LATEST_VERSION.npk
else
sudo -E python3 npk.py sign routeros-x86-$LATEST_VERSION.npk routeros-x86-$LATEST_VERSION.npk
sudo cp -f routeros-x86-$LATEST_VERSION.npk ./chr/var/pdb/routeros-x86/image
sudo cp routeros-x86-$LATEST_VERSION.npk ./all_packages/routeros-x86-$LATEST_VERSION.npk
fi
#sudo mkdir -p ./chr/var/pdb/option
#sudo cp ./all_packages/option-$LATEST_VERSION.npk ./chr/var/pdb/option/image
sudo umount ./chr
sudo qemu-nbd -d /dev/nbd0
sudo rm -rf ./chr
sudo qemu-img convert -f raw -O qcow2 chr-$LATEST_VERSION.img chr-$LATEST_VERSION.qcow2
sudo qemu-img convert -f raw -O vmdk chr-$LATEST_VERSION.img chr-$LATEST_VERSION.vmdk
sudo qemu-img convert -f raw -O vpc chr-$LATEST_VERSION.img chr-$LATEST_VERSION.vhd
sudo qemu-img convert -f raw -O vhdx chr-$LATEST_VERSION.img chr-$LATEST_VERSION.vhdx
sudo qemu-img convert -f raw -O vdi chr-$LATEST_VERSION.img chr-$LATEST_VERSION.vdi
sudo zip chr-$LATEST_VERSION.qcow2.zip chr-$LATEST_VERSION.qcow2
sudo zip chr-$LATEST_VERSION.vmdk.zip chr-$LATEST_VERSION.vmdk
sudo zip chr-$LATEST_VERSION.vhd.zip chr-$LATEST_VERSION.vhd
sudo zip chr-$LATEST_VERSION.vhdx.zip chr-$LATEST_VERSION.vhdx
sudo zip chr-$LATEST_VERSION.vdi.zip chr-$LATEST_VERSION.vdi
sudo zip chr-$LATEST_VERSION.img.zip chr-$LATEST_VERSION.img
sudo rm chr-$LATEST_VERSION.qcow2
sudo rm chr-$LATEST_VERSION.vmdk
sudo rm chr-$LATEST_VERSION.vhd
sudo rm chr-$LATEST_VERSION.vhdx
sudo rm chr-$LATEST_VERSION.vdi
sudo rm chr-$LATEST_VERSION.img
cd ./all_packages
sudo zip ../all_packages-x86-$LATEST_VERSION.zip *.npk
cd ..
- name: Upload Files
if: steps.get_latest.outputs.has_new_version == 'true'
run: |
if [ "${{ steps.get_latest.outputs.has_patched_npk }}" == "false" ]; then
OUTDIR="$LATEST_VERSION-UNDONE"
else
OUTDIR="$LATEST_VERSION"
fi
sudo mkdir -p ./publish/$OUTDIR
sudo cp CHANGELOG ./publish/$OUTDIR/
sudo cp ./all_packages/*.npk ./publish/$OUTDIR/
sudo chown -R root:root ./publish/
sudo apt-get install -y lftp ssh sshpass > /dev/null 2>&1
sudo -E lftp -u ${{ secrets.SSH_USERNAME }},'${{ secrets.SSH_PASSWORD }}' sftp://${{ secrets.SSH_SERVER }}:${{ secrets.SSH_PORT }} <<EOF
set sftp:auto-confirm yes
mirror --reverse --verbose --only-newer ./publish ${{ secrets.SSH_DIRECTORY }}
bye
EOF
sshpass -p "${{ secrets.SSH_PASSWORD }}" ssh -o StrictHostKeyChecking=no -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_SERVER }} "echo $LATEST_VERSION $BUILD_TIME | tee /rw/disk/${{ secrets.SSH_DIRECTORY }}/NEWEST6.${{ matrix.channel }}; chown -R 32768:32768 /rw/disk/${{ secrets.SSH_DIRECTORY }}/"
- name: Clear Cloudflare cache
if: steps.get_latest.outputs.has_new_version == 'true'
run: |
curl --request POST --url "https://api.cloudflare.com/client/v4/zones/fe6831ed6dc9e6235e69ef2a31f2e7fe/purge_cache" \
--header "Authorization: Bearer 9GDQkzU51QXaqzp1qMjyFKpyeJyOdnNoG9GZQaGP" \
--header "Content-Type:application/json" \
--data '{"purge_everything": true}'
- name: Delete Release tag ${{ env.LATEST_VERSION }}
if: steps.get_latest.outputs.has_new_version == 'true' && steps.get_latest.outputs.has_patched_npk == 'true'
run: |
HEADER="Authorization: token ${{ secrets.GITHUB_TOKEN }}"
RELEASE_INFO=$(curl -s -H $HEADER https://api.github.com/repos/${{ github.repository }}/releases/tags/$LATEST_VERSION)
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/$LATEST_VERSION
echo "Tag $LATEST_VERSION deleted successfully."
curl -X DELETE -H "$HEADER" https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID
echo "Release with tag $LATEST_VERSION deleted successfully."
else
echo "Release not found for tag: $LATEST_VERSION)"
fi
- name: Create Release tag ${{ env.LATEST_VERSION }}
if: steps.get_latest.outputs.has_new_version == 'true' && steps.get_latest.outputs.has_patched_npk == 'true'
uses: softprops/action-gh-release@v2
with:
name: "RouterOS ${{ env.LATEST_VERSION }}"
body_path: "CHANGELOG"
tag_name: ${{ env.LATEST_VERSION }}
make_latest: false
prerelease: ${{ matrix.channel == 'testing' }}
files: |
mikrotik-${{ env.LATEST_VERSION }}.iso
chr-${{ env.LATEST_VERSION }}*.zip
install-image-${{ env.LATEST_VERSION }}.zip
routeros-x86-${{ env.LATEST_VERSION }}.npk
all_packages-x86-${{ env.LATEST_VERSION }}.zip

View File

@ -1,616 +0,0 @@
name: Patch Mikrotik RouterOS 7.x
on:
# push:
# branches: [ "main" ]
schedule:
- cron: "0 0 * * *" # At UTC 00:00 on every day
workflow_dispatch:
inputs:
arch:
description: 'Architecture (x86, arm64, arm)'
required: true
default: 'x86'
type: choice
options:
- x86
- arm64
- arm
channel:
description: 'Channel (stable, testing)'
required: true
default: 'stable'
type: choice
options:
- stable
- testing
version:
description: "Specify version (e.g., 7.19.2), empty for latest"
required: false
default: ''
type: string
buildtime:
description: "Custom build time, leave empty for now"
required: false
default: ''
type: string
release:
description: "Release to GitHub & Upload "
required: false
default: false
type: boolean
permissions:
contents: write
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 }}
jobs:
Set_Variables:
runs-on: ubuntu-22.04
outputs:
BUILD_TIME: ${{ steps.set_vars.outputs.BUILD_TIME }}
RELEASE: ${{ steps.set_vars.outputs.RELEASE }}
MATRIX_JSON: ${{ steps.set_vars.outputs.MATRIX_JSON }}
steps:
- name: Set variables
id: set_vars
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
BUILD_TIME="${{ github.event.inputs.buildtime }}"
RELEASE="${{ github.event.inputs.release }}"
if [ -z "$BUILD_TIME" ]; then
BUILD_TIME=$(date +'%s')
fi
if [ -z "$RELEASE" ]; then
RELEASE=false
fi
ARCH="${{ github.event.inputs.arch }}"
CHANNEL="${{ github.event.inputs.channel }}"
MATRIX_JSON=$(jq -nc --arg arch "$ARCH" --arg channel "$CHANNEL" '[{arch: $arch, channel: $channel}]')
else
BUILD_TIME=$(date +'%s')
RELEASE=true
MATRIX_JSON='[{"arch":"x86","channel":"stable"},{"arch":"arm64","channel":"stable"},{"arch":"arm","channel":"stable"},{"arch":"x86","channel":"testing"},{"arch":"arm64","channel":"testing"},{"arch":"arm","channel":"testing"}]'
MATRIX_JSON='[{"arch":"x86","channel":"stable"},{"arch":"arm64","channel":"stable"},{"arch":"x86","channel":"testing"},{"arch":"arm64","channel":"testing"}]'
fi
echo "BUILD_TIME=$BUILD_TIME" >> $GITHUB_OUTPUT
echo "RELEASE=$RELEASE" >> $GITHUB_OUTPUT
echo "MATRIX_JSON=$MATRIX_JSON" >> $GITHUB_OUTPUT
echo "BUILD_TIME: $BUILD_TIME"
echo "RELEASE: $RELEASE"
echo "MATRIX_JSON: $MATRIX_JSON"
Patch_RouterOS:
needs: Set_Variables
runs-on: ubuntu-22.04
strategy:
matrix:
include: ${{ fromJSON(needs.Set_Variables.outputs.MATRIX_JSON) }}
env:
TZ: 'Asia/Shanghai'
BUILD_TIME: ${{ needs.Set_Variables.outputs.BUILD_TIME }}
RELEASE: ${{ needs.Set_Variables.outputs.RELEASE }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Get latest routeros version
id: get_latest
run: |
echo $(uname -a)
echo Build Time:$BUILD_TIME
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
if [ -n "${{ github.event.inputs.version }}" ]; then
LATEST_VERSION="${{ github.event.inputs.version }}"
else
LATEST_VERSION=$(wget -nv -O - https://${{ env.MIKRO_UPGRADE_URL }}/routeros/NEWESTa7.${{ matrix.channel }} | cut -d ' ' -f1)
fi
else
LATEST_VERSION=$(wget -nv -O - https://${{ env.MIKRO_UPGRADE_URL }}/routeros/NEWESTa7.${{ matrix.channel }} | cut -d ' ' -f1)
fi
echo Latest Version:$LATEST_VERSION
if [ "${{ github.event_name }}" == "schedule" ]; then
_LATEST_VERSION=$(wget -nv -O - https://${{ env.CUSTOM_UPGRADE_URL }}/routeros/NEWESTa7.${{ matrix.channel }} | cut -d ' ' -f1)
if [ "$_LATEST_VERSION" == "$LATEST_VERSION" ]; then
echo "No new version found"
echo "has_new_version=false" >> $GITHUB_OUTPUT
exit 0
fi
fi
echo "has_new_version=true" >> $GITHUB_OUTPUT
wget -nv -O CHANGELOG https://${{ env.MIKRO_UPGRADE_URL }}/routeros/$LATEST_VERSION/CHANGELOG
cat CHANGELOG
if [[ "${{ matrix.arch }}" == "arm64" ]]; then
echo "ARCH=-arm64" >> $GITHUB_ENV
elif [[ "${{ matrix.arch }}" == "arm" ]]; then
echo "ARCH=-arm" >> $GITHUB_ENV
else
echo "ARCH=" >> $GITHUB_ENV
fi
echo "LATEST_VERSION=${LATEST_VERSION}" >> $GITHUB_ENV
sudo apt-get update > /dev/null
- name: Get loader patch files
if: steps.get_latest.outputs.has_new_version == 'true'
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: Cache Squashfs
if: steps.get_latest.outputs.has_new_version == 'true'
id: cache-squashfs
uses: actions/cache@v4
with:
path: |
python3.sfs
option.sfs
key: busybox-python3-squashfs-${{ matrix.arch }}
- name: Create Squashfs for option and python3
if: steps.get_latest.outputs.has_new_version == 'true' && steps.cache-squashfs.outputs.cache-hit != 'true'
run: |
sudo mkdir -p ./option-root/bin/
if [ "${{ matrix.arch }}" == "x86" ]; then
sudo cp busybox/busybox_x86 ./option-root/bin/busybox
sudo chmod +x ./option-root/bin/busybox
sudo cp keygen/keygen_x86 ./option-root/bin/keygen
sudo chmod +x ./option-root/bin/keygen
elif [ "${{ matrix.arch }}" == "arm64" ]; then
sudo cp busybox/busybox_aarch64 ./option-root/bin/busybox
sudo chmod +x ./option-root/bin/busybox
sudo cp keygen/keygen_aarch64 ./option-root/bin/keygen
sudo chmod +x ./option-root/bin/keygen
elif [ "${{ matrix.arch }}" == "arm" ]; then
sudo cp busybox/busybox_arm ./option-root/bin/busybox
sudo chmod +x ./option-root/bin/busybox
sudo cp keygen/keygen_arm ./option-root/bin/keygen
sudo chmod +x ./option-root/bin/keygen
else
echo "Unsupported architecture: ${{ matrix.arch }}"
exit 1
fi
sudo chmod +x ./busybox/busybox_x86
COMMANDS=$(./busybox/busybox_x86 --list)
for cmd in $COMMANDS; do
sudo ln -sf /pckg/option/bin/busybox ./option-root/bin/$cmd
done
sudo mksquashfs option-root option.sfs -quiet -comp xz -no-xattrs -b 256k
sudo rm -rf option-root
if [ "${{ matrix.arch }}" == "x86" ]; then
sudo wget -O cpython.tar.gz -nv https://github.com/indygreg/python-build-standalone/releases/download/20250708/cpython-3.11.13+20250708-x86_64-unknown-linux-musl-install_only_stripped.tar.gz
elif [ "${{ matrix.arch }}" == "arm64" ]; then
sudo wget -O cpython.tar.gz -nv https://github.com/indygreg/python-build-standalone/releases/download/20250708/cpython-3.11.13+20250708-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz
elif [ "${{ matrix.arch }}" == "arm" ]; then
sudo wget -O cpython.tar.gz -nv https://github.com/indygreg/python-build-standalone/releases/download/20250708/cpython-3.11.13+20250708-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz
fi
sudo tar -xf cpython.tar.gz
sudo rm cpython.tar.gz
sudo rm -rf ./python/include
sudo rm -rf ./python/share
sudo mksquashfs python python3.sfs -quiet -comp xz -no-xattrs -b 256k
sudo rm -rf ./python
- name: Cache mikrotik-${{ env.LATEST_VERSION }}${{ env.ARCH }}.iso
if: steps.get_latest.outputs.has_new_version == 'true' && (matrix.arch == 'x86' || matrix.arch == 'arm64')
id: cache-mikrotik
uses: actions/cache@v4
with:
path: |
mikrotik.iso
key: mikrotik-${{ env.LATEST_VERSION }}${{ env.ARCH }}
- name: Get mikrotik-${{ env.LATEST_VERSION }}${{ env.ARCH }}.iso
if: steps.get_latest.outputs.has_new_version == 'true' && steps.cache-mikrotik.outputs.cache-hit != 'true' && (matrix.arch == 'x86' || matrix.arch == 'arm64')
run: |
sudo wget -nv -O mikrotik.iso https://download.mikrotik.com/routeros/$LATEST_VERSION/mikrotik-$LATEST_VERSION$ARCH.iso
- name: Patch mikrotik-${{ env.LATEST_VERSION }}${{ env.ARCH }}.iso
if: steps.get_latest.outputs.has_new_version == 'true' && (matrix.arch == 'x86' || matrix.arch == 'arm64')
run: |
sudo apt-get install -y mkisofs xorriso > /dev/null
sudo mkdir ./iso
sudo mount -o loop,ro mikrotik.iso ./iso
sudo mkdir ./new_iso
sudo rsync -a ./iso/ ./new_iso/
sudo umount ./iso
sudo rm -rf ./iso
sudo mv ./new_iso/routeros-$LATEST_VERSION$ARCH.npk ./
sudo -E python3 patch.py npk routeros-$LATEST_VERSION$ARCH.npk
NPK_FILES=$(find ./new_iso/*.npk)
for file in $NPK_FILES; do
sudo -E python3 npk.py sign $file $file
done
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/
sudo -E python3 npk.py create ./new_iso/gps-$LATEST_VERSION$ARCH.npk ./python3-$LATEST_VERSION$ARCH.npk python3 ./python3.sfs -desc="python 3.11.13"
sudo cp python3-$LATEST_VERSION$ARCH.npk ./new_iso/
sudo mkdir ./efiboot
sudo mount -o loop ./new_iso/efiboot.img ./efiboot
if [ "${{ matrix.arch }}" == "x86" ]; then
sudo -E python3 patch.py kernel ./efiboot/linux.x86_64
sudo cp ./efiboot/linux.x86_64 ./BOOTX64.EFI
sudo cp ./BOOTX64.EFI ./new_iso/isolinux/linux
sudo umount ./efiboot
sudo mkisofs -o mikrotik-$LATEST_VERSION$ARCH.iso \
-V "MikroTik $LATEST_VERSION ${{ matrix.arch }}" \
-sysid "" -preparer "MiKroTiK" \
-publisher "" -A "MiKroTiK RouterOS" \
-input-charset utf-8 \
-b isolinux/isolinux.bin \
-c isolinux/boot.cat \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
-eltorito-alt-boot \
-e efiboot.img \
-no-emul-boot \
-R -J \
./new_iso
elif [ "${{ matrix.arch }}" == "arm64" ]; then
sudo -E python3 patch.py kernel ./efiboot/EFI/BOOT/BOOTAA64.EFI
sudo umount ./efiboot
sudo xorriso -as mkisofs -o mikrotik-$LATEST_VERSION$ARCH.iso \
-V "MikroTik $LATEST_VERSION ${{ matrix.arch }}" \
-sysid "" -preparer "MiKroTiK" \
-publisher "" -A "MiKroTiK RouterOS" \
-input-charset utf-8 \
-b efiboot.img \
-no-emul-boot \
-R -J \
./new_iso
fi
sudo rm -rf ./efiboot
sudo mkdir ./all_packages
sudo cp ./new_iso/*.npk ./all_packages/
sudo rm -rf ./new_iso
cd ./all_packages
sudo zip ../all_packages-${{ matrix.arch }}-$LATEST_VERSION.zip *.npk
cd ..
- name: Cache chr-${{ env.LATEST_VERSION }}${{ env.ARCH }}.img.zip
if: steps.get_latest.outputs.has_new_version == 'true' && (matrix.arch == 'x86' || matrix.arch == 'arm64')
id: cache-chr-img
uses: actions/cache@v4
with:
path: |
chr.img
key: chr-${{ env.LATEST_VERSION }}${{ env.ARCH }}.img
- name: Get chr-${{ env.LATEST_VERSION }}${{ env.ARCH }}.img
if: steps.get_latest.outputs.has_new_version == 'true' && steps.cache-chr-img.outputs.cache-hit != 'true' && (matrix.arch == 'x86' || matrix.arch == 'arm64')
run: |
sudo wget -nv -O chr.img.zip https://download.mikrotik.com/routeros/$LATEST_VERSION/chr-$LATEST_VERSION$ARCH.img.zip
sudo unzip chr.img.zip
sudo rm chr.img.zip
sudo mv chr-$LATEST_VERSION$ARCH.img chr.img
- name: Create chr-${{ env.LATEST_VERSION }}${{ env.ARCH }}.img
if: steps.get_latest.outputs.has_new_version == 'true' && (matrix.arch == 'x86' || matrix.arch == 'arm64')
run: |
sudo modprobe nbd
sudo apt-get install -y qemu-utils > /dev/null
truncate --size 128M chr-$LATEST_VERSION$ARCH.img
sgdisk --clear --set-alignment=2 \
--new=1::+32M --typecode=1:8300 --change-name=1:"RouterOS Boot" --attributes=1:set:2 \
--new=2::-0 --typecode=2:8300 --change-name=2:"RouterOS" \
--gpttombr=1:2 \
chr-$LATEST_VERSION$ARCH.img
dd if=chr-$LATEST_VERSION$ARCH.img of=pt.bin bs=1 count=66 skip=446
echo -e "\x80" | dd of=pt.bin bs=1 count=1 conv=notrunc
sgdisk --mbrtogpt --clear --set-alignment=2 \
--new=1::+32M --typecode=1:8300 --change-name=1:"RouterOS Boot" --attributes=1:set:2 \
--new=2::-0 --typecode=2:8300 --change-name=2:"RouterOS" \
chr-$LATEST_VERSION$ARCH.img
dd if=chr.img of=chr-$LATEST_VERSION$ARCH.img bs=1 count=446 conv=notrunc
dd if=pt.bin of=chr-$LATEST_VERSION$ARCH.img bs=1 count=66 seek=446 conv=notrunc
sudo qemu-nbd -c /dev/nbd0 -f raw chr-$LATEST_VERSION$ARCH.img
sudo mkfs.vfat -n "Boot" /dev/nbd0p1
sudo mkfs.ext4 -F -L "RouterOS" -m 0 /dev/nbd0p2
sudo mkdir -p ./img/{boot,routeros}
sudo mount /dev/nbd0p1 ./img/boot/
if [ "${{ matrix.arch }}" == "x86" ]; then
sudo cp chr.img chr-$LATEST_VERSION$ARCH-legacy-bios.img
sudo qemu-nbd -c /dev/nbd1 -f raw chr-$LATEST_VERSION$ARCH-legacy-bios.img
sudo -E python3 patch.py block /dev/nbd1p1 EFI/BOOT/BOOTX64.EFI
sudo mkdir -p ./chr/{boot,routeros}
sudo mount /dev/nbd1p1 ./chr/boot/
sudo mkdir -p ./img/boot/EFI/BOOT
sudo cp ./chr/boot/EFI/BOOT/BOOTX64.EFI ./img/boot/EFI/BOOT/BOOTX64.EFI
sudo umount /dev/nbd1p1
sudo shred -v -n 1 -z /dev/nbd1p2
sudo mkfs.ext4 -F -L "RouterOS" -m 0 /dev/nbd1p2
sudo mount /dev/nbd1p2 ./chr/routeros/
sudo mkdir -p ./chr/routeros/{var/pdb/{system,option},boot,rw}
sudo cp ./all_packages/option-$LATEST_VERSION$ARCH.npk ./chr/routeros/var/pdb/option/image
sudo cp ./all_packages/routeros-$LATEST_VERSION$ARCH.npk ./chr/routeros/var/pdb/system/image
sudo umount /dev/nbd1p2
sudo qemu-nbd -d /dev/nbd1
sudo rm -rf ./chr
sudo qemu-img convert -f raw -O qcow2 chr-$LATEST_VERSION$ARCH-legacy-bios.img chr-$LATEST_VERSION$ARCH-legacy-bios.qcow2
sudo qemu-img convert -f raw -O vmdk chr-$LATEST_VERSION$ARCH-legacy-bios.img chr-$LATEST_VERSION$ARCH-legacy-bios.vmdk
sudo qemu-img convert -f raw -O vpc chr-$LATEST_VERSION$ARCH-legacy-bios.img chr-$LATEST_VERSION$ARCH-legacy-bios.vhd
sudo qemu-img convert -f raw -O vhdx chr-$LATEST_VERSION$ARCH-legacy-bios.img chr-$LATEST_VERSION$ARCH-legacy-bios.vhdx
sudo qemu-img convert -f raw -O vdi chr-$LATEST_VERSION$ARCH-legacy-bios.img chr-$LATEST_VERSION$ARCH-legacy-bios.vdi
sudo zip chr-$LATEST_VERSION$ARCH-legacy-bios.qcow2.zip chr-$LATEST_VERSION$ARCH-legacy-bios.qcow2
sudo zip chr-$LATEST_VERSION$ARCH-legacy-bios.vmdk.zip chr-$LATEST_VERSION$ARCH-legacy-bios.vmdk
sudo zip chr-$LATEST_VERSION$ARCH-legacy-bios.vhd.zip chr-$LATEST_VERSION$ARCH-legacy-bios.vhd
sudo zip chr-$LATEST_VERSION$ARCH-legacy-bios.vhdx.zip chr-$LATEST_VERSION$ARCH-legacy-bios.vhdx
sudo zip chr-$LATEST_VERSION$ARCH-legacy-bios.vdi.zip chr-$LATEST_VERSION$ARCH-legacy-bios.vdi
sudo zip chr-$LATEST_VERSION$ARCH-legacy-bios.img.zip chr-$LATEST_VERSION$ARCH-legacy-bios.img
sudo rm chr-$LATEST_VERSION$ARCH-legacy-bios.qcow2
sudo rm chr-$LATEST_VERSION$ARCH-legacy-bios.vmdk
sudo rm chr-$LATEST_VERSION$ARCH-legacy-bios.vhd
sudo rm chr-$LATEST_VERSION$ARCH-legacy-bios.vhdx
sudo rm chr-$LATEST_VERSION$ARCH-legacy-bios.vdi
sudo rm chr-$LATEST_VERSION$ARCH-legacy-bios.img
elif [ "${{ matrix.arch }}" == "arm64" ]; then
sudo qemu-nbd -c /dev/nbd1 -f raw chr.img
sudo mkdir -p ./chr/boot
sudo mount /dev/nbd1p1 ./chr/boot/
sudo -E python3 patch.py kernel ./chr/boot/EFI/BOOT/BOOTAA64.EFI -O ./BOOTAA64.EFI
sudo mkdir -p ./img/boot/EFI/BOOT
sudo cp ./BOOTAA64.EFI ./img/boot/EFI/BOOT/BOOTAA64.EFI
sudo umount /dev/nbd1p1
sudo rm -rf ./chr
sudo qemu-nbd -d /dev/nbd1
fi
sudo umount /dev/nbd0p1
sudo mount /dev/nbd0p2 ./img/routeros/
sudo mkdir -p ./img/routeros/{var/pdb/{system,option},boot,rw}
sudo cp ./all_packages/option-$LATEST_VERSION$ARCH.npk ./img/routeros/var/pdb/option/image
sudo cp ./all_packages/routeros-$LATEST_VERSION$ARCH.npk ./img/routeros/var/pdb/system/image
sudo umount /dev/nbd0p2
sudo rm -rf ./img
sudo qemu-nbd -d /dev/nbd0
sudo qemu-img convert -f raw -O qcow2 chr-$LATEST_VERSION$ARCH.img chr-$LATEST_VERSION$ARCH.qcow2
sudo qemu-img convert -f raw -O vmdk chr-$LATEST_VERSION$ARCH.img chr-$LATEST_VERSION$ARCH.vmdk
sudo qemu-img convert -f raw -O vpc chr-$LATEST_VERSION$ARCH.img chr-$LATEST_VERSION$ARCH.vhd
sudo qemu-img convert -f raw -O vhdx chr-$LATEST_VERSION$ARCH.img chr-$LATEST_VERSION$ARCH.vhdx
sudo qemu-img convert -f raw -O vdi chr-$LATEST_VERSION$ARCH.img chr-$LATEST_VERSION$ARCH.vdi
sudo zip chr-$LATEST_VERSION$ARCH.qcow2.zip chr-$LATEST_VERSION$ARCH.qcow2
sudo zip chr-$LATEST_VERSION$ARCH.vmdk.zip chr-$LATEST_VERSION$ARCH.vmdk
sudo zip chr-$LATEST_VERSION$ARCH.vhd.zip chr-$LATEST_VERSION$ARCH.vhd
sudo zip chr-$LATEST_VERSION$ARCH.vhdx.zip chr-$LATEST_VERSION$ARCH.vhdx
sudo zip chr-$LATEST_VERSION$ARCH.vdi.zip chr-$LATEST_VERSION$ARCH.vdi
sudo zip chr-$LATEST_VERSION$ARCH.img.zip chr-$LATEST_VERSION$ARCH.img
sudo rm chr-$LATEST_VERSION$ARCH.qcow2
sudo rm chr-$LATEST_VERSION$ARCH.vmdk
sudo rm chr-$LATEST_VERSION$ARCH.vhd
sudo rm chr-$LATEST_VERSION$ARCH.vhdx
sudo rm chr-$LATEST_VERSION$ARCH.vdi
sudo rm chr-$LATEST_VERSION$ARCH.img
- name: Cache all_packages-${{ matrix.arch }}-$LATEST_VERSION.zip
if: steps.get_latest.outputs.has_new_version == 'true' && matrix.arch != 'x86' && matrix.arch != 'arm64'
id: cache-all-packages
uses: actions/cache@v4
with:
path: all_packages.zip
key: all_packages-${{ matrix.arch }}-$LATEST_VERSION.zip
- name: Get all_packages-${{ matrix.arch }}-$LATEST_VERSION.zip
if: steps.get_latest.outputs.has_new_version == 'true' && steps.cache-all-packages.outputs.cache-hit != 'true' && matrix.arch != 'x86' && matrix.arch != 'arm64'
run: |
sudo wget -nv -O all_packages.zip https://download.mikrotik.com/routeros/$LATEST_VERSION/all_packages-${{ matrix.arch }}-$LATEST_VERSION.zip
- name: Cache Main package routeros-${{ env.LATEST_VERSION }}${{ env.ARCH }}.npk
if: steps.get_latest.outputs.has_new_version == 'true' && matrix.arch != 'x86' && matrix.arch != 'arm64'
id: cache-main-package
uses: actions/cache@v4
with:
path: routeros.npk
key: routeros-${{ env.LATEST_VERSION }}${{ env.ARCH }}.npk
- name: Get Main package routeros-${{ env.LATEST_VERSION }}${{ env.ARCH }}.npk
if: steps.get_latest.outputs.has_new_version == 'true' && steps.cache-main-package.outputs.cache-hit != 'true' && matrix.arch != 'x86' && matrix.arch != 'arm64'
run: |
sudo wget -nv -O routeros.npk https://download.mikrotik.com/routeros/$LATEST_VERSION/routeros-$LATEST_VERSION$ARCH.npk
- name: Patch all_packages-${{ matrix.arch }}-$LATEST_VERSION.zip
if: steps.get_latest.outputs.has_new_version == 'true' && matrix.arch != 'x86' && matrix.arch != 'arm64'
run: |
sudo unzip all_packages.zip -d ./all_packages
sudo cp routeros.npk routeros-$LATEST_VERSION$ARCH.npk
sudo -E python3 patch.py npk routeros-$LATEST_VERSION$ARCH.npk
NPK_FILES=$(find ./all_packages/*.npk)
for file in $NPK_FILES; do
sudo -E python3 npk.py sign $file $file
done
sudo cp -f routeros-$LATEST_VERSION$ARCH.npk ./all_packages/routeros-$LATEST_VERSION$ARCH.npk
sudo -E python3 npk.py create ./all_packages/gps-$LATEST_VERSION$ARCH.npk ./all_packages/option-$LATEST_VERSION$ARCH.npk option ./option.sfs -desc="busybox"
sudo -E python3 npk.py create ./all_packages/gps-$LATEST_VERSION$ARCH.npk ./all_packages/python3-$LATEST_VERSION$ARCH.npk python3 ./python3.sfs -desc="python 3.11.13"
cd ./all_packages
sudo zip ../all_packages-${{ matrix.arch }}-$LATEST_VERSION.zip *.npk
cd ..
- name: Cache refind
if: steps.get_latest.outputs.has_new_version == 'true' && matrix.arch == 'x86' && env.RELEASE == 'true'
id: cache-refind
uses: actions/cache@v4
with:
path: refind-bin-0.14.2.zip
key: refind
- name: Get refind
if: steps.get_latest.outputs.has_new_version == 'true' && matrix.arch == 'x86' && steps.cache-refind.outputs.cache-hit != 'true' && env.RELEASE == 'true'
run: sudo wget -nv -O refind-bin-0.14.2.zip https://nchc.dl.sourceforge.net/project/refind/0.14.2/refind-bin-0.14.2.zip
- name: Create install-image-${{ env.LATEST_VERSION }}${{ env.ARCH }}.img
if: steps.get_latest.outputs.has_new_version == 'true' && matrix.arch == 'x86' && env.RELEASE == 'true'
run: |
sudo modprobe nbd
sudo apt-get install -y qemu-utils extlinux > /dev/null
truncate --size 128M install-image-$LATEST_VERSION.img
sudo qemu-nbd -c /dev/nbd0 -f raw install-image-$LATEST_VERSION.img
sudo mkfs.vfat -n "Install" /dev/nbd0
sudo mkdir ./install
sudo mount /dev/nbd0 ./install
sudo mkdir -p ./install/EFI/BOOT
sudo unzip refind-bin-0.14.2.zip refind-bin-0.14.2/refind/refind_x64.efi
sudo cp refind-bin-0.14.2/refind/refind_x64.efi ./install/EFI/BOOT/BOOTX64.EFI
sudo rm -rf refind-bin-0.14.2
echo -e 'timeout 0\ntextonly\ntextmode 0\nshowtools shutdown, reboot, exit\nmenuentry "Install RouterOS" {\n\tloader /linux\n\toptions "load_ramdisk=1 root=/dev/ram0 -install -hdd"\n}\ndefault_selection /EFI/BOOT/BOOTX64.EFI' \
> refind.conf
sudo cp refind.conf ./install/EFI/BOOT/
sudo rm refind.conf
sudo extlinux --install -H 64 -S 32 ./install/
echo -e 'default system\nLABEL system\n\tKERNEL linux\n\tAPPEND load_ramdisk=1 -install -hdd' \
> syslinux.cfg
sudo cp syslinux.cfg ./install/
sudo rm syslinux.cfg
sudo cp ./BOOTX64.EFI ./install/linux
NPK_FILES=($(find ./all_packages/*.npk))
for ((i=1; i<=${#NPK_FILES[@]}; i++))
do
echo "${NPK_FILES[$i-1]}=>$i.npk"
sudo cp ${NPK_FILES[$i-1]} ./install/$i.npk
done
sudo touch ./install/CHOOSE
sudo touch ./install/autorun.scr
sudo umount /dev/nbd0
sudo qemu-nbd -d /dev/nbd0
sudo rm -rf ./install
sudo zip install-image-$LATEST_VERSION.zip ./install-image-$LATEST_VERSION.img
sudo rm ./install-image-$LATEST_VERSION.img
- name: Cache NetInstall ${{ env.LATEST_VERSION }}${{ env.ARCH }}
if: steps.get_latest.outputs.has_new_version == 'true' && matrix.arch == 'x86' && env.RELEASE == 'true'
id: cache-netinstall
uses: actions/cache@v4
with:
path: |
netinstall.zip
netinstall.tar.gz
key: netinstall-${{ env.LATEST_VERSION }}
- name: Get netinstall ${{ env.LATEST_VERSION }}
if: steps.get_latest.outputs.has_new_version == 'true' && matrix.arch == 'x86' && steps.cache-netinstall.outputs.cache-hit != 'true' && env.RELEASE == 'true'
run: |
sudo wget -nv -O netinstall.zip https://download.mikrotik.com/routeros/$LATEST_VERSION/netinstall-$LATEST_VERSION.zip
sudo wget -nv -O netinstall.tar.gz https://download.mikrotik.com/routeros/$LATEST_VERSION/netinstall-$LATEST_VERSION.tar.gz
- name: Patch netinstall ${{ env.LATEST_VERSION }}${{ env.ARCH }}
if: steps.get_latest.outputs.has_new_version == 'true' && matrix.arch == 'x86' && env.RELEASE == 'true'
run: |
sudo unzip netinstall.zip
sudo -E python3 patch.py netinstall netinstall.exe
sudo zip netinstall-$LATEST_VERSION.zip ./netinstall.exe
sudo tar -xvf netinstall.tar.gz
sudo -E python3 patch.py netinstall netinstall-cli
sudo tar -czvf netinstall-$LATEST_VERSION.tar.gz ./netinstall-cli
- name: Upload Files
if: steps.get_latest.outputs.has_new_version == 'true' && env.RELEASE == 'true'
run: |
sudo mkdir -p ./publish/$LATEST_VERSION
sudo cp CHANGELOG ./publish/$LATEST_VERSION/
sudo cp ./all_packages/*.npk ./publish/$LATEST_VERSION/
sudo chown -R root:root ./publish/
LOCAL_PATH=./publish/$LATEST_VERSION
REMOTE_PATH=${{ secrets.SSH_DIRECTORY }}
SERVER=${{ secrets.SSH_SERVER }}
USER=${{ secrets.SSH_USERNAME }}
PASS=${{ secrets.SSH_PASSWORD }}
PORT=${{ secrets.SSH_PORT }}
CHANNEL=${{ matrix.channel }}
sudo apt-get install -y lftp ssh sshpass > /dev/null 2>&1
sudo -E lftp -u "$USER","$PASS" sftp://$SERVER:$PORT <<EOF
set sftp:auto-confirm yes
mirror --reverse --verbose --only-newer ./publish "$REMOTE_PATH"
bye
EOF
sshpass -p "$PASS" ssh -o StrictHostKeyChecking=no -p $PORT $USER@$SERVER \
"echo $LATEST_VERSION $BUILD_TIME | tee /rw/disk/$REMOTE_PATH/NEWESTa7.$CHANNEL; bash /rw/disk/$REMOTE_PATH/packages.sh /rw/disk/$REMOTE_PATH/$LATEST_VERSION"
sshpass -p "$PASS" ssh -o StrictHostKeyChecking=no -p $PORT $USER@$SERVER \
"chown -R 32768:32768 /rw/disk/$REMOTE_PATH/"
- name: Clear Cloudflare cache
if: steps.get_latest.outputs.has_new_version == 'true' && env.RELEASE == 'true'
run: |
curl --request POST --url "https://api.cloudflare.com/client/v4/zones/fe6831ed6dc9e6235e69ef2a31f2e7fe/purge_cache" \
--header "Authorization: Bearer 9GDQkzU51QXaqzp1qMjyFKpyeJyOdnNoG9GZQaGP" \
--header "Content-Type:application/json" \
--data '{"purge_everything": true}'
- name: Delete Release tag ${{ env.LATEST_VERSION }} ${{ env.ARCH }}
if: steps.get_latest.outputs.has_new_version == 'true' && 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/$LATEST_VERSION$ARCH)
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/$LATEST_VERSION$ARCH
echo "Tag $LATEST_VERSION$ARCH deleted successfully."
curl -X DELETE -H "$HEADER" https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID
echo "Release with tag $LATEST_VERSION$ARCH deleted successfully."
else
echo "Release not found for tag: $LATEST_VERSION)"
fi
- name: Create Release tag ${{ env.LATEST_VERSION }} ${{ env.ARCH }}
if: steps.get_latest.outputs.has_new_version == 'true' && env.RELEASE == 'true'
uses: softprops/action-gh-release@v2
with:
name: "RouterOS ${{ env.LATEST_VERSION }} ${{ env.ARCH }}"
body_path: "CHANGELOG"
tag_name: ${{ env.LATEST_VERSION }}${{ env.ARCH }}
make_latest: ${{ matrix.channel == 'stable'}} && ${{ matrix.arch == 'x86'}}
prerelease: ${{ matrix.channel == 'testing' }}
files: |
mikrotik-${{ env.LATEST_VERSION }}${{ env.ARCH }}.iso
chr-${{ env.LATEST_VERSION }}*.zip
netinstall-${{ env.LATEST_VERSION }}.*
install-image-${{ env.LATEST_VERSION }}.zip
routeros-${{ env.LATEST_VERSION }}${{ env.ARCH }}.npk
all_packages-*-${{ env.LATEST_VERSION }}.zip
- name: Upload Files as Artifact (No Release)
if: steps.get_latest.outputs.has_new_version == 'true' && env.RELEASE == 'false'
uses: actions/upload-artifact@v4
with:
name: mikrotik-${{ env.LATEST_VERSION }}${{ env.ARCH }}
path: |
all_packages-*-${{ env.LATEST_VERSION }}.zip
mikrotik-${{ env.LATEST_VERSION }}${{ env.ARCH }}.iso
chr-${{ env.LATEST_VERSION }}*.zip

570
.github/workflows/patch_v6.yml vendored Normal file
View File

@ -0,0 +1,570 @@
name: Patch RouterOS v6
on:
workflow_dispatch:
inputs:
channel:
description: 'Channel (stable, long-term)'
required: true
default: 'stable'
type: choice
options:
- stable
- long-term
version:
description: "Specify version (e.g., 6.49.19), empty for latest"
required: false
default: ''
type: string
buildtime:
description: "Custom build time, leave empty for original"
required: false
default: ''
type: string
release:
description: "Release to Upload "
required: false
default: false
type: boolean
workflow_call:
inputs:
channel:
required: true
type: string
version:
required: false
type: string
default: ''
buildtime:
required: false
type: string
default: ''
release:
required: false
type: boolean
default: false
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 }}
jobs:
Prepare_Patch:
runs-on: ubuntu-24.04
outputs:
VERSION: ${{ steps.prepare.outputs.VERSION }}
BUILD_TIME: ${{ steps.prepare.outputs.BUILD_TIME }}
ARCHS: ${{ steps.prepare.outputs.ARCHS }}
RELEASE: ${{ steps.prepare.outputs.RELEASE }}
SKIP_BUILD: ${{ steps.prepare.outputs.SKIP_BUILD }}
steps:
- name: Prepare Patch for ${{ inputs.channel }}
id: prepare
run: |
CHANNEL="${{ inputs.channel }}"
VERSION="${{ inputs.version }}"
RELEASE="${{ inputs.release }}"
BUILD_TIME="${{ inputs.buildtime }}"
ARCHS='["x86"]'
SKIP_BUILD=false
if [ -z "$VERSION" ]; then
echo "Checking latest version from $CHANNEL channel..."
TIMESTAMP=$(date +%s)
NEWEST=$(wget -nv -O - "https://${{ env.MIKRO_UPGRADE_URL }}/routeros/NEWEST6.$CHANNEL?t=$TIMESTAMP")
VERSION=$(echo "$NEWEST" | cut -d ' ' -f1)
echo "Official version: $VERSION"
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]]; then
PATCH_NEWEST=$(wget -nv -O - "https://${{ env.CUSTOM_UPGRADE_URL }}/routeros/NEWEST6.$CHANNEL?t=$TIMESTAMP" 2>/dev/null || echo "")
PATCH_VERSION=$(echo "$PATCH_NEWEST" | cut -d ' ' -f1)
echo "Patch version: ${PATCH_VERSION:-none}"
if [ "$PATCH_VERSION" == "$VERSION" ]; then
echo "No new version for $CHANNEL, skipping..."
SKIP_BUILD=true
else
echo "New version $VERSION available, building..."
fi
fi
fi
echo "SKIP_BUILD=$SKIP_BUILD" >> $GITHUB_OUTPUT
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "BUILD_TIME=$BUILD_TIME" >> $GITHUB_OUTPUT
echo "ARCHS=$ARCHS" >> $GITHUB_OUTPUT
echo "RELEASE=$RELEASE" >> $GITHUB_OUTPUT
echo "Version: $VERSION, Archs: $ARCHS"
Patch_RouterOS:
needs: Prepare_Patch
if: needs.Prepare_Patch.outputs.SKIP_BUILD == 'false'
runs-on: ubuntu-24.04
strategy:
matrix:
arch: ${{ fromJson(needs.Prepare_Patch.outputs.ARCHS) }}
fail-fast: false
env:
TZ: 'Asia/Shanghai'
CHANNEL: ${{ inputs.channel }}
ARCH: ${{ matrix.arch }}
VERSION: ${{ needs.Prepare_Patch.outputs.VERSION }}
BUILD_TIME: ${{ needs.Prepare_Patch.outputs.BUILD_TIME }}
BUILD_DIR: "/tmp/build/${{ needs.Prepare_Patch.outputs.VERSION }}"
PUBLISH_DIR: "${{ github.workspace }}/publish/${{ needs.Prepare_Patch.outputs.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
- name: Create Squashfs for Option Package
run: |
echo "Creating Option Package Squashfs for channel: $CHANNEL, arch: $ARCH"
mkdir -p $BUILD_DIR/option-root/bin/
cp ./busybox/busybox_$ARCH $BUILD_DIR/option-root/bin/busybox
chmod +x $BUILD_DIR/option-root/bin/busybox
cp ./keygen/keygen_$ARCH $BUILD_DIR/option-root/bin/keygen
chmod +x $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 <<EOF
import hashlib, crcmod, sys
with open('$BUILD_DIR/option.jg','rb') as f:
data = f.read()
sha1 = hashlib.sha1(data).hexdigest()[:12]
crc = crcmod.mkCrcFun(0x104C11DB7, initCrc=0xFFFFFFFF, rev=False, xorOut=0xFFFFFFFF)(data)
print(f"{sha1} {crc}")
EOF
)
SHA1_PREFIX=$(echo "$HASH" | awk '{print $1}')
CRC=$(echo "$HASH" | awk '{print $2}')
mv $BUILD_DIR/option.jg $BUILD_DIR/option-$SHA1_PREFIX.jg
python3 -E << EOF
import gzip
gzip.open('$BUILD_DIR/option-$SHA1_PREFIX.jg.gz', 'wb', compresslevel=0).write(open('$BUILD_DIR/option-$SHA1_PREFIX.jg', 'rb').read())
EOF
SIZE=$(stat -c %s $BUILD_DIR/option-$SHA1_PREFIX.jg.gz)
mv $BUILD_DIR/option-$SHA1_PREFIX.jg.gz $BUILD_DIR/option-root/home/web/webfig/
tee $BUILD_DIR/option-root/home/web/webfig/option.info > /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"
- 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 -r $BUILD_DIR/iso/* $BUILD_DIR/new_iso/
chmod -R +w $BUILD_DIR/new_iso/
mv $BUILD_DIR/new_iso/*.npk $BUILD_DIR/all_packages/
mv $BUILD_DIR/all_packages/system-$VERSION$ARCH_EXT.npk $BUILD_DIR/
sudo umount $BUILD_DIR/iso
rm -rf $BUILD_DIR/iso
- name: Cache routeros-${{ env.ARCH }}-${{ env.VERSION }}.npk
id: cache-routeros-npk
uses: actions/cache@v5
with:
path: ${{ env.BUILD_DIR }}/routeros-${{ env.ARCH }}-${{ env.VERSION }}.npk
key: routeros-${{ env.ARCH }}-${{ env.VERSION }}.npk
- name: Get routeros-${{ env.ARCH }}-${{ env.VERSION }}.npk
if: steps.cache-routeros-npk.outputs.cache-hit != 'true'
run: |
wget -nv -O $BUILD_DIR/routeros-$ARCH-$VERSION.npk \
https://download.mikrotik.com/routeros/$VERSION/routeros-$ARCH-$VERSION.npk
- 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 -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
OPTION_FROM_NPK=gps-$VERSION$ARCH_EXT.npk
OPTION_NPK=option-$VERSION$ARCH_EXT.npk
python3 npk.py create $BUILD_DIR/all_packages/$OPTION_FROM_NPK $BUILD_DIR/all_packages/$OPTION_NPK option $BUILD_DIR/option.sfs -desc="busybox"
rm -rf $BUILD_DIR/option.sfs
python3 patch.py npk $BUILD_DIR/system-$VERSION$ARCH_EXT.npk -O $BUILD_DIR/all_packages/system-$VERSION$ARCH_EXT.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-$ARCH-$VERSION.npk -O $PUBLISH_DIR/routeros-$ARCH-$VERSION.npk
echo "✅ all package npk files are signed and patched: "
ls -la $PUBLISH_DIR/*.npk
- name: Create ISO & INSTALL image files
run: |
set -e
echo "Creating ISO..."
sudo modprobe nbd
sudo apt-get install syslinux syslinux-common xorriso extlinux qemu-utils -y > /dev/null
unzip $PUBLISH_DIR/all_packages-$ARCH-$VERSION.zip -d $BUILD_DIR/new_iso/
mv $BUILD_DIR/new_iso/isolinux/initrd.rgz $BUILD_DIR/initrd.rgz
python3 patch.py kernel $BUILD_DIR/initrd.rgz -O $BUILD_DIR/new_iso/isolinux/initrd.rgz
rm -f $BUILD_DIR/initrd.rgz
xorriso -as mkisofs -o $PUBLISH_DIR/mikrotik-$VERSION$ARCH_EXT.iso \
-V "MikroTik $VERSION" \
-sysid "" -preparer "MiKroTiK" \
-publisher "" -A "MiKroTiK RouterOS" \
-input-charset utf-8 \
-b isolinux/isolinux.bin \
-c isolinux/boot.cat \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
-R -J \
$BUILD_DIR/new_iso
echo "✅ ISO created: "
ls -la $PUBLISH_DIR/mikrotik-$VERSION$ARCH_EXT.iso
echo "Creating install image..."
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/
cp -vf $BUILD_DIR/new_iso/isolinux/initrd.rgz $BUILD_DIR/install/initrd.rgz
tee $BUILD_DIR/install/syslinux.cfg > /dev/null << 'EOF'
default system
LABEL system
KERNEL linux
APPEND load_ramdisk=1 initrd=initrd.rgz -hdd-install
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
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
rm -rf $BUILD_DIR/new_iso
- name: Create CHR image files
run: |
set -e
echo "Creating CHR image..."
mkdir -p $BUILD_DIR/chr
truncate --size 128M $BUILD_DIR/chr-$VERSION$ARCH_EXT.img
sfdisk $BUILD_DIR/chr-$VERSION$ARCH_EXT.img <<EOF
label: dos
unit: sectors
start=1, size=, type=83, bootable
EOF
dd if=$BUILD_DIR/chr-$VERSION$ARCH_EXT.img of=$BUILD_DIR/mbr.bin bs=1 count=512 conv=notrunc
echo "FA31C08ED0BC007C89E65007501FFBFCBF0006B90001F2A5EA1D060000BEBE07B304803C807423803C00750983C610FECB75EFCD18BE9B06AC3C00740B56BB0700B40ECD105EEBF0EBFE8B148B4C0289F5BF0500BB007CB8010257CD135F730C31C0CD134F75EDBE7C06EBCCBFFE7D813D55AA75C289EEEA007C00004572726F72206C6F6164696E67206F7065726174696E672073797374656D004D697373696E67206F7065726174696E672073797374656D0000000000" | xxd -r -p | sudo dd of=$BUILD_DIR/mbr.bin bs=1 count=440 conv=notrunc
printf '\x01' | sudo dd of=$BUILD_DIR/mbr.bin bs=1 count=1 seek=336 conv=notrunc
dd if=$BUILD_DIR/mbr.bin of=$BUILD_DIR/chr-$VERSION$ARCH_EXT.img bs=1 count=512 conv=notrunc
sudo qemu-nbd -d /dev/nbd0
sudo qemu-nbd -c /dev/nbd0 -f raw $BUILD_DIR/chr-$VERSION$ARCH_EXT.img
sudo partprobe /dev/nbd0
sudo mkfs.ext3 -F -L "system" -m 0 /dev/nbd0p1
sudo mount /dev/nbd0p1 $BUILD_DIR/chr
sudo mkdir -p $BUILD_DIR/chr/{boot,bin,rw/disk,var/pdb/{routeros-x86,option,dude}}
mkdir -p $BUILD_DIR/{boot,bin}
python3 npk.py extract $PUBLISH_DIR/routeros-$ARCH-$VERSION.npk bin/milo $BUILD_DIR/bin/milo
chmod +x $BUILD_DIR/bin/milo
python3 npk.py extract $PUBLISH_DIR/routeros-$ARCH-$VERSION.npk boot/vmlinuz-64 $BUILD_DIR/boot/vmlinuz-64
python3 npk.py extract $PUBLISH_DIR/routeros-$ARCH-$VERSION.npk boot/initrd.rgz $BUILD_DIR/boot/initrd.rgz
python3 npk.py extract $PUBLISH_DIR/routeros-$ARCH-$VERSION.npk boot/map $BUILD_DIR/boot/map
python3 npk.py extract $PUBLISH_DIR/routeros-$ARCH-$VERSION.npk boot/milo.conf $BUILD_DIR/boot/milo.conf
python3 npk.py extract $PUBLISH_DIR/routeros-$ARCH-$VERSION.npk boot/vmlinuz $BUILD_DIR/boot/vmlinuz
python3 npk.py extract $PUBLISH_DIR/routeros-$ARCH-$VERSION.npk boot/vmlinuz-smp $BUILD_DIR/boot/vmlinuz-smp
sudo mv $BUILD_DIR/boot $BUILD_DIR/chr/
sudo mv $BUILD_DIR/bin $BUILD_DIR/chr/
sudo cp $PUBLISH_DIR/routeros-$ARCH-$VERSION.npk $BUILD_DIR/chr/var/pdb/routeros-x86/image
sudo cp $PUBLISH_DIR/option-$VERSION$ARCH_EXT.npk $BUILD_DIR/chr/var/pdb/option/image
sudo cp $PUBLISH_DIR/dude-$VERSION$ARCH_EXT.npk $BUILD_DIR/chr/var/pdb/dude/image
echo -e '#!/bin/sh\n# This script will be executed *before* RouterOS *loader* start.\n# You can put your own initialization stuff in here\n' |sudo tee $BUILD_DIR/chr/rw/disk/rc.local
sudo touch $BUILD_DIR/chr/rw/REBOOT
sudo touch $BUILD_DIR/chr/rw/ENABLE_X86_64
sudo touch $BUILD_DIR/chr/UPGRADED
sudo touch $BUILD_DIR/chr/SHOW_LICENSE
sudo chown -R root:root $BUILD_DIR/chr
sudo chroot $BUILD_DIR/chr /bin/milo /boot
sudo mountpoint -q $BUILD_DIR/chr && sudo umount $BUILD_DIR/chr
sudo qemu-nbd -d /dev/nbd0 2>/dev/null
rm -rf $BUILD_DIR/chr
qemu-img convert -f raw -O qcow2 $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.qcow2
qemu-img convert -f raw -O vmdk $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.vmdk
qemu-img convert -f raw -O vpc $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.vhd
qemu-img convert -f raw -O vhdx $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.vhdx
qemu-img convert -f raw -O vdi $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.vdi
echo "✅ CHR created: "
ls -la $BUILD_DIR/chr-$VERSION$ARCH_EXT.*
(cd $BUILD_DIR && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.qcow2.zip chr-$VERSION$ARCH_EXT.qcow2 && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.vmdk.zip chr-$VERSION$ARCH_EXT.vmdk && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.vhd.zip chr-$VERSION$ARCH_EXT.vhd && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.vhdx.zip chr-$VERSION$ARCH_EXT.vhdx && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.vdi.zip chr-$VERSION$ARCH_EXT.vdi &&
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.img.zip chr-$VERSION$ARCH_EXT.img )
rm $BUILD_DIR/chr-$VERSION$ARCH_EXT.*
- name: Upload Files as Artifact (No Release)
if: needs.Prepare_Patch.outputs.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: needs.Prepare_Patch.outputs.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: needs.Prepare_Patch.outputs.RELEASE == 'true'
run: |
sed -i "1i Build Time:$BUILD_TIME" $PUBLISH_DIR/CHANGELOG
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: Create Release tag ${{ env.VERSION }}${{ env.ARCH_EXT }}
if: needs.Prepare_Patch.outputs.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.PUBLISH_DIR}}/CHANGELOG
tag_name: ${{ env.VERSION }}${{ env.ARCH_EXT }}
make_latest: false
prerelease: false
files: |
${{env.PUBLISH_DIR}}/*.zip
${{env.PUBLISH_DIR}}/*.iso
${{env.PUBLISH_DIR}}/routeros*.npk
${{env.PUBLISH_DIR}}/*.zip.sha256
${{env.PUBLISH_DIR}}/*.iso.sha256
${{env.PUBLISH_DIR}}/routeros*.npk.sha256
- name: Upload Files
if: needs.Prepare_Patch.outputs.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
sed -i '1d' $PUBLISH_DIR/CHANGELOG
echo $VERSION $BUILD_TIME | tee $PUBLISH_DIR/info
touch $PUBLISH_DIR/$CHANNEL
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 <<EOF
set sftp:auto-confirm yes
mirror --reverse --verbose --only-newer $PUBLISH_DIR "$REMOTE_PATH"
bye
EOF
- name: Clear Cloudflare cache
if: needs.Prepare_Patch.outputs.RELEASE == 'true'
run: |
curl --request POST --url "https://api.cloudflare.com/client/v4/zones/fe6831ed6dc9e6235e69ef2a31f2e7fe/purge_cache" \
--header "Authorization: Bearer 9GDQkzU51QXaqzp1qMjyFKpyeJyOdnNoG9GZQaGP" \
--header "Content-Type:application/json" \
--data '{"purge_everything": true}'
Update_Release:
needs: [Prepare_Patch,Patch_RouterOS]
if: |
needs.Prepare_Patch.outputs.SKIP_BUILD == 'false' &&
needs.Patch_RouterOS.result == 'success' &&
needs.Prepare_Patch.outputs.RELEASE == 'true'
runs-on: ubuntu-24.04
env:
CHANNEL: ${{ inputs.channel }}
VERSION: ${{ needs.Prepare_Patch.outputs.VERSION }}
steps:
- name: Update Release
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
REMOTE_PATH=${{ secrets.SSH_DIRECTORY }}
SERVER=${{ secrets.SSH_SERVER }}
USER=${{ secrets.SSH_USERNAME }}
PASS=${{ secrets.SSH_PASSWORD }}
PORT=${{ secrets.SSH_PORT }}
sshpass -p "$PASS" ssh -o StrictHostKeyChecking=no -p $PORT $USER@$SERVER \
"ln -sf /root/$REMOTE_PATH/$VERSION/info /rw/disk/$REMOTE_PATH/NEWEST6.$CHANNEL; \
ln -sf /root/$REMOTE_PATH/$VERSION/info /rw/disk/$REMOTE_PATH/NEWESTa6.$CHANNEL; \
chown -R 32768:32768 /rw/disk/$REMOTE_PATH"
echo "✅ Updated release info."

993
.github/workflows/patch_v7.yml vendored Normal file
View File

@ -0,0 +1,993 @@
name: Patch RouterOS v7
on:
workflow_dispatch:
inputs:
channel:
description: 'Channel (stable, long-term, testing, development)'
required: true
default: 'stable'
type: choice
options:
- stable
- long-term
- testing
- development
arch:
description: 'Architecture (x86, arm64)'
required: true
default: 'all'
type: choice
options:
- all
- x86
- arm64
version:
description: "Specify version (e.g., 7.19.2), empty for latest"
required: false
default: ''
type: string
buildtime:
description: "Custom build time, leave empty for original"
required: false
default: ''
type: string
release:
description: "Release to Upload "
required: false
default: false
type: boolean
workflow_call:
inputs:
channel:
required: true
type: string
arch:
required: false
type: string
default: 'all'
version:
required: false
type: string
default: ''
buildtime:
required: false
type: string
default: ''
release:
required: false
type: boolean
default: false
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 }}
jobs:
Prepare_Patch:
runs-on: ubuntu-24.04
outputs:
VERSION: ${{ steps.prepare.outputs.VERSION }}
BUILD_TIME: ${{ steps.prepare.outputs.BUILD_TIME }}
ARCHS: ${{ steps.prepare.outputs.ARCHS }}
RELEASE: ${{ steps.prepare.outputs.RELEASE }}
SKIP_BUILD: ${{ steps.prepare.outputs.SKIP_BUILD }}
steps:
- name: Prepare Patch for ${{ inputs.channel }}
id: prepare
run: |
CHANNEL="${{ inputs.channel }}"
ARCH_INPUT="${{ inputs.arch }}"
VERSION="${{ inputs.version }}"
RELEASE="${{ inputs.release }}"
BUILD_TIME="${{ inputs.buildtime }}"
if [ "$ARCH_INPUT" == "all" ]; then
ARCHS='["x86", "arm64"]'
else
ARCHS=$(jq -c -n --arg arch "$ARCH_INPUT" '[$arch]')
fi
SKIP_BUILD=false
if [ -z "$VERSION" ]; then
echo "Checking latest version from $CHANNEL channel..."
TIMESTAMP=$(date +%s)
NEWEST=$(wget -nv -O - "https://${{ env.MIKRO_UPGRADE_URL }}/routeros/NEWESTa7.$CHANNEL?t=$TIMESTAMP")
VERSION=$(echo "$NEWEST" | cut -d ' ' -f1)
echo "Official version: $VERSION"
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]]; then
PATCH_NEWEST=$(wget -nv -O - "https://${{ env.CUSTOM_UPGRADE_URL }}/routeros/NEWESTa7.$CHANNEL?t=$TIMESTAMP" 2>/dev/null || echo "")
PATCH_VERSION=$(echo "$PATCH_NEWEST" | cut -d ' ' -f1)
echo "Patch version: ${PATCH_VERSION:-none}"
if [ "$PATCH_VERSION" == "$VERSION" ]; then
echo "No new version for $CHANNEL, skipping..."
SKIP_BUILD=true
else
echo "New version $VERSION available, building..."
fi
fi
fi
echo "SKIP_BUILD=$SKIP_BUILD" >> $GITHUB_OUTPUT
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "BUILD_TIME=$BUILD_TIME" >> $GITHUB_OUTPUT
echo "ARCHS=$ARCHS" >> $GITHUB_OUTPUT
echo "RELEASE=$RELEASE" >> $GITHUB_OUTPUT
echo "Version: $VERSION, Archs: $ARCHS"
Patch_RouterOS:
needs: Prepare_Patch
if: needs.Prepare_Patch.outputs.SKIP_BUILD == 'false'
runs-on: ubuntu-24.04
strategy:
matrix:
arch: ${{ fromJson(needs.Prepare_Patch.outputs.ARCHS) }}
fail-fast: false
env:
TZ: 'Asia/Shanghai'
CHANNEL: ${{ inputs.channel }}
ARCH: ${{ matrix.arch }}
VERSION: ${{ needs.Prepare_Patch.outputs.VERSION }}
BUILD_TIME: ${{ needs.Prepare_Patch.outputs.BUILD_TIME }}
BUILD_DIR: "/tmp/build/${{ needs.Prepare_Patch.outputs.VERSION }}"
PUBLISH_DIR: "${{ github.workspace }}/publish/${{ needs.Prepare_Patch.outputs.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: Create Squashfs for Option and Python Package
run: |
echo "Creating Option Package Squashfs for channel: $CHANNEL, arch: $ARCH"
mkdir -p $BUILD_DIR/option-root/bin/
cp ./busybox/busybox_$ARCH $BUILD_DIR/option-root/bin/busybox
chmod +x $BUILD_DIR/option-root/bin/busybox
cp ./keygen/keygen_$ARCH $BUILD_DIR/option-root/bin/keygen
chmod +x $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 <<EOF
import hashlib, crcmod, sys
with open('$BUILD_DIR/option.jg','rb') as f:
data = f.read()
sha1 = hashlib.sha1(data).hexdigest()[:12]
crc = crcmod.mkCrcFun(0x104C11DB7, initCrc=0xFFFFFFFF, rev=False, xorOut=0xFFFFFFFF)(data)
print(f"{sha1} {crc}")
EOF
)
SHA1_PREFIX=$(echo "$HASH" | awk '{print $1}')
CRC=$(echo "$HASH" | awk '{print $2}')
mv $BUILD_DIR/option.jg $BUILD_DIR/option-$SHA1_PREFIX.jg
python3 -E << EOF
import gzip
gzip.open('$BUILD_DIR/option-$SHA1_PREFIX.jg.gz', 'wb', compresslevel=0).write(open('$BUILD_DIR/option-$SHA1_PREFIX.jg', 'rb').read())
EOF
SIZE=$(stat -c %s $BUILD_DIR/option-$SHA1_PREFIX.jg.gz)
mv $BUILD_DIR/option-$SHA1_PREFIX.jg.gz $BUILD_DIR/option-root/home/web/webfig/
tee $BUILD_DIR/option-root/home/web/webfig/option.info > /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' && needs.Prepare_Patch.outputs.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' && needs.Prepare_Patch.outputs.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' && needs.Prepare_Patch.outputs.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: Create 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 /usr/lib/ISOLINUX/isolinux.bin $BUILD_DIR/new_iso/isolinux/
cp /usr/lib/syslinux/modules/bios/ldlinux.c32 $BUILD_DIR/new_iso/isolinux/
echo -e 'default system\n\nlabel system\n\tkernel linux\n\tappend load_ramdisk=1 root=/dev/ram0 -install -cdrom' | tee $BUILD_DIR/new_iso/isolinux/isolinux.cfg
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
#修改为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
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"
}
default_selection /EFI/BOOT/BOOTX64.EFI
EOF
cp -v $BUILD_DIR/new_iso/isolinux/linux $BUILD_DIR/efiboot/linux.x86_64
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 "✅ ISO created: "
ls -la $PUBLISH_DIR/mikrotik-$VERSION$ARCH_EXT.iso
fi
rm -rf $BUILD_DIR/new_iso
rm -rf $BUILD_DIR/efiboot
- name: Create CHR image files
run: |
set -e
OVF_TEMPLATE='<?xml version="1.0" encoding="UTF-8"?>
<Envelope vmw:buildId="build-18663434" xmlns="http://schemas.dmtf.org/ovf/envelope/1" xmlns:cim="http://schemas.dmtf.org/wbem/wscim/1/common" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1" xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:vmw="http://www.vmware.com/schema/ovf" xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<References>
<File ovf:href="{VMDK_FILE}" ovf:id="file1" ovf:size="{FILE_SIZE}"/>
</References>
<DiskSection>
<Info>Virtual disk information</Info>
<Disk ovf:capacity="128" 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>
<Network ovf:name="VM Network">
<Description>The VM Network network</Description>
</Network>
</NetworkSection>
<VirtualSystem ovf:id="vm">
<Info>A virtual machine</Info>
<Name>MikroTik_RouterOS_CHR</Name>
<OperatingSystemSection ovf:id="100" vmw:osType="other3xLinux64Guest">
<Info>The kind of installed guest operating system</Info>
</OperatingSystemSection>
<VirtualHardwareSection>
<Info>Virtual hardware requirements</Info>
<System>
<vssd:ElementName>Virtual Hardware Family</vssd:ElementName>
<vssd:InstanceID>0</vssd:InstanceID>
<vssd:VirtualSystemIdentifier>MikroTik_RouterOS_CHR</vssd:VirtualSystemIdentifier>
<vssd:VirtualSystemType>vmx-10</vssd:VirtualSystemType>
</System>
<Item>
<rasd:AllocationUnits>hertz * 10^6</rasd:AllocationUnits>
<rasd:Description>Number of Virtual CPUs</rasd:Description>
<rasd:ElementName>1 virtual CPU(s)</rasd:ElementName>
<rasd:InstanceID>1</rasd:InstanceID>
<rasd:ResourceType>3</rasd:ResourceType>
<rasd:VirtualQuantity>1</rasd:VirtualQuantity>
</Item>
<Item>
<rasd:AllocationUnits>byte * 2^20</rasd:AllocationUnits>
<rasd:Description>Memory Size</rasd:Description>
<rasd:ElementName>1024MB of memory</rasd:ElementName>
<rasd:InstanceID>2</rasd:InstanceID>
<rasd:ResourceType>4</rasd:ResourceType>
<rasd:VirtualQuantity>1024</rasd:VirtualQuantity>
</Item>
<Item>
<rasd:Address>0</rasd:Address>
<rasd:Description>IDE Controller</rasd:Description>
<rasd:ElementName>ideController0</rasd:ElementName>
<rasd:InstanceID>3</rasd:InstanceID>
<rasd:ResourceType>5</rasd:ResourceType>
</Item>
<Item>
<rasd:AddressOnParent>0</rasd:AddressOnParent>
<rasd:ElementName>disk0</rasd:ElementName>
<rasd:HostResource>ovf:/disk/vmdisk1</rasd:HostResource>
<rasd:InstanceID>4</rasd:InstanceID>
<rasd:Parent>3</rasd:Parent>
<rasd:ResourceType>17</rasd:ResourceType>
</Item>
<Item>
<rasd:AddressOnParent>1</rasd:AddressOnParent>
<rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
<rasd:Connection>VM Network</rasd:Connection>
<rasd:Description>VmxNet3 ethernet adapter on &quot;VM Network&quot;</rasd:Description>
<rasd:ElementName>ethernet0</rasd:ElementName>
<rasd:InstanceID>5</rasd:InstanceID>
<rasd:ResourceSubType>VmxNet3</rasd:ResourceSubType>
<rasd:ResourceType>10</rasd:ResourceType>
<vmw:Config ovf:required="false" vmw:key="slotInfo.pciSlotNumber" vmw:value="160"/>
<vmw:Config ovf:required="false" vmw:key="connectable.allowGuestControl" vmw:value="true"/>
</Item>
<Item ovf:required="false">
<rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
<rasd:ElementName>video</rasd:ElementName>
<rasd:InstanceID>6</rasd:InstanceID>
<rasd:ResourceType>24</rasd:ResourceType>
</Item>
<Item ovf:required="false">
<rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
<rasd:ElementName>vmci</rasd:ElementName>
<rasd:InstanceID>7</rasd:InstanceID>
<rasd:ResourceSubType>vmware.vmci</rasd:ResourceSubType>
<rasd:ResourceType>1</rasd:ResourceType>
</Item>
<vmw:Config ovf:required="false" vmw:key="firmware" vmw:value="{FIRMWARE_TYPE}"/>
</VirtualHardwareSection>
<AnnotationSection ovf:required="false">
<Info>A human-readable annotation</Info>
<Annotation>MikroTik RouterOS CHR</Annotation>
</AnnotationSection>
</VirtualSystem>
</Envelope>'
sudo apt-get install -y parted > /dev/null
mkdir -p $BUILD_DIR/chr/{boot,routeros}
if [ "$ARCH" == "x86" ]; then
echo "Creating x86 uefi CHR..."
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
chmod +x $BUILD_DIR/chr/milo
#要求MBR分区表第一个分区类型必须为0x8300,否则ERROR: could not find disk!
truncate --size 128M $BUILD_DIR/chr-$VERSION$ARCH_EXT.img
sgdisk \
--zap-all \
--set-alignment=1 \
--new=1:34:+32M --typecode=1:8300 --change-name=1:"RouterOS Boot" \
--new=2:0:0 --typecode=2:8300 --change-name=2:"RouterOS" \
--gpttombr=1:2 \
$BUILD_DIR/chr-$VERSION$ARCH_EXT.img
dd if=$BUILD_DIR/chr-$VERSION$ARCH_EXT.img of=$BUILD_DIR/mbr.bin bs=1 count=512 conv=notrunc
printf '\x01' | dd of=$BUILD_DIR/mbr.bin bs=1 count=1 seek=336 conv=notrunc
sgdisk \
--zap-all \
--set-alignment=1 \
--new=1:34:+32M --typecode=1:EF00 --change-name=1:"RouterOS Boot" \
--new=2:0:0 --typecode=2:8300 --change-name=2:"RouterOS" \
$BUILD_DIR/chr-$VERSION$ARCH_EXT.img
dd if=$BUILD_DIR/mbr.bin of=$BUILD_DIR/chr-$VERSION$ARCH_EXT.img bs=1 count=512 conv=notrunc
rm $BUILD_DIR/mbr.bin
sudo qemu-nbd -d /dev/nbd0
sudo qemu-nbd -c /dev/nbd0 -f raw $BUILD_DIR/chr-$VERSION$ARCH_EXT.img
sudo partprobe /dev/nbd0
sudo mkfs.vfat -n "Boot" /dev/nbd0p1
sudo mkfs.ext4 -F -L "RouterOS" -m 0 /dev/nbd0p2
sudo mount -o uid=$(id -u),gid=$(id -g) /dev/nbd0p1 $BUILD_DIR/chr/boot
sudo mkdir -p $BUILD_DIR/chr/boot/EFI/BOOT
sudo cp -v $BUILD_DIR/chr/BOOTX64.EFI $BUILD_DIR/chr/boot/EFI/BOOT/BOOTX64.EFI
sudo umount /dev/nbd0p1
sudo mount /dev/nbd0p2 $BUILD_DIR/chr/routeros
sudo mkdir -p $BUILD_DIR/chr/routeros/{var/pdb/{system,option},boot,bin,rw/disk}
# cmdStartVerify bin/milo
sudo cp -v $BUILD_DIR/chr/milo $BUILD_DIR/chr/routeros/bin/milo
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
echo -e '#!/bin/sh\n# This script will be executed *before* RouterOS *loader* start.\n# You can put your own initialization stuff in here\n' |sudo tee $BUILD_DIR/chr/routeros/rw/disk/rc.local
sudo umount /dev/nbd0p2
sudo qemu-nbd -d /dev/nbd0
qemu-img convert -f raw -O qcow2 $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.qcow2
qemu-img convert -f raw -O vmdk $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.vmdk
qemu-img convert -f raw -O vpc $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.vhd
qemu-img convert -f raw -O vhdx $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.vhdx
qemu-img convert -f raw -O vdi $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.vdi
VMDK_FILE=chr-$VERSION$ARCH_EXT-disk1.vmdk
qemu-img convert -f raw -O vmdk -o subformat=streamOptimized \
$BUILD_DIR/chr-$VERSION$ARCH_EXT.img \
$BUILD_DIR/$VMDK_FILE
FILE_SIZE=$(stat -c %s "$BUILD_DIR/$VMDK_FILE")
OVF_FILE=chr-$VERSION$ARCH_EXT.ovf
OVA_FILE=chr-$VERSION$ARCH_EXT.ova
echo "$OVF_TEMPLATE" | sed -e "s|{VMDK_FILE}|$VMDK_FILE|g" \
-e "s|{FILE_SIZE}|$FILE_SIZE|g" \
-e "s|{FIRMWARE_TYPE}|efi|g" | tee $BUILD_DIR/$OVF_FILE
(cd $BUILD_DIR && tar -cvf "$OVA_FILE" "$OVF_FILE" "$VMDK_FILE")
rm $BUILD_DIR/$VMDK_FILE
rm $BUILD_DIR/$OVF_FILE
echo "✅ UEFI CHR created: "
ls -la $BUILD_DIR/chr-$VERSION$ARCH_EXT.*
(cd $BUILD_DIR && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.ova.zip chr-$VERSION$ARCH_EXT.ova && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.qcow2.zip chr-$VERSION$ARCH_EXT.qcow2 && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.vmdk.zip chr-$VERSION$ARCH_EXT.vmdk && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.vhd.zip chr-$VERSION$ARCH_EXT.vhd && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.vhdx.zip chr-$VERSION$ARCH_EXT.vhdx && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.vdi.zip chr-$VERSION$ARCH_EXT.vdi &&
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.img.zip chr-$VERSION$ARCH_EXT.img )
rm $BUILD_DIR/chr-$VERSION$ARCH_EXT.*
FIRMWARE_TYPE="bios"
echo "Creating x86 legacy bios CHR..."
truncate --size 128M $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img
sgdisk \
--zap-all \
--set-alignment=1 \
--new=1:34:+32M --typecode=1:8300 --change-name=1:"RouterOS Boot" --attributes=1:set:2 \
--new=2:0:-4096 --typecode=2:8300 --change-name=2:"RouterOS" \
--gpttombr=1:2 \
$BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img
dd if=$BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img of=$BUILD_DIR/mbr.bin bs=1 count=512 conv=notrunc
echo "FA31C08ED0BC007C89E65007501FFBFCBF0006B90001F2A5EA1D060000BEBE07B304803C807423803C00750983C610FECB75EFCD18BE9B06AC3C00740B56BB0700B40ECD105EEBF0EBFE8B148B4C0289F5BF0500BB007CB8010257CD135F730C31C0CD134F75EDBE7C06EBCCBFFE7D813D55AA75C289EEEA007C00004572726F72206C6F6164696E67206F7065726174696E672073797374656D004D697373696E67206F7065726174696E672073797374656D0000000000" | xxd -r -p | sudo dd of=$BUILD_DIR/mbr.bin bs=1 count=440 conv=notrunc
printf '\x01' | sudo dd of=$BUILD_DIR/mbr.bin bs=1 count=1 seek=336 conv=notrunc
printf '\x80' | sudo dd of=$BUILD_DIR/mbr.bin bs=1 count=1 seek=446 conv=notrunc
sgdisk \
--zap-all \
--set-alignment=1 \
--new=1:34:+32M --typecode=1:8300 --change-name=1:"RouterOS Boot" --attributes=1:set:2 \
--new=2:0:-4096 --typecode=2:8300 --change-name=2:"RouterOS" \
$BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img
dd if=$BUILD_DIR/mbr.bin of=$BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img bs=1 count=512 conv=notrunc
sudo rm $BUILD_DIR/mbr.bin
sudo qemu-nbd -d /dev/nbd0
sudo qemu-nbd -c /dev/nbd0 -f raw $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img
sudo partprobe /dev/nbd0
sudo mkfs.ext2 -F -m 0 -L "RouterOS Boot" /dev/nbd0p1
sudo mkfs.ext4 -F -m 0 -L "RouterOS" /dev/nbd0p2
sudo mount /dev/nbd0p1 $BUILD_DIR/chr/boot
sudo mkdir -p $BUILD_DIR/chr/boot/EFI/BOOT
sudo cp -vf $BUILD_DIR/chr/BOOTX64.EFI $BUILD_DIR/chr/boot/EFI/BOOT/BOOTX64.EFI
sudo $BUILD_DIR/chr/milo $BUILD_DIR/chr/boot
sudo umount /dev/nbd0p1
sudo mount /dev/nbd0p2 $BUILD_DIR/chr/routeros
sudo mkdir -p $BUILD_DIR/chr/routeros/{var/pdb/{system,option},boot,bin,rw/disk}
sudo cp -v $BUILD_DIR/chr/milo $BUILD_DIR/chr/routeros/bin/milo
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
echo -e '#!/bin/sh\n# This script will be executed *before* RouterOS *loader* start.\n# You can put your own initialization stuff in here\n' | sudo tee $BUILD_DIR/chr/routeros/rw/disk/rc.local
sudo umount /dev/nbd0p2
sudo qemu-nbd -d /dev/nbd0
qemu-img convert -f raw -O qcow2 $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.qcow2
qemu-img convert -f raw -O vmdk $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.vmdk
qemu-img convert -f raw -O vpc $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.vhd
qemu-img convert -f raw -O vhdx $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.vhdx
qemu-img convert -f raw -O vdi $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.vdi
VMDK_FILE=chr-$VERSION$ARCH_EXT-disk1.vmdk
qemu-img convert -f raw -O vmdk -o subformat=streamOptimized \
$BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img \
$BUILD_DIR/$VMDK_FILE
FILE_SIZE=$(stat -c %s "$BUILD_DIR/$VMDK_FILE")
OVF_FILE=chr-$VERSION$ARCH_EXT-legacy-bios.ovf
OVA_FILE=chr-$VERSION$ARCH_EXT-legacy-bios.ova
echo "$OVF_TEMPLATE" | sed -e "s|{VMDK_FILE}|$VMDK_FILE|g" \
-e "s|{FILE_SIZE}|$FILE_SIZE|g" \
-e "s|{FIRMWARE_TYPE}|bios|g" | tee $BUILD_DIR/$OVF_FILE
(cd $BUILD_DIR && tar -cvf "$OVA_FILE" "$OVF_FILE" "$VMDK_FILE")
rm $BUILD_DIR/$VMDK_FILE
rm $BUILD_DIR/$OVF_FILE
echo "✅ legacy BIOS CHR created: "
ls -la $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.*
(cd $BUILD_DIR && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.ova.zip chr-$VERSION$ARCH_EXT-legacy-bios.ova && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.qcow2.zip chr-$VERSION$ARCH_EXT-legacy-bios.qcow2 && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.vmdk.zip chr-$VERSION$ARCH_EXT-legacy-bios.vmdk && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.vhd.zip chr-$VERSION$ARCH_EXT-legacy-bios.vhd && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.vhdx.zip chr-$VERSION$ARCH_EXT-legacy-bios.vhdx && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.vdi.zip chr-$VERSION$ARCH_EXT-legacy-bios.vdi && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.img.zip chr-$VERSION$ARCH_EXT-legacy-bios.img )
rm $BUILD_DIR/chr-$VERSION$ARCH_EXT-legacy-bios.*
rm $BUILD_DIR/chr/BOOTX64.EFI
rm $BUILD_DIR/chr/milo
elif [ "$ARCH" == "arm64" ]; then
echo "Creating arm64 CHR..."
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
#要求MBR分区表第一个分区类型必须为0x8300,否则ERROR: could not find disk!
truncate --size 128M $BUILD_DIR/chr-$VERSION$ARCH_EXT.img
sgdisk \
--zap-all \
--set-alignment=1 \
--new=1:34:+32M --typecode=1:8300 --change-name=1:"RouterOS Boot" \
--new=2:0:0 --typecode=2:8300 --change-name=2:"RouterOS" \
--gpttombr=1:2 \
$BUILD_DIR/chr-$VERSION$ARCH_EXT.img
dd if=$BUILD_DIR/chr-$VERSION$ARCH_EXT.img of=$BUILD_DIR/mbr.bin bs=1 count=512 conv=notrunc
printf '\x01' | dd of=$BUILD_DIR/mbr.bin bs=1 count=1 seek=336 conv=notrunc
sgdisk \
--zap-all \
--set-alignment=1 \
--new=1:34:+32M --typecode=1:EF00 --change-name=1:"RouterOS Boot" \
--new=2:0:0 --typecode=2:8300 --change-name=2:"RouterOS" \
$BUILD_DIR/chr-$VERSION$ARCH_EXT.img
dd if=$BUILD_DIR/mbr.bin of=$BUILD_DIR/chr-$VERSION$ARCH_EXT.img bs=1 count=512 conv=notrunc
rm $BUILD_DIR/mbr.bin
sudo qemu-nbd -d /dev/nbd0
sudo qemu-nbd -c /dev/nbd0 -f raw $BUILD_DIR/chr-$VERSION$ARCH_EXT.img
sudo partprobe /dev/nbd0
sudo mkfs.vfat -n "Boot" "/dev/nbd0p1"
sudo mkfs.ext4 -F -m 0 -L "RouterOS" "/dev/nbd0p2"
sudo mount -o uid=$(id -u),gid=$(id -g) /dev/nbd0p1 $BUILD_DIR/chr/boot
sudo mkdir -p $BUILD_DIR/chr/boot/EFI/BOOT
sudo mv -v $BUILD_DIR/chr/BOOTAA64.EFI $BUILD_DIR/chr/boot/EFI/BOOT/BOOTAA64.EFI
sudo umount /dev/nbd0p1
sudo mount /dev/nbd0p2 $BUILD_DIR/chr/routeros
sudo mkdir -p $BUILD_DIR/chr/routeros/{var/pdb/{system,option},boot,rw/disk}
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
echo -e '#!/bin/sh\n# This script will be executed *before* RouterOS *loader* start.\n# You can put your own initialization stuff in here\n' | sudo tee $BUILD_DIR/chr/routeros/rw/disk/rc.local
sudo umount /dev/nbd0p2
sudo qemu-nbd -d /dev/nbd0
qemu-img convert -f raw -O qcow2 $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.qcow2
qemu-img convert -f raw -O vmdk $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.vmdk
qemu-img convert -f raw -O vpc $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.vhd
qemu-img convert -f raw -O vhdx $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.vhdx
qemu-img convert -f raw -O vdi $BUILD_DIR/chr-$VERSION$ARCH_EXT.img $BUILD_DIR/chr-$VERSION$ARCH_EXT.vdi
VMDK_FILE=chr-$VERSION$ARCH_EXT-disk1.vmdk
qemu-img convert -f raw -O vmdk -o subformat=streamOptimized \
$BUILD_DIR/chr-$VERSION$ARCH_EXT.img \
$BUILD_DIR/$VMDK_FILE
FILE_SIZE=$(stat -c %s "$BUILD_DIR/$VMDK_FILE")
OVF_FILE=chr-$VERSION$ARCH_EXT.ovf
OVA_FILE=chr-$VERSION$ARCH_EXT.ova
echo "$OVF_TEMPLATE" | sed -e "s|{VMDK_FILE}|$VMDK_FILE|g" \
-e "s|{FILE_SIZE}|$FILE_SIZE|g" \
-e "s|{FIRMWARE_TYPE}|efi|g" | tee $BUILD_DIR/$OVF_FILE
(cd $BUILD_DIR && tar -cvf "$OVA_FILE" "$OVF_FILE" "$VMDK_FILE")
rm $BUILD_DIR/$VMDK_FILE
rm $BUILD_DIR/$OVF_FILE
echo "✅ CHR created: "
ls -la $BUILD_DIR/chr-$VERSION$ARCH_EXT.*
(cd $BUILD_DIR && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.ova.zip chr-$VERSION$ARCH_EXT.ova && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.qcow2.zip chr-$VERSION$ARCH_EXT.qcow2 && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.vmdk.zip chr-$VERSION$ARCH_EXT.vmdk && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.vhd.zip chr-$VERSION$ARCH_EXT.vhd && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.vhdx.zip chr-$VERSION$ARCH_EXT.vhdx && \
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.vdi.zip chr-$VERSION$ARCH_EXT.vdi &&
zip $PUBLISH_DIR/chr-$VERSION$ARCH_EXT.img.zip chr-$VERSION$ARCH_EXT.img )
rm $BUILD_DIR/chr-$VERSION$ARCH_EXT.*
fi
rm -rf $BUILD_DIR/chr
- name: Upload Files as Artifact (No Release)
if: needs.Prepare_Patch.outputs.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: needs.Prepare_Patch.outputs.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: needs.Prepare_Patch.outputs.RELEASE == 'true'
run: |
sed -i "1i Build Time:$BUILD_TIME" $PUBLISH_DIR/CHANGELOG
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: Create Release tag ${{ env.VERSION }}${{ env.ARCH_EXT }}
if: needs.Prepare_Patch.outputs.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.PUBLISH_DIR}}/CHANGELOG
tag_name: ${{ env.VERSION }}${{ env.ARCH_EXT }}
make_latest: ${{ env.CHANNEL == 'stable' && matrix.arch == 'x86'}}
prerelease: ${{ env.CHANNEL == 'testing'}}
files: |
${{env.PUBLISH_DIR}}/*.zip
${{env.PUBLISH_DIR}}/*.iso
${{env.PUBLISH_DIR}}/routeros*.npk
${{env.PUBLISH_DIR}}/*.zip.sha256
${{env.PUBLISH_DIR}}/*.iso.sha256
${{env.PUBLISH_DIR}}/routeros*.npk.sha256
- name: Upload Files
if: needs.Prepare_Patch.outputs.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
sed -i '1d' $PUBLISH_DIR/CHANGELOG
echo $VERSION $BUILD_TIME | tee $PUBLISH_DIR/info
touch $PUBLISH_DIR/$CHANNEL
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 <<EOF
set sftp:auto-confirm yes
mirror --reverse --verbose --only-newer $PUBLISH_DIR "$REMOTE_PATH"
bye
EOF
- name: Clear Cloudflare cache
if: needs.Prepare_Patch.outputs.RELEASE == 'true'
run: |
curl --request POST --url "https://api.cloudflare.com/client/v4/zones/fe6831ed6dc9e6235e69ef2a31f2e7fe/purge_cache" \
--header "Authorization: Bearer 9GDQkzU51QXaqzp1qMjyFKpyeJyOdnNoG9GZQaGP" \
--header "Content-Type:application/json" \
--data '{"purge_everything": true}'
Update_Release:
needs: [Prepare_Patch,Patch_RouterOS]
if: |
needs.Prepare_Patch.outputs.SKIP_BUILD == 'false' &&
needs.Patch_RouterOS.result == 'success' &&
needs.Prepare_Patch.outputs.RELEASE == 'true'
runs-on: ubuntu-24.04
env:
CHANNEL: ${{ inputs.channel }}
VERSION: ${{ needs.Prepare_Patch.outputs.VERSION }}
steps:
- name: Update Release
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
REMOTE_PATH=${{ secrets.SSH_DIRECTORY }}
SERVER=${{ secrets.SSH_SERVER }}
USER=${{ secrets.SSH_USERNAME }}
PASS=${{ secrets.SSH_PASSWORD }}
PORT=${{ secrets.SSH_PORT }}
sshpass -p "$PASS" ssh -o StrictHostKeyChecking=no -p $PORT $USER@$SERVER \
"bash /rw/disk/$REMOTE_PATH/packages.sh /rw/disk/$REMOTE_PATH/$VERSION; \
ln -sf /root/$REMOTE_PATH/$VERSION/info /rw/disk/$REMOTE_PATH/NEWESTa7.$CHANNEL; \
chown -R 32768:32768 /rw/disk/$REMOTE_PATH"
echo "✅ Updated: packages.csv"

1
.gitignore vendored
View File

@ -3,4 +3,5 @@ __pycache__/
venv/
app/
loader/
test/
test_*.py

View File

@ -1,7 +1,8 @@
# Contributor Covenant Code of Conduct
# WTF Code of Conduct (WTFCoC)
*"Do What The Fuck You Want, But Don't Be A Dick"*
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience...
📄([https://www.contributor-covenant.org/version/2/1/code_of_conduct/](https://www.contributor-covenant.org/version/2/1/code_of_conduct/)
1. **Do whatever you want** with the code
2. **Don't be a dick** to others
3. **No bureaucracy** - handle problems like adults
4. **You're on your own** - this isn't a safe space
5. **Final rule**: If this bothers you, re-read the WTFPL license name (words 2-4)

679
LICENSE
View File

@ -1,674 +1,13 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Preamble
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
0. You just DO WHAT THE FUCK YOU WANT TO.

105
README.md
View File

@ -1,55 +1,69 @@
[![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)
# MikroTik RouterOS Patch [[English](README_EN.md)]
# MikroTik RouterOS Patch
[![Patch Mikrotik RouterOS](https://github.com/elseif/MikroTikPatch/actions/workflows/main.yml/badge.svg)](https://github.com/elseif/MikroTikPatch/actions/workflows/main.yml)
![Cloud Status](https://img.shields.io/endpoint?url=https://mikrotik.ltd/status/cloud)
### [[Discord](https://discord.gg/keV6MWQFtX)] [[Telegram](https://t.me/mikrotikpatch)] [[Keygen(Telegram Bot)](https://t.me/ROS_Keygen_Bot)]
[![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)
### 国内镜像下载 密码: elseif
[ [123pan](https://www.123pan.com/s/IpxgTd-BYjQh.html) ]
[ [7.15.3](https://elseif.lanzouj.com/b00cre7g7a)]
[ [7.15.3-arm64](https://elseif.lanzouj.com/b00cre7g8b) ]
[ [6.49.15](https://elseif.lanzouj.com/b00crdq4ji) ]
[ [6.49.13](https://elseif.lanzouj.com/b00crdq4kj) ]
支持:在线更新、在线授权、云备份、DDNS
## ⚠️ 重要声明
**此项目及工具仅供测试用途。使用风险自负。生产环境请使用官方授权版本。**
![](image/install.png)
![](image/routeros.png)
## 架构x86、arm64
- **主页:** https://mikrotik.ltd/
- **演示:** https://demo.mikrotik.ltd/
- **授权: 安装option.npk后将自动授予最高级别许可证。**
- **源代码:** https://github.com/elseif/MikroTikPatch
- **授权BOT** https://t.me/ROS_Keygen_Bot
- **Docker** `docker run -d --privileged --name chr ghcr.io/elseif/chr:latest`
### x86模式授权许可
![](image/x86.png)
### x86模式在线授权(v6.x)
![](image/renew_v6.png)
### Chr模式在线授权
![](image/renew.png)
### Chr模式授权许可
![](image/chr.png)
## 架构arm64, arm, mipsbe, mmips, ppc, smips, x86
- **主页:** https://routeros.ltd/
- **授权: 需要赞助***CHR版本(x86/Arm64)支持在线直接获取授权*
- **功能:** 支持在线自定义制作品牌包
![](image/arm.png)
![](image/mips.png)
## 支持的云功能
| 功能 | 命令 |
|------|------|
| 在线升级 | `system/package/update/install` |
| DDNS | `ip/cloud/set ddns-enabled=yes` |
| 云备份 | `/system/backup/cloud/upload-file action=create-and-upload password=any` |
## 如何使用Shell
安装 option-{version}.npk 包
telnet到RouterOS,用户名devel,密码与admin的密码相同
## 如何授权许可
进入shell
运行 keygen
参考上图。
Chr镜像支持在线授权许可
## 如何使用Python
安装 python3-{version}.npk 包
telnet到RouterOS,用户名devel,密码与admin的密码相同
运行 python -V
### npk.py
对npk文件进行解包修改创建签名和验证
### patch.py
替换公钥并签名
## 所有的修补操作都自动运行在[Github Action](https://github.com/elseif/MikroTikPatch/blob/main/.github/workflows/)。
## 启用容器模式(无需物理重启)
1. 安装 `option.npk` 包;
2. 打开终端执行:`system/device-mode/update container=yes`
3. 打开新终端执行:`system/shell cmd="reboot -f"`
## 通过终端进入shell
- 安装 `option.npk`
- 打开终端执行:`/system/shell``/sh`
## 通过ssh或telnet进入shell
- 安装 `option.npk`
- ssh或telnet登录用户名为:`devel`,密码与`admin`密码相同。
## x86架构支持CHR和x86模式切换
- 安装 `option.npk`
- 进入shell执行`keygen chr``keygen x86`
## 支持开机自动运行脚本
- 安装 `option.npk`
- 创建或者编辑文件目录下的`rc.local`文件,即:`/rw/disk/rc.local`文件
- 写入脚本内容,例如:
```bash
#!/bin/sh
# This script will be executed *before* RouterOS *loader* start.
# You can put your own initialization stuff in here
echo "Hello, world!"
```
- 开机启动时将看到输出增加:`Starting rc.local...`
- 启动后进入shell执行`cat /ram/startup.catlog | grep 'Hello'` 查看输出。
### 更多关于RouterOS的信息请查看: https://manual.mikrotik.com/
### 感谢赞助
[ZMTO](https://console.zmto.com)
[DartNode(aff)](https://dartnode.com?aff=SnazzyLobster067) | [ZMTO(aff)](https://console.zmto.com/?affid=1588) | [Vultr(aff)](https://www.vultr.com/?ref=9807160-9J)
[![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
@ -59,3 +73,8 @@

View File

@ -1,51 +0,0 @@
[![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)
# MikroTik RouterOS Patch [[中文](README.md)]
### [[Discord](https://discord.gg/keV6MWQFtX)] [[Telegram](https://t.me/mikrotikpatch)] [[Keygen(Telegram Bot)](https://t.me/ROS_Keygen_Bot)]
### Download [Latest Patched](https://github.com/elseif/MikroTikPatch/releases/latest) iso file,install it and enjoy.
### CHR image is both support BIOS and UEFI boot mode.
### Support online upgrade,online license,cloud backup,cloud DDNS
![](image/install.png)
![](image/routeros.png)
### license RouterOS for x86.
![](image/x86.png)
### Renew license for x86 v6.x
![](image/renew_v6.png)
### Renew license for chr
![](image/renew.png)
### license RouterOS for chr
![](image/chr.png)
![](image/arm.png)
![](image/mips.png)
## How to use shell
install option-{version}.npk package
run telnet to routeros with username devel and password is same as admin
## How to license RouterOS
telnet to routeros with username devel and password is same as admin
run keygen
chr mode could use renew lincense online
## How to use python3
install python3-{version}.npk package
run telnet to routeros with username devel and password is same as admin
run python -V
### npk.py
SignVerifyCreate, Extract npk file.
### patch.py
Patch public key and sign NPK files
## Thanks for sponsoring
[ZMTO](https://console.zmto.com/)
## all patches are applied automatically with [Github Action](https://github.com/elseif/MikroTikPatch/blob/main/.github/workflows/).

View File

@ -1,64 +0,0 @@
[![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)
# Patch para MikroTik RouterOS [[中文](README.md)]
### [[Discord](https://discord.gg/keV6MWQFtX)] [[Telegram](https://t.me/mikrotikpatch)] [[Keygen (Bot do Telegram)](https://t.me/ROS_Keygen_Bot)]
### Baixe a [ISO modificada mais recente](https://github.com/elseif/MikroTikPatch/releases/latest), instale e aproveite.
### A imagem CHR suporta modo de boot tanto BIOS quanto UEFI.
### Suporte a atualização online, licença online, backup em nuvem e DDNS em nuvem
![](image/install.png)
![](image/routeros.png)
### Licenciar o RouterOS para x86
![](image/x86.png)
### Renovar a licença para x86 v6.x
![](image/renew_v6.png)
### Renovar a licença para CHR
![](image/renew.png)
### Licenciar o RouterOS para CHR
![](image/chr.png)
![](image/arm.png)
![](image/mips.png)
## Como usar o shell
```bash
instale o pacote option-{versão}.npk
acesse via telnet o routeros com o usuário devel e a senha igual a do usuário admin
```
## Como licenciar o RouterOS
```bash
acesse via telnet o routeros com o usuário devel e a senha igual a do admin
execute o keygen
modo CHR pode usar a renovação de licença online
```
## Como usar o Python 3
```bash
instale o pacote python3-{versão}.npk
acesse via telnet o routeros com o usuário devel e a senha igual a do admin
execute `python -V`
```
### npk.py
```bash
Assina, verifica, cria e extrai arquivos .npk
```
### patch.py
```bash
Altera a chave pública e assina arquivos .npk
```
## Thanks for sponsoring
[ZMTO](https://console.zmto.com/)
## Todos os patches são aplicados automaticamente com [GitHub Actions](https://github.com/elseif/MikroTikPatch/blob/main/.github/workflows/)

Binary file not shown.

Binary file not shown.

BIN
busybox/busybox_arm64 Normal file

Binary file not shown.

Binary file not shown.

355
chr.sh
View File

@ -1,15 +1,340 @@
curl -# -L https://github.com/elseif/MikroTikPatch/releases/download/7.19.3/chr-7.19.3-legacy-bios.img.zip -o /tmp/chr.img.zip && cd /tmp && \
gunzip -c chr.img.zip > chr.img && \
STORAGE=`lsblk | grep disk | cut -d ' ' -f 1 | head -n 1` && \
echo STORAGE is $STORAGE && \
ETH=`ip route show default | sed -n 's/.* dev \([^\ ]*\) .*/\1/p'` && \
echo ETH is $ETH && \
ADDRESS=`ip addr show $ETH | grep global | cut -d' ' -f 6 | head -n 1` && \
echo ADDRESS is $ADDRESS && \
GATEWAY=`ip route list | grep default | cut -d' ' -f 3` && \
echo GATEWAY is $GATEWAY && \
sleep 5 && \
dd if=chr.img of=/dev/$STORAGE bs=4M oflag=sync && \
echo "Ok, reboot" && \
echo 1 > /proc/sys/kernel/sysrq && \
echo b > /proc/sysrq-trigger
#!/bin/bash
set -e
_ask() {
local _redo=0
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="Storage Device:"
MSG_ADDRESS="IP Address:"
MSG_GATEWAY="Gateway:"
MSG_DNS="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_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_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_storage() {
STORAGE=$(lsblk | grep disk | head -1 | awk '{print $1}')
ask_until "$MSG_STORAGE_DEVICE" "$STORAGE"
STORAGE=$resp
}
confirm_address() {
ETH=$(ip route show default | grep '^default' | sed -n 's/.* dev \([^\ ]*\) .*/\1/p')
MAC=$(cat /sys/class/net/$ETH/address | tr 'a-z' 'A-Z')
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)
if [[ $V7 == 1 && $BOOT_MODE == "BIOS" ]]; then
IMG_URL="https://github.com/elseif/MikroTikPatch/releases/download/$VERSION/chr-$VERSION-legacy-bios.img.zip"
else
IMG_URL="https://github.com/elseif/MikroTikPatch/releases/download/$VERSION/chr-$VERSION.img.zip"
fi
;;
aarch64)
IMG_URL="https://github.com/elseif/MikroTikPatch/releases/download/$VERSION-arm64/chr-$VERSION-arm64.img.zip"
;;
*)
echo "$MSG_UNSUPPORTED_ARCH"
exit 1
;;
esac
echo "$MSG_FILE_DOWNLOAD $(basename "$IMG_URL")"
http_get "$IMG_URL" "/tmp/chr.img.zip"
cd /tmp
extract_zip "chr.img.zip" chr.img
}
create_autorun() {
LOOP=$(losetup -f)
if losetup -P "$LOOP" chr.img; then
sleep 1
MNT=/tmp/chr
mkdir -p $MNT
PARTITION=$([ "$V7" == 1 ] && echo "p2" || echo "p1")
if mount "${LOOP}${PARTITION}" "$MNT"; then
confirm_address
RANDOM_ADMIN_PASS=$(head -c 512 /dev/urandom | tr -dc 'A-HJ-KMNP-Za-hj-kmnp-z2-9' | 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=[/interface ethernet get [find mac-address=$MAC] name]
/ip route add gateway=$GATEWAY
EOF
cat "$MNT/rw/autorun.scr"
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 "$MSG_ERROR_LOOP"
echo "$MSG_AUTO_RUN_FILE_NOT_CREATED"
fi
}
write_reboot() {
confirm_storage
printf "$MSG_WARNING\n" "$STORAGE"
ask_yesno "$MSG_CONFIRM_CONTINUE"
if [ $? -ne 0 ]; then
echo "$MSG_OPERATION_ABORTED"
exit 1
fi
sync
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_reboot
exit 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 725 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

770
index.html Normal file
View File

@ -0,0 +1,770 @@
<!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();
} else {
decided = true; clearTimeout(timer);
}
});
})();
</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;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
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', // Enable class-based dark mode
theme: {
extend: {
fontFamily: {
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>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm text-slate-50 dark:text-gray-900"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.6,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body text-slate-50 dark:text-gray-900"></path>
</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/main.yml" target="_blank"><img src="https://github.com/elseif/MikroTikPatch/actions/workflows/main.yml/badge.svg" alt="Patch Mikrotik RouterOS"></a>
<img src="https://img.shields.io/endpoint?logo=icloud&url=https://mikrotik.ltd/status/cloud" alt="Cloud 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>
<a class="github-button" href="https://github.com/elseif/MikroTikPatch/fork" data-icon="octicon-repo-forked" data-size="large" data-show-count="true" aria-label="Fork elseif/MikroTikPatch on GitHub">Fork</a>
</div>
</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">
<p data-i18n="infoLabel1"></p>
<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">
<span class="sr-only">Toggle theme</span>
<span class="iconify text-xl sun-icon" data-icon="ph:sun-bold"></span>
<span class="iconify text-xl moon-icon" data-icon="ph:moon-bold"></span>
</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">
<option value="en">English</option>
<option value="zh">中文</option>
</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">
<input type="checkbox" id="gh-proxy" name="gh-proxy" class="sr-only peer">
<div class="w-11 h-6 bg-slate-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
</label>
</div>
</div>
</div>
</div>
</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">
<span class="ml-2 text-sm font-medium">curl</span>
</label>
<label class="flex items-center px-2 cursor-pointer">
<input type="radio" name="tool" value="wget" 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">
<span class="ml-2 text-sm font-medium">wget</span>
</label>
</div>
</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>
</div>
</div>
</main>
<!-- 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>
<!-- Telegram Keyget Bot link -->
<a href="https://t.me/ROS_Keygen_Bot" 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">Keygen Bot</span>
</a>
</div>
</div>
</footer>
</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',
trash: 'ph:trash-bold',
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();
const id = setTimeout(() => controller.abort(), timeout);
const res = await fetch(url, { signal: controller.signal });
clearTimeout(id);
if (res.ok) {
const text = await res.text();
return text.split(" ")[0]; // Extract version number from response
}
} catch (e) {
console.warn("fetch failed or timeout:", e);
}
return defaultValue; // Return fallback version if fetch fails
}
// Internationalization (i18n) translations for English and Chinese
const i18n = {
en: {
language: "Language",
infoLabel1:`If you are already running <b>patched</b> RouterOS, upgrading to the latest version can be done by clicking on <b>"Check For Updates"</b> in <b>QuickSet or System > Packages</b> menu in WebFig or WinBox.`,
infoLabel2:`For more information about <b>install and upgrade</b> see the <a href="https://help.mikrotik.com/docs/" target="_blank" class="text-blue-500 dark:text-blue-400 hover:underline">Documentation</a>. For more information about <b>patch</b> see the <a href="https://github.com/elseif/MikroTikPatch" target="_blank" class="text-blue-500 dark:text-blue-400 hover:underline">MikroTikPatch</a>.`,
installCmdLabel:"Install Command",
proxyLabel:"Enable GitHub Proxy Accelerator",
loading:"Loading latest versions...",
quickInformation:"Quick Information",
settings:"Settings",
theme:"Theme",
changelog:"Changelog",
loadingChangelog:"Loading changelog...",
changelogError:"Failed to load changelog. Please try again later.",
},
zh: {
language: "语言",
infoLabel1:`如果你已经在运行<b>Patch</b>过的RouterOS可以通过WebFig或WinBox中的<b>QuickSet或System > Packages</b>菜单点击<b>"Check For Updates"</b>来升级到最新版本。`,
infoLabel2:`有关<b>安装和升级</b>的更多信息,请参阅<a href="https://help.mikrotik.com/docs/" target="_blank" class="text-blue-500 dark:text-blue-400 hover:underline">文档</a><br>有关<b>Patch</b>的更多信息,请参阅<a href="https://github.com/elseif/MikroTikPatch" target="_blank" class="text-blue-500 dark:text-blue-400 hover:underline">MikroTikPatch</a>`,
installCmdLabel:"安装命令",
proxyLabel:"启用GitHub代理加速",
loading:"正在加载最新版本...",
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 === 'installCmdLabel') {
el.innerHTML = `<span class="iconify mr-2 text-blue-500" data-icon="ph:terminal-window-bold"></span>${i18n[lang][key]}`;
}
else {
el.innerHTML = i18n[lang][key];
}
}
});
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');
moonIcon.classList.add('hidden');
} else {
sunIcon.classList.add('hidden');
moonIcon.classList.remove('hidden');
}
}
// 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?ts=${Date.now()}`, "7.19.4"),
fetch_latest_versions(`https://upgrade.mikrotik.ltd/routeros/NEWESTa7.testing?ts=${Date.now()}`, "7.20rc1"),
fetch_latest_versions(`https://upgrade.mikrotik.ltd/routeros/NEWEST6.long-term?ts=${Date.now()}`, "6.49.18"),
fetch_latest_versions(`https://upgrade.mikrotik.ltd/routeros/NEWEST6.stable?ts=${Date.now()}`, "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",
versions:[ { title:"Stable", version:routeros7_stable }, { title:"Testing", version:routeros7_testing } ],
groups: [
{ title:"ARM64 / AMPERE", arch:"arm64", downloads:[ { title:"Main package", type:"main" }, { title:"Extra packages", type:"extra" }, { title:"ISO image for AMPERE", type:"iso" } ] },
{ title:"X86", arch:"x86", downloads:[ { title:"Main package", type:"main" }, { title:"Extra packages", type:"extra" }, { title:"CD Image", type:"iso" }, { title:"Install image", type:"install" } ] },
{ title:"GENERAL", arch:"general", downloads:[ { title:"Netinstall (Windows)", type:"netinstall_windows" }, { title:"Netinstall (CLI Linux)", type:"netinstall_linux_cli" }, { title:"Changelog", type:"changlog" } ] }
]
},
{
title:"RouterOS v6",
versions:[ { title:"Long-Term", version:routeros6_longterm }, { title:"Stable", version:routeros6_stable } ],
groups: [
{ title:"X86", arch:"x86", downloads:[ { title:"Main package", type:"main" },{ title:"Extra packages", type:"extra" }, { title:"CD Image", type:"iso" }, { title:"Install image", type:"install" }, { title:"Changelog", type:"changlog" } ] }
]
},
{
title:"Cloud Hosted Router",
versions:[ { title:"Long-Term", version:routeros6_longterm }, { title:"Stable", version:routeros6_stable }, { title:"Stable", version:routeros7_stable }, { title:"Testing", version:routeros7_testing } ],
groups:[
{ title:"X86", arch:"x86", downloads:[ { title:"Main package", type:"main" }, { title:"Extra packages", type:"extra" }, { title:"VHDX image", type:"vhdx" }, { title:"VMDK image", type:"vmdk" }, { title:"VDI image", type:"vdi" }, { title:"VirtualPC image", type:"vhd" }, { title:"Raw disk image", type:"img" }, { title:"OVA template", type:"ova" } ] },
{ title:"ARM64 / AMPERE", arch:"arm64", downloads:[ { title:"Main package", type:"main" }, { title:"Extra packages", type:"extra" }, { title:"VHDX image", type:"vhdx" }, { title:"VMDK image", type:"vmdk" }, { title:"VDI image", type:"vdi" }, { title:"VirtualPC image", type:"vhd" }, { title:"Raw disk image", type:"img" } ] }
]
},
];
// 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`);
// Main package for RouterOS v6
if (type === "main" && version.charAt(0) === "6") files.push(`routeros-x86-${version}.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; // 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";
summary.innerHTML = `<span>${download.title}</span><span class="iconify text-2xl transition-transform duration-300 group-open:rotate-180" data-icon="ph:caret-down-bold"></span>`;
const content = document.createElement("div");
content.className = "px-6 pb-6";
const table = document.createElement("div");
table.className = "overflow-x-auto";
table.innerHTML = `
<table class="w-full min-w-max text-sm text-left text-slate-500 dark:text-slate-400">
<thead class="text-xs text-slate-700 dark:text-slate-200 uppercase bg-slate-50/80 dark:bg-gray-700/80">
<tr>
<th scope="col" class="px-6 py-3 rounded-l-lg">Package / Architecture</th>
${download.versions.map(v => `<th scope="col" class="px-6 py-3 text-center">${v.version}<br><span class="font-normal normal-case">${v.title}</span></th>`).join("")}
<th class="rounded-r-lg"></th>
</tr>
</thead>
<tbody>
${download.groups.map(g => `
<tr class="bg-slate-100/80 dark:bg-gray-700/50">
<th colspan="${download.versions.length + 2}" class="px-6 py-2 text-base font-semibold text-slate-800 dark:text-white">${g.title}</th>
</tr>
${g.downloads.map(d => `
<tr class="bg-transparent border-b last:border-b-0 border-slate-200 dark:border-gray-700 hover:bg-slate-50/50 dark:hover:bg-gray-800/50 transition-colors">
<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"><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>`;
const links = files.map(file => {
const url = `${baseUrl(v.version, g.arch)}${file}`;
let label = "Download";
if (download.title === "Cloud Hosted Router" && file.includes('chr')) {
if (v.version.charAt(0) === "6" ){
label = "Legacy";
} else {
label = file.includes("legacy-bios") ? "Legacy" : "UEFI";
}
}
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("");
return `<td class="px-6 py-4 text-center space-x-2">${links}</td>`;
}).join("")}
<td></td>
</tr>
`).join("")}
`).join("")}
</tbody>
</table>
`;
content.appendChild(table);
accordion.appendChild(summary);
accordion.appendChild(content);
container.appendChild(accordion);
});
// GitHub proxy functionality
const ghProxyCheckbox = document.getElementById("gh-proxy");
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";
// Update all download links with/without GitHub proxy
function updateAllDownloadLinks() {
// 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) {
link.href = link.href.replace("https://github.com/", "https://gh-proxy.com/https://github.com/");
} else if (!isProxyEnabled && hasProxy) {
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");
activeBtn ? updateCommand(activeBtn.dataset.version) : updateCommand();
}
// GitHub proxy toggle event listener
ghProxyCheckbox.addEventListener("change", () => {
localStorage.setItem("gh-proxy-enabled", ghProxyCheckbox.checked ? "true" : "false");
updateAllDownloadLinks();
});
// 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>
<button data-version="${routeros6_longterm}" class="px-3 py-1.5 text-sm font-medium rounded-md hover:bg-blue-200 dark:hover:bg-gray-600 transition-all">v6 Long-Term</button>
<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 _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>${_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");
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();
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");
}
});
});
updateCommand();
// Copy command to clipboard functionality
copyBtn.addEventListener("click", () => {
const code = commandDiv.querySelector("code").innerText;
navigator.clipboard.writeText(code).then(() => {
copyBtn.querySelector('.copy-icon').classList.add('hidden');
copyBtn.querySelector('.check-icon').classList.remove('hidden');
setTimeout(() => {
copyBtn.querySelector('.copy-icon').classList.remove('hidden');
copyBtn.querySelector('.check-icon').classList.add('hidden');
}, 2000);
}).catch(err => console.error("Copy failed:", err));
});
});
</script>
</body>
</html>

Binary file not shown.

Binary file not shown.

BIN
keygen/keygen_arm64 Normal file

Binary file not shown.

Binary file not shown.

173
npk.py
View File

@ -1,6 +1,6 @@
import struct,zlib
import argparse,os
import argparse,os,tempfile
from datetime import datetime
from dataclasses import dataclass
from enum import IntEnum
@ -51,7 +51,45 @@ class NpkInfo:
def name(self,value:str):
self._name = value[:16].encode().ljust(16,b'\x00')
@staticmethod
def decode_version(value:bytes):
def encode_version(version: str) -> bytes:
numbers = []
build = None
i = 0
n = len(version)
while i < n:
if version[i].isdigit():
num_str = ''
while i < n and version[i].isdigit():
num_str += version[i]
i += 1
numbers.append(int(num_str))
elif version[i] == '.':
i += 1
elif version[i].isalpha():
build = ''
while i < n and version[i].isalpha():
build += version[i]
i += 1
else:
i += 1
major = numbers[0] if len(numbers) > 0 else 0
minor = numbers[1] if len(numbers) > 1 else 0
revision = numbers[2] if len(numbers) > 2 else 0
if build == 'alpha':
build = 97
elif build == 'beta':
build = 98
elif build == 'rc':
build = 99
elif build == 'test':
build = 102
revision |= 0x80
else:# 'final'
build = 102
revision &= 0x7f
return struct.pack('4B',revision,build,minor,major)
@staticmethod
def decode_version(value:bytes)->str:
revision,build,minor,major = struct.unpack_from('4B',value)
if build == 97:
build = 'alpha'
@ -64,31 +102,10 @@ class NpkInfo:
build = 'test'
revision &= 0x7f
else:
build = 'final'
build = ''
else:
build = 'unknown'
return f'{major}.{minor}.{revision}.{build}'
@staticmethod
def encode_version(value:str):
s = value.split('.')
if 4 != len(s) and s[3] in [ 'alpha', 'beta', 'rc','final', 'test']:
raise ValueError('Invalid version string')
major = int(s[0])
minor = int(s[1])
revision = int(s[2])
if s[3] == 'alpha':
build = 97
elif s[3] == 'beta':
build = 98
elif s[3] == 'rc':
build = 99
elif s[3] == 'final':
build = 102
revision &= 0x7f
else: #'test'
build = 102
revision |= 0x80
return struct.pack('4B',revision,build,minor,major)
build = 'unknown'
return f'{major}.{minor}{build + str(revision) if build !="" else "." + str(revision) if revision != 0 else ""}'
@property
def version(self)->str:
return self.decode_version(self._version)
@ -137,7 +154,7 @@ class NpkFileContainer:
self._items= items
def serialize(self)->bytes:
compressed_data = b''
compressor = zlib.compressobj()
compressor = zlib.compressobj(level=0)
for item in self._items:
data = struct.pack(self._format, item.perm,item.type,item.usr_or_grp, item.modify_time,item.revision,item.rc,item.minor,item.major,item.create_time,item.unknow,len(item.data),len(item.name))
data += item.name + item.data
@ -207,7 +224,48 @@ class NovaPackage(Package):
self._parts.append(NpkPartItem(NpkPartID(part_id),NpkInfo.unserialize_from(part_data)))
else:
self._parts.append(NpkPartItem(NpkPartID(part_id),part_data))
def set_null_block(self):
def rebuild_squashfs(data):
with tempfile.TemporaryDirectory() as tmp_dir:
squashfs_file = os.path.join(tmp_dir, 'squashfs.sfs')
squashfs_root = os.path.join(tmp_dir, 'squashfs-root')
with open(squashfs_file, 'wb') as f:
f.write(data)
os.system(f'unsquashfs -d {squashfs_root} {squashfs_file}')
os.remove(squashfs_file)
os.system(f'mksquashfs {squashfs_root} {squashfs_file} -no-recovery -noappend -exit-on-error -quiet -comp xz -no-xattrs -b 256k -all-root')
with open(squashfs_file, 'rb') as f:
return f.read()
def get_size(package,size):
for part in package._parts:
size += 6
size += len(part.data)
return size
def set_null(package,offset):
has_squashfs = False
for part in package._parts:
if part.id == NpkPartID.SQUASHFS and len(part.data) >= 4 and part.data[:4] in [b'hsqs',b'sqsh']:
part.data = rebuild_squashfs(part.data)
has_squashfs = True
if has_squashfs:
count = offset
for part in package._parts:
count += 6
if part.id == NpkPartID.NULL_BLOCK:
break
count += len(part.data)
count += 6
pad_len = (4096 - (count % 4096)) % 4096
package[NpkPartID.NULL_BLOCK].data = b'\x00' * pad_len
set_null(self,8)
offset = get_size(self,8)
for package in self._packages:
set_null(package,offset)
offset += get_size(package,0)
def get_digest(self,hash_fnc,package:Package=None)->bytes:
parts = package._parts if package else self._parts
for part in parts:
@ -229,17 +287,32 @@ class NovaPackage(Package):
import hashlib
from mikro import mikro_kcdsa_sign,mikro_eddsa_sign
build_time = os.getenv('BUILD_TIME',None)
self.set_null_block()
if len(self._packages) > 0:
if build_time:
self[NpkPartID.PKG_INFO].data._build_time = int(build_time)
else:
build_time = self[NpkPartID.PKG_INFO].data._build_time
os.environ['BUILD_TIME'] = str(build_time)
github_env = os.getenv('GITHUB_ENV')
if github_env:
with open(github_env, 'a') as f:
f.write(f"BUILD_TIME={build_time}\n")
for package in self._packages:
if len(package[NpkPartID.SIGNATURE].data) != 20+48+64:
package[NpkPartID.SIGNATURE].data = b'\0'*(20+48+64)
if build_time:
package[NpkPartID.NAME_INFO].data._build_time = int(build_time)
else:
build_time = self[NpkPartID.NAME_INFO].data._build_time
os.environ['BUILD_TIME'] = str(build_time)
github_env = os.getenv('GITHUB_ENV')
if github_env:
with open(github_env, 'a') as f:
f.write(f"BUILD_TIME={build_time}\n")
sha1_digest = self.get_digest(hashlib.new('SHA1'),package)
sha256_digest = self.get_digest(hashlib.new('SHA256'),package)
kcdsa_signature = mikro_kcdsa_sign(sha256_digest[:20],kcdsa_private_key)
kcdsa_signature = mikro_kcdsa_sign(sha1_digest,kcdsa_private_key)
eddsa_signature = mikro_eddsa_sign(sha256_digest,eddsa_private_key)
package[NpkPartID.SIGNATURE].data = sha1_digest + kcdsa_signature + eddsa_signature
else:
@ -247,9 +320,16 @@ class NovaPackage(Package):
self[NpkPartID.SIGNATURE].data = b'\0'*(20+48+64)
if build_time:
self[NpkPartID.NAME_INFO].data._build_time = int(build_time)
else:
build_time = self[NpkPartID.NAME_INFO].data._build_time
os.environ['BUILD_TIME'] = str(build_time)
github_env = os.getenv('GITHUB_ENV')
if github_env:
with open(github_env, 'a') as f:
f.write(f"BUILD_TIME={build_time}\n")
sha1_digest = self.get_digest(hashlib.new('SHA1'))
sha256_digest = self.get_digest(hashlib.new('SHA256'))
kcdsa_signature = mikro_kcdsa_sign(sha256_digest[:20],kcdsa_private_key)
kcdsa_signature = mikro_kcdsa_sign(sha1_digest,kcdsa_private_key)
eddsa_signature = mikro_eddsa_sign(sha256_digest,eddsa_private_key)
self[NpkPartID.SIGNATURE].data = sha1_digest + kcdsa_signature + eddsa_signature
@ -263,9 +343,9 @@ class NovaPackage(Package):
signature = package[NpkPartID.SIGNATURE].data
if sha1_digest != signature[:20]:
return False
if not mikro_kcdsa_verify(sha256_digest[:20],signature[20:68],kcdsa_public_key):
if not mikro_kcdsa_verify(sha1_digest,signature[20:68],kcdsa_public_key):
return False
if not mikro_eddsa_verify(sha256_digest,signature[68:132],eddsa_public_key):
if len(signature) == 132 and not mikro_eddsa_verify(sha256_digest,signature[68:132],eddsa_public_key):
return False
else:
sha1_digest = self.get_digest(hashlib.new('SHA1'))
@ -273,11 +353,10 @@ class NovaPackage(Package):
signature = self[NpkPartID.SIGNATURE].data
if sha1_digest != signature[:20]:
return False
if not mikro_kcdsa_verify(sha256_digest[:20],signature[20:68],kcdsa_public_key):
if not mikro_kcdsa_verify(sha1_digest,signature[20:68],kcdsa_public_key):
return False
if not mikro_eddsa_verify(sha256_digest,signature[68:132],eddsa_public_key):
if len(signature) == 132 and not mikro_eddsa_verify(sha256_digest,signature[68:132],eddsa_public_key):
return False
return True
def save(self,file):
@ -325,6 +404,10 @@ if __name__=='__main__':
create_option_parser.add_argument('name',type=str,help='NPK name')
create_option_parser.add_argument('squashfs',type=str,help='NPK squashfs file')
create_option_parser.add_argument('-desc','--description',type=str,help='NPK description')
extract_parser = subparsers.add_parser('extract',help='Extract npk file')
extract_parser.add_argument('input',type=str, help='Input file')
extract_parser.add_argument('name',type=str,help='File to extract')
extract_parser.add_argument('output',type=str,help='Output to file')
args = parser.parse_args()
kcdsa_private_key = bytes.fromhex(os.environ['CUSTOM_LICENSE_PRIVATE_KEY'])
eddsa_private_key = bytes.fromhex(os.environ['CUSTOM_NPK_SIGN_PRIVATE_KEY'])
@ -355,5 +438,23 @@ if __name__=='__main__':
option_npk.sign(kcdsa_private_key,eddsa_private_key)
option_npk.save(args.output)
print(f'Created {args.output}')
elif args.command =='extract':
print(f'Extracting {args.name} from {args.input}')
npk = NovaPackage.load(args.input)
file_container = None
if len(npk._packages) > 0:
for package in npk._packages:
if package[NpkPartID.NAME_INFO].data.name == 'system':
file_container = NpkFileContainer.unserialize_from(package[NpkPartID.FILE_CONTAINER].data)
else:
if npk[NpkPartID.NAME_INFO].data.name == 'system':
file_container = NpkFileContainer.unserialize_from(npk[NpkPartID.FILE_CONTAINER].data)
if file_container:
for item in file_container:
if item.name == args.name.encode():
with open(args.output,'wb') as f:
f.write(item.data)
print(f'Extracted {args.name} to {args.output}')
break
else:
parser.print_help()
parser.print_help()

View File

@ -1,32 +0,0 @@
def install_package(package, version="upgrade", index_url='https://mirrors.aliyun.com/pypi/simple/'):
from sys import executable
from subprocess import check_call
result = False
try:
if version.lower() == "upgrade":
result = check_call([executable, "-m", "pip", "install", package, "--upgrade", "-i", index_url])
else:
from pkg_resources import get_distribution
current_package_version = None
try:
current_package_version = get_distribution(package)
except Exception:
pass
if current_package_version is None or current_package_version != version:
installation_sign = "==" if ">=" not in version else ""
result = check_call([executable, "-m", "pip", "install", package + installation_sign + version, "-i", index_url])
except Exception as e:
print(e)
result = -1
return result
def check_package(package):
from importlib import import_module
try:
import_module(package)
return True
except ImportError:
return False
def check_install_package(packages):
for package in packages:
if not check_package(package):
install_package(package)

248
patch.py
View File

@ -1,5 +1,7 @@
import subprocess,lzma
import struct,os,re
import struct,os,re,tempfile
import pefile
from elftools.elf.elffile import ELFFile
from npk import NovaPackage,NpkPartID,NpkFileContainer
def replace_chunks(old_chunks,new_chunks,data,name):
@ -104,42 +106,12 @@ def patch_bzimage(data:bytes,key_dict:dict):
new_data = new_data.replace(vmlinux_xz,new_vmlinux_xz)
return new_data
def patch_block(dev:str,file:str,key_dict):
BLOCK_SIZE = 4096
#sudo debugfs /dev/nbd0p1 -R 'stats' | grep "Block size" | sed -n '1p' | cut -d ':' -f 2
#sudo debugfs /dev/nbd0p1 -R 'stat boot/initrd.rgz' 2> /dev/null | sed -n '11p'
stdout,_ = run_shell_command(f"debugfs {dev} -R 'stat {file}' 2> /dev/null | sed -n '11p' ")
#(0-11):1592-1603, (IND):1173, (12-15):1604-1607, (16-26):1424-1434
blocks_info = stdout.decode().strip().split(',')
print(f'blocks_info : {blocks_info}')
blocks = []
ind_block_id = None
for block_info in blocks_info:
_tmp = block_info.strip().split(':')
if _tmp[0].strip() == '(IND)':
ind_block_id = int(_tmp[1])
else:
print(f'block_info : {block_info}')
id_range = _tmp[0].strip().replace('(','').replace(')','').split('-')
block_range = _tmp[1].strip().replace('(','').replace(')','').split('-')
blocks += [id for id in range(int(block_range[0]),int(block_range[1])+1)]
print(f' blocks : {len(blocks)} ind_block_id : {ind_block_id}')
#sudo debugfs /dev/nbd0p1 -R 'cat boot/initrd.rgz' > data
data,stderr = run_shell_command(f"debugfs {dev} -R 'cat {file}' 2> /dev/null")
new_data = patch_kernel(data,key_dict)
print(f'write block {len(blocks)} : [',end="")
with open(dev,'wb') as f:
for index,block_id in enumerate(blocks):
print('#',end="")
f.seek(block_id*BLOCK_SIZE)
f.write(new_data[index*BLOCK_SIZE:(index+1)*BLOCK_SIZE])
f.flush()
print(']')
def patch_initrd_xz(initrd_xz:bytes,key_dict:dict,ljust=True):
initrd = lzma.decompress(initrd_xz)
try:
initrd = lzma.decompress(initrd_xz)
except Exception as e:
print(f'size:{len(initrd_xz)},header:{initrd_xz[:20].hex().upper()},footer:{initrd_xz[-20:].hex().upper()}\n')
raise Exception(f'failed to decompress initrd_xz: {e}')
new_initrd = initrd
for old_public_key,new_public_key in key_dict.items():
new_initrd = replace_key(old_public_key,new_public_key,new_initrd,'initrd')
@ -199,12 +171,58 @@ def patch_pe(data: bytes,key_dict:dict):
new_data = data.replace(vmlinux_xz,new_vmlinux_xz)
return new_data
def build_efi(input_file, output_file):
def find_xz_streams(data:bytes):
streams = []
XZ_HEADER_MAGIC = b'\xFD7zXZ\x00\x00\x01'
XZ_FOOTER_MAGIC = b'\x00\x00\x00\x00\x01\x59\x5A'
i = 0
while True:
start = data.find(XZ_HEADER_MAGIC, i)
if start == -1:
break
end = data.find(XZ_FOOTER_MAGIC, start)
assert end != -1, 'XZ footer not found'
end += len(XZ_FOOTER_MAGIC)
streams.append((start, end))
i = end
return streams
with open(input_file, 'rb') as f:
elf = ELFFile(f)
initrd_section =elf.get_section_by_name('initrd')
assert initrd_section is not None,'initrd section not found'
initrd_data = initrd_section.data()
xz_streams = find_xz_streams(initrd_data)
assert len(xz_streams) == 2,'only support 2 xz streams'
efi_xz = initrd_data[xz_streams[0][0]:xz_streams[0][1]]
cpio_xz = initrd_data[xz_streams[1][0]:xz_streams[1][1]]
try:
efi = lzma.decompress(efi_xz)
except Exception as e:
print(f'size:{len(efi_xz)},header:{efi_xz[:20].hex().upper()},footer:{efi_xz[-20:].hex().upper()}\n')
raise Exception(f'failed to decompress efi: {e}')
with pefile.PE(data = efi) as pe:
data_section =[section for section in pe.sections if section.Name == b'.data\x00\x00\x00'][0]
rva = data_section.VirtualAddress
addr = data_section.PointerToRawData
data = data_section.get_data()
size = len(data)
alignment = ((rva + size) + 4096 - 1) & ~(4096 - 1) #4096对齐
alignment = alignment - (rva+size)
new_data = data + b'\x00'*alignment
new_data += struct.pack('<I',len(cpio_xz))
new_data += cpio_xz
data_section.SizeOfRawData = len(new_data)
new_file_data = pe.write()
new_file_data = new_file_data[:addr]
new_file_data += new_data
with open(output_file, 'wb') as f:
f.write(new_file_data)
def patch_netinstall(key_dict: dict,input_file,output_file=None):
netinstall = open(input_file,'rb').read()
if netinstall[:2] == b'MZ':
from package import check_install_package
check_install_package(['pefile'])
import pefile
ROUTEROS_BOOT = {
129:{'arch':'power','name':'Powerboot'},
130:{'arch':'e500','name':'e500_boot'},
@ -243,7 +261,6 @@ def patch_netinstall(key_dict: dict,input_file,output_file=None):
pe.set_bytes_at_rva(rva,new_data)
pe.write(output_file or input_file)
elif netinstall[:4] == b'\x7FELF':
import re
# 83 00 00 00 C4 68 C4 0B 5A C2 04 08 10 9E 52 00
# 8A 00 00 00 C3 68 C4 0B 6A 60 57 08 C0 3D 54 00
# 81 00 00 00 D3 68 C4 0B 2A 9E AB 08 5C 1B 78 00
@ -304,14 +321,12 @@ def patch_kernel(data:bytes,key_dict):
return patch_elf(data,key_dict)
elif data[:5] == b'\xFD7zXZ':
print('patching initrd')
return patch_initrd_xz(data,key_dict)
return patch_initrd_xz(data,key_dict,False)
else:
raise Exception('unknown kernel format')
def patch_loader(loader_file):
try:
from package import check_install_package
check_install_package(['pyelftools'])
from loader.patch_loader import patch_loader as do_patch_loader
arch = os.getenv('ARCH') or 'x86'
arch = arch.replace('-', '')
@ -321,40 +336,82 @@ def patch_loader(loader_file):
print("loader module import failed. cannot run patch_loader.py")
def patch_squashfs(path,key_dict):
url_replacements = {
os.environ.get('MIKRO_LICENCE_URL', '').encode(): os.environ.get('CUSTOM_LICENCE_URL', '').encode(),
os.environ.get('MIKRO_UPGRADE_URL', '').encode(): os.environ.get('CUSTOM_UPGRADE_URL', '').encode(),
os.environ.get('MIKRO_CLOUD_URL', '').encode(): os.environ.get('CUSTOM_CLOUD_URL', '').encode(),
os.environ.get('MIKRO_CLOUD_PUBLIC_KEY', '').encode(): os.environ.get('CUSTOM_CLOUD_PUBLIC_KEY', '').encode(),
}
url_replacements = {k: v for k, v in url_replacements.items() if k and v}
renew_replacements = {
os.environ.get('MIKRO_RENEW_URL', '').encode(): os.environ.get('CUSTOM_RENEW_URL', '').encode(),
}
renew_replacements = {k: v for k, v in renew_replacements.items() if k and v}
for root, dirs, files in os.walk(path):
for _file in files:
file = os.path.join(root,_file)
if os.path.isfile(file):
if _file =='loader':
patch_loader(file)
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)
if _data != data:
open(file,'wb').write(_data)
url_dict = {
os.environ['MIKRO_LICENCE_URL'].encode():os.environ['CUSTOM_LICENCE_URL'].encode(),
os.environ['MIKRO_UPGRADE_URL'].encode():os.environ['CUSTOM_UPGRADE_URL'].encode(),
os.environ['MIKRO_CLOUD_URL'].encode():os.environ['CUSTOM_CLOUD_URL'].encode(),
os.environ['MIKRO_CLOUD_PUBLIC_KEY'].encode():os.environ['CUSTOM_CLOUD_PUBLIC_KEY'].encode(),
}
data = open(file,'rb').read()
for old_url,new_url in url_dict.items():
if 'mode' in files and 'keyman' in files:
for file_path in [os.path.join(root, 'mode'),os.path.join(root, 'keyman')]:
with open(file_path, 'rb') as f:
data = f.read()
for old_url,new_url in url_replacements.items():
if old_url in data:
print(f'{file} url patched {old_url.decode()[:7]}...')
print(f'{file_path} url patched {old_url.decode()[:7]}...')
data = data.replace(old_url,new_url)
open(file,'wb').write(data)
if os.path.split(file)[1] == 'licupgr':
url_dict = {
os.environ['MIKRO_RENEW_URL'].encode():os.environ['CUSTOM_RENEW_URL'].encode(),
}
for old_url,new_url in url_dict.items():
if old_url in data:
print(f'{file} url patched {old_url.decode()[:7]}...')
data = data.replace(old_url,new_url)
open(file,'wb').write(data)
modified = False
for old_public_key,new_public_key in key_dict.items():
new_data = replace_key(old_public_key,new_public_key,data,file_path)
if new_data != data:
data = new_data
modified = True
with open(f'{file_path}_', 'wb') as f:
f.write(data)
if 'loader' in files and os.path.isfile(os.path.join(root, 'loader')):
loader_file = os.path.join(root, 'loader')
patch_loader(loader_file)
if 'BOOTX64.EFI' in files and os.path.isfile(os.path.join(root, 'BOOTX64.EFI')):
efi_file = os.path.join(root, 'BOOTX64.EFI')
with open(efi_file, 'rb') as f:
data = f.read()
new_data = patch_kernel(data,key_dict)
assert new_data != data, f'{file_path} key not patched'
with open(efi_file, 'wb') as f:
f.write(new_data)
for filename in files:
if filename in ['mode','keyman','loader','BOOTX64.EFI']:
continue
file_path = os.path.join(root,filename)
if not os.path.isfile(file_path):
continue
modified = False
with open(file_path, 'rb') as f:
data = f.read()
for old_public_key,new_public_key in key_dict.items():
new_data = replace_key(old_public_key,new_public_key,data,file_path)
if new_data != data:
data = new_data
modified = True
for old_url,new_url in url_replacements.items():
if old_url in data:
print(f'{file_path} url patched {old_url.decode()[:7]}...')
data = data.replace(old_url,new_url)
modified = True
if filename == 'licupgr':
for old_url,new_url in renew_replacements.items():
if old_url in data:
print(f'{file_path} url patched {old_url.decode()[:7]}...')
data = data.replace(old_url,new_url)
modified = True
if modified:
with open(file_path, 'wb') as f:
f.write(data)
def run_shell_command(command):
process = subprocess.run(command, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@ -368,22 +425,21 @@ def patch_npk_package(package,key_dict):
print(f'patch {item.name} ...')
item.data = patch_kernel(item.data,key_dict)
package[NpkPartID.FILE_CONTAINER].data = file_container.serialize()
squashfs_file = 'squashfs-root.sfs'
extract_dir = 'squashfs-root'
open(squashfs_file,'wb').write(package[NpkPartID.SQUASHFS].data)
print(f"extract {squashfs_file} ...")
run_shell_command(f"unsquashfs -d {extract_dir} {squashfs_file}")
patch_squashfs(extract_dir,key_dict)
logo = os.path.join(extract_dir,"nova/lib/console/logo.txt")
run_shell_command(f"sudo sed -i '1d' {logo}")
run_shell_command(f"sudo sed -i '8s#.*# elseif@live.cn https://github.com/elseif/MikroTikPatch#' {logo}")
print(f"pack {extract_dir} ...")
run_shell_command(f"rm -f {squashfs_file}")
run_shell_command(f"mksquashfs {extract_dir} {squashfs_file} -quiet -comp xz -no-xattrs -b 256k")
print(f"clean ...")
run_shell_command(f"rm -rf {extract_dir}")
package[NpkPartID.SQUASHFS].data = open(squashfs_file,'rb').read()
run_shell_command(f"rm -f {squashfs_file}")
with tempfile.TemporaryDirectory() as tmp_dir:
squashfs_file = os.path.join(tmp_dir, 'squashfs-root.sfs')
extract_dir = os.path.join(tmp_dir, 'squashfs-root')
with open(squashfs_file,'wb') as f:
f.write(package[NpkPartID.SQUASHFS].data)
print(f"extract {squashfs_file} ...")
run_shell_command(f"unsquashfs -d {extract_dir} {squashfs_file}")
patch_squashfs(extract_dir,key_dict)
logo = os.path.join(extract_dir,"nova/lib/console/logo.txt")
run_shell_command(f"sed -i '1d' {logo}")
run_shell_command(f"sed -i '8s#.*# elseif@live.cn https://github.com/elseif/MikroTikPatch#' {logo}")
print(f"pack {extract_dir} ...")
run_shell_command(f"mksquashfs {extract_dir} {squashfs_file} -no-recovery -noappend -exit-on-error -quiet -comp xz -no-xattrs -b 256k -all-root")
with open(squashfs_file,'rb') as f:
package[NpkPartID.SQUASHFS].data = f.read()
def patch_npk_file(key_dict,kcdsa_private_key,eddsa_private_key,input_file,output_file=None):
npk = NovaPackage.load(input_file)
@ -405,9 +461,9 @@ if __name__ == '__main__':
kernel_parser = subparsers.add_parser('kernel',help='patch kernel file')
kernel_parser.add_argument('input',type=str, help='Input file')
kernel_parser.add_argument('-O','--output',type=str,help='Output file')
block_parser = subparsers.add_parser('block',help='patch block file')
block_parser.add_argument('dev',type=str, help='block device')
block_parser.add_argument('file',type=str, help='file path')
buildefi_parser = subparsers.add_parser('buildefi',help='build efi file')
buildefi_parser.add_argument('input',type=str, help='kernel file')
buildefi_parser.add_argument('output',type=str,help='Output to file')
netinstall_parser = subparsers.add_parser('netinstall',help='patch netinstall file')
netinstall_parser.add_argument('input',type=str, help='Input file')
netinstall_parser.add_argument('-O','--output',type=str,help='Output file')
@ -425,9 +481,9 @@ if __name__ == '__main__':
print(f'patching {args.input} ...')
data = patch_kernel(open(args.input,'rb').read(),key_dict)
open(args.output or args.input,'wb').write(data)
elif args.command == 'block':
print(f'patching {args.file} in {args.dev} ...')
patch_block(args.dev,args.file,key_dict)
elif args.command == 'buildefi':
print(f'building EFI from {args.input} ...')
build_efi(args.input,args.output)
elif args.command == 'netinstall':
print(f'patching {args.input} ...')
patch_netinstall(key_dict,args.input,args.output)

View File

@ -87,7 +87,7 @@ class ShortWeierstrassCurve(EllipticCurve, CurveOpIsomorphism, CurveOpExportSage
@property
def is_koblitz(self):
"""Returns whether the curve allows for efficient computation of a map
\phi in the field (i.e. that the curve is commonly known as a 'Koblitz
phi in the field (i.e. that the curve is commonly known as a 'Koblitz
Curve'). This corresponds to examples 3 and 4 of the paper "Faster
Point Multiplication on Elliptic Curves with Efficient Endomorphisms"
by Gallant, Lambert and Vanstone."""