]> Git [git.vados.ru] :: Repos - Mikrotiks/ros.vados.ru.git/commitdiff
mikro_bak initial Commit
authorvados-dev <vados@vados.ru>
Mon, 2 Mar 2026 19:23:36 +0000 (22:23 +0300)
committervados-dev <vados@vados.ru>
Mon, 2 Mar 2026 19:23:36 +0000 (22:23 +0300)
227 files changed:
.gitignore [new file with mode: 0644]
Mikrotiks/.bash_history [new file with mode: 0644]
Mikrotiks/.bashrc [new file with mode: 0644]
Mikrotiks/.bin/bash-progress-indicator/LICENSE [new file with mode: 0644]
Mikrotiks/.bin/bash-progress-indicator/README.md [new file with mode: 0644]
Mikrotiks/.bin/bash-progress-indicator/main.sh [new file with mode: 0755]
Mikrotiks/.bin/bash-progress-indicator/progress.sh [new file with mode: 0644]
Mikrotiks/.bin/bash-progress-indicator/progress_advanced.sh [new file with mode: 0644]
Mikrotiks/.bin/bash-progress-indicator/spinner.sh [new file with mode: 0644]
Mikrotiks/.bin/logs [new symlink]
Mikrotiks/.bin/ros-conf/README.md [new file with mode: 0644]
Mikrotiks/.bin/ros-conf/setup-ssh-keys.sh [new file with mode: 0755]
Mikrotiks/.bin/ros-conf/sync-script.sh [new file with mode: 0755]
Mikrotiks/.bin/ros-diff.sh [new file with mode: 0755]
Mikrotiks/.bin/scripts/mktmpfs.rsc [new file with mode: 0644]
Mikrotiks/.bin/test.sh [new file with mode: 0755]
Mikrotiks/.bin/vados-dev.pub [new file with mode: 0644]
Mikrotiks/.config/.mikrotiks [new file with mode: 0644]
Mikrotiks/.config/mc/hotlist [new file with mode: 0644]
Mikrotiks/.config/mc/ini [new file with mode: 0644]
Mikrotiks/.config/mc/panels.ini [new file with mode: 0644]
Mikrotiks/.gitconfig [new file with mode: 0644]
Mikrotiks/.lesshst [new file with mode: 0644]
Mikrotiks/.profile [new file with mode: 0644]
Mikrotiks/.repo_.gitconfig.json [new file with mode: 0644]
Mikrotiks/.selected_editor [new file with mode: 0644]
Mikrotiks/.wget-hsts [new file with mode: 0644]
Mikrotiks/gpg_vados-dev.asc [new file with mode: 0644]
Mikrotiks/gpg_vados-dev.pub [new file with mode: 0644]
bin/checksums.log [new file with mode: 0644]
bin/checksums.sh [new file with mode: 0755]
bin/get_backups.sh [new file with mode: 0644]
bin/ros-conf/README.md [new file with mode: 0644]
bin/ros-conf/setup-ssh-keys.sh [new file with mode: 0755]
bin/ros-conf/sync-script.sh [new file with mode: 0755]
html/.bin/checksums.sh [new file with mode: 0755]
html/.bin/commitinfo.sh [new file with mode: 0755]
html/.bin/html.sh [new file with mode: 0755]
html/.bin/static-html.sh [new file with mode: 0755]
html/.bin/template-capsman.sh [new file with mode: 0755]
html/.bin/template-local.sh [new file with mode: 0755]
html/.bin/template-wifi.sh [new file with mode: 0755]
html/.gitignore [new file with mode: 0644]
html/.include/css/style.css [new file with mode: 0644]
html/.include/img/browser-01.avif [new file with mode: 0644]
html/.include/img/browser-02.avif [new file with mode: 0644]
html/.include/img/browser-03.avif [new file with mode: 0644]
html/.include/img/eworm-meadow.avif [new file with mode: 0644]
html/.include/img/logo.avif [new file with mode: 0644]
html/.include/img/logo.png [new file with mode: 0644]
html/.include/img/logo.svg [new file with mode: 0644]
html/.include/img/qr-code.png [new file with mode: 0644]
html/.include/js/clipboard.js [new file with mode: 0644]
html/.include/js/color.js [new file with mode: 0644]
html/.include/js/notif.js [new file with mode: 0644]
html/.tmpl/readme.md [new file with mode: 0644]
html/AM-Backup-UpdateEmail.rsc [new file with mode: 0644]
html/AM-CapsManRoll-Upgrade.rsc [new file with mode: 0644]
html/AM-CapsUpgrade.rsc [new file with mode: 0644]
html/AM-CheckUpdates.rsc [new file with mode: 0644]
html/AM-DnsNetwatch.rsc [new file with mode: 0644]
html/AM-DownloadPackages.capsman.rsc [new file with mode: 0644]
html/AM-GlobalConfig.rsc [new file with mode: 0644]
html/AM-GlobalFunc.rsc [new file with mode: 0644]
html/AM-Init-Script.rsc [new file with mode: 0644]
html/AM-LogForward.rsc [new file with mode: 0644]
html/AM-SFTP-backup.rsc [new file with mode: 0644]
html/AM-SshKeysImport.rsc [new file with mode: 0644]
html/AM-backupSFTP.rsc [new file with mode: 0644]
html/AM-setup.rsc [new file with mode: 0644]
html/CERTIFICATES.d/01-dialog-A.avif [new file with mode: 0644]
html/CERTIFICATES.d/02-dialog-B.avif [new file with mode: 0644]
html/CERTIFICATES.d/03-window.avif [new file with mode: 0644]
html/CERTIFICATES.d/04-certificate.avif [new file with mode: 0644]
html/COPYING.md [new file with mode: 0644]
html/INITIAL-COMMANDS.md [new file with mode: 0644]
html/Makefile [new file with mode: 0644]
html/README.d/00-builtin-trust-store.avif [new file with mode: 0644]
html/README.d/01-download-certs.avif [new file with mode: 0644]
html/README.d/02-import-certs.avif [new file with mode: 0644]
html/README.d/03-check-certs.avif [new file with mode: 0644]
html/README.d/04-import-scripts.avif [new file with mode: 0644]
html/README.d/05-run-scripts.avif [new file with mode: 0644]
html/README.d/06-schedule-update.avif [new file with mode: 0644]
html/README.d/07-edit-global-config-overlay.avif [new file with mode: 0644]
html/README.d/08-apply-configuration.avif [new file with mode: 0644]
html/README.d/09-update-scripts.avif [new file with mode: 0644]
html/README.d/10-install-scripts.avif [new file with mode: 0644]
html/README.d/11-schedule-script.avif [new file with mode: 0644]
html/README.d/12-setup-lease-script.avif [new file with mode: 0644]
html/README.d/13-install-custom-script.avif [new file with mode: 0644]
html/README.d/14-remove-script.avif [new file with mode: 0644]
html/README.d/hello-world.rsc [new file with mode: 0644]
html/README.d/notification-news-and-changes.avif [new file with mode: 0644]
html/README.d/telegram-group.avif [new file with mode: 0644]
html/README.md [new file with mode: 0644]
html/RunOnce.rsc [new file with mode: 0644]
html/Update-Certificates.rsc [new file with mode: 0644]
html/certs/DigiCert-Global-Root-G3.pem [new file with mode: 0644]
html/certs/GTS-Root-R1.pem [new file with mode: 0644]
html/certs/GTS-Root-R4.pem [new file with mode: 0644]
html/certs/GTS-Root-RX.pem [new file with mode: 0644]
html/certs/Go-Daddy-Root-Certificate-Authority-G2.pem [new file with mode: 0644]
html/certs/ISRG-Root-X1.pem [new file with mode: 0644]
html/certs/ISRG-Root-X2.pem [new file with mode: 0644]
html/certs/Makefile [new file with mode: 0644]
html/certs/Root-YE.pem [new file with mode: 0644]
html/certs/Root-YR.pem [new file with mode: 0644]
html/certs/SSL-com-Root-Certification-Authority-ECC.pem [new file with mode: 0644]
html/certs/Starfield-Root-Certificate-Authority-G2.pem [new file with mode: 0644]
html/certs/USERTrust-RSA-Certification-Authority.pem [new file with mode: 0644]
html/checkUpd.diff [new file with mode: 0644]
html/doc/accesslist-duplicates.d/01-example.avif [new file with mode: 0644]
html/doc/accesslist-duplicates.md [new file with mode: 0644]
html/doc/backup-cloud.d/notification.avif [new file with mode: 0644]
html/doc/backup-cloud.md [new file with mode: 0644]
html/doc/backup-email.md [new file with mode: 0644]
html/doc/backup-partition.md [new file with mode: 0644]
html/doc/backup-upload.d/notification.avif [new file with mode: 0644]
html/doc/backup-upload.md [new file with mode: 0644]
html/doc/capsman-download-packages.md [new file with mode: 0644]
html/doc/capsman-rolling-upgrade.md [new file with mode: 0644]
html/doc/certificate-renew-issued.md [new file with mode: 0644]
html/doc/check-certificates.d/notification-01-warn.avif [new file with mode: 0644]
html/doc/check-certificates.d/notification-02-renew.avif [new file with mode: 0644]
html/doc/check-certificates.md [new file with mode: 0644]
html/doc/check-health.d/notification-01-cpu-utilization-high.avif [new file with mode: 0644]
html/doc/check-health.d/notification-02-cpu-utilization-ok.avif [new file with mode: 0644]
html/doc/check-health.d/notification-03-ram-utilization-high.avif [new file with mode: 0644]
html/doc/check-health.d/notification-04-ram-utilization-ok.avif [new file with mode: 0644]
html/doc/check-health.d/notification-05-voltage.avif [new file with mode: 0644]
html/doc/check-health.d/notification-06-temperature-high.avif [new file with mode: 0644]
html/doc/check-health.d/notification-07-temperature-ok.avif [new file with mode: 0644]
html/doc/check-health.d/notification-08-state-fail.avif [new file with mode: 0644]
html/doc/check-health.d/notification-09-state-ok.avif [new file with mode: 0644]
html/doc/check-health.md [new file with mode: 0644]
html/doc/check-lte-firmware-upgrade.d/notification.avif [new file with mode: 0644]
html/doc/check-lte-firmware-upgrade.md [new file with mode: 0644]
html/doc/check-perpetual-license.d/notification-01-warn.avif [new file with mode: 0644]
html/doc/check-perpetual-license.d/notification-02-renew.avif [new file with mode: 0644]
html/doc/check-perpetual-license.md [new file with mode: 0644]
html/doc/check-routeros-update.d/notification-01-found.avif [new file with mode: 0644]
html/doc/check-routeros-update.d/notification-02-neighbor.avif [new file with mode: 0644]
html/doc/check-routeros-update.d/terminal.avif [new file with mode: 0644]
html/doc/check-routeros-update.md [new file with mode: 0644]
html/doc/cloud-backup.md [new file with mode: 0644]
html/doc/collect-wireless-mac.d/notification.avif [new file with mode: 0644]
html/doc/collect-wireless-mac.md [new file with mode: 0644]
html/doc/daily-psk.d/notification.avif [new file with mode: 0644]
html/doc/daily-psk.md [new file with mode: 0644]
html/doc/dhcp-lease-comment.md [new file with mode: 0644]
html/doc/dhcp-to-dns.md [new file with mode: 0644]
html/doc/early-errors.md [new file with mode: 0644]
html/doc/email-backup.md [new file with mode: 0644]
html/doc/firmware-upgrade-reboot.md [new file with mode: 0644]
html/doc/fw-addr-lists.md [new file with mode: 0644]
html/doc/global-wait.md [new file with mode: 0644]
html/doc/gps-track.md [new file with mode: 0644]
html/doc/hotspot-to-wpa.md [new file with mode: 0644]
html/doc/ip-addr-bridge.md [new file with mode: 0644]
html/doc/ipsec-to-dns.md [new file with mode: 0644]
html/doc/ipv6-update.md [new file with mode: 0644]
html/doc/lease-script.md [new file with mode: 0644]
html/doc/leds-mode.md [new file with mode: 0644]
html/doc/log-forward.d/notification-01-info.avif [new file with mode: 0644]
html/doc/log-forward.d/notification-02-warn.avif [new file with mode: 0644]
html/doc/log-forward.md [new file with mode: 0644]
html/doc/mod/bridge-port-to.md [new file with mode: 0644]
html/doc/mod/bridge-port-vlan.md [new file with mode: 0644]
html/doc/mod/inspectvar.d/01-inspectvar.avif [new file with mode: 0644]
html/doc/mod/inspectvar.md [new file with mode: 0644]
html/doc/mod/ipcalc.d/01-ipcalc.avif [new file with mode: 0644]
html/doc/mod/ipcalc.d/02-ipcalcreturn.avif [new file with mode: 0644]
html/doc/mod/ipcalc.md [new file with mode: 0644]
html/doc/mod/notification-email.md [new file with mode: 0644]
html/doc/mod/notification-gotify.d/appsetup.avif [new file with mode: 0644]
html/doc/mod/notification-gotify.md [new file with mode: 0644]
html/doc/mod/notification-matrix.d/01-authenticate.avif [new file with mode: 0644]
html/doc/mod/notification-matrix.d/02-join-room.avif [new file with mode: 0644]
html/doc/mod/notification-matrix.md [new file with mode: 0644]
html/doc/mod/notification-ntfy.md [new file with mode: 0644]
html/doc/mod/notification-telegram.d/01-newbot.avif [new file with mode: 0644]
html/doc/mod/notification-telegram.d/02-getchatid.avif [new file with mode: 0644]
html/doc/mod/notification-telegram.d/03-setuserpic.avif [new file with mode: 0644]
html/doc/mod/notification-telegram.md [new file with mode: 0644]
html/doc/mod/scriptrunonce.d/01-scriptrunonce.avif [new file with mode: 0644]
html/doc/mod/scriptrunonce.d/hello-world.rsc [new file with mode: 0644]
html/doc/mod/scriptrunonce.md [new file with mode: 0644]
html/doc/mod/ssh-keys-import.md [new file with mode: 0644]
html/doc/mode-button.md [new file with mode: 0644]
html/doc/netwatch-dns.md [new file with mode: 0644]
html/doc/netwatch-notify.d/notification-01-down.avif [new file with mode: 0644]
html/doc/netwatch-notify.d/notification-02-up.avif [new file with mode: 0644]
html/doc/netwatch-notify.md [new file with mode: 0644]
html/doc/netwatch-syslog.md [new file with mode: 0644]
html/doc/ospf-to-leds.md [new file with mode: 0644]
html/doc/packages-update.md [new file with mode: 0644]
html/doc/ppp-on-up.md [new file with mode: 0644]
html/doc/rotate-ntp.md [new file with mode: 0644]
html/doc/sms-action.md [new file with mode: 0644]
html/doc/sms-forward.d/notification.avif [new file with mode: 0644]
html/doc/sms-forward.md [new file with mode: 0644]
html/doc/ssh-keys-import.md [new file with mode: 0644]
html/doc/super-mario-theme.md [new file with mode: 0644]
html/doc/telegram-chat.d/01-chat-specific.avif [new file with mode: 0644]
html/doc/telegram-chat.d/02-chat-all.avif [new file with mode: 0644]
html/doc/telegram-chat.d/03-reply.avif [new file with mode: 0644]
html/doc/telegram-chat.md [new file with mode: 0644]
html/doc/unattended-lte-firmware-upgrade.md [new file with mode: 0644]
html/doc/update-gre-address.md [new file with mode: 0644]
html/doc/update-tunnelbroker.md [new file with mode: 0644]
html/doc/upload-backup.md [new file with mode: 0644]
html/func-collection/CustomBackups.rsc [new file with mode: 0644]
html/func-collection/DateTime.rsc [new file with mode: 0644]
html/func-collection/README.md [new file with mode: 0644]
html/func-collection/bash/ros-deploy.sh [new file with mode: 0644]
html/func-collection/bash/setup-ssh-keys.sh [new file with mode: 0644]
html/func-collection/bash/sync-script.sh [new file with mode: 0644]
html/func-collection/urls/README.md [new file with mode: 0644]
html/ipcalc.rsc [new file with mode: 0644]
html/logo/telegram.md [new file with mode: 0644]
html/mikro_bak_rsa.pub [new file with mode: 0644]
html/mod/AM-TG-Bot.rsc [new file with mode: 0644]
html/mod/AM-TG-Notifications.rsc [new file with mode: 0644]
html/staging_main.diff [new file with mode: 0644]
html/test.rsc [new file with mode: 0644]
ros.code-workspace [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..f360a17
--- /dev/null
@@ -0,0 +1,26 @@
+.bak
+.vscode
+**/oxidized
+**/upload
+**/test
+**/.bak
+**/.cache
+**/.docker
+**/.gnupg
+**/.local
+**/.ssh
+**/.vscode
+**/backups
+**/Precedent-Config.rsc
+# backup and temporary files
+*~
+# patches and related files
+*.orig
+*.patch
+*.rej
+
+# html files (as generated from markdown)
+*.html
+
+# checksums file as used by $ScriptInstallUpdate
+checksums.json
\ No newline at end of file
diff --git a/Mikrotiks/.bash_history b/Mikrotiks/.bash_history
new file mode 100644 (file)
index 0000000..2b1b7cd
--- /dev/null
@@ -0,0 +1,582 @@
+exit
+ssh-keygen -t rsa -b 2048
+mc
+cd .bin
+ls -la
+cd ~
+ls -la
+cd .bin/ros-conf/ 
+ls -la
+./setup-ssh-keys.sh 
+ssh mikro_bak@10.30.30.10
+ssh mikro_bak@10.30.30.10 2222
+ssh mikro_bak@10.30.30.10 -p 2222
+exit
+ssh mikro_bak@10.30.30.10 -p 2222
+cd ~./bin
+ls -la
+cd ~
+cd .bin/
+ls -la
+cd ros-conf/
+ls -la
+mc
+./setup-ssh-keys.sh
+ssh mikro_bak@10.30.30.10 -p 2222
+ls -la .
+./setup-ssh-keys.sh 
+ssh://mikro_bak@10.30.30.10:2222
+ssh ssh://mikro_bak@10.30.30.10:2222/
+ls -la
+cd .bin/
+cd ros-conf/
+ls -la
+./setup-ssh-keys.sh 
+mc
+exit
+./setup-ssh-keys.sh
+source ~/.bashrc
+mc
+./setup-ssh-keys.sh 
+ls -la .
+cd .bin/
+ls -la .
+cd ros-conf/
+./setup-ssh-keys.sh 
+exit
+./setup-ssh-keys.sh
+../
+cd ~
+cd .bin/
+cd ros-conf/
+./setup-ssh-keys.sh 
+mc
+exit
+cd ~
+cd .bin/
+cd ros-conf/
+./setup-ssh-keys.sh 
+ssh mikro_bak@10.30.30.10 -p 2222
+./setup-ssh-keys.sh
+cat mikro_bak_rsa@10.30.30.81.pub 
+ssh mikro_bak@10.30.30.10 -p 2222
+ssh mikro_bak@10.30.30.81 -p 2222
+]
+"|
+'57777774799666999-9600609+
+q
+\
+;
+#
+
+ssh ssh://mikro_bak@10.30.30.81:2222
+./setup-ssh-keys.sh
+ssh ssh://mikro_bak@10.30.30.81:2222
+ssh mikro_bak@10.30.30.81
+ssh mikro_bak@10.30.30.81 -p 2222
+mc
+exit
+cat mikro_bak_rsa@10.30.30.81.pub >> known_hosts 
+ssh mikro_bak@10.30.30.81 -p 2222
+ssh mikro_bak@10.30.30.81 -p 2222
+ssh -vvv mikro_bak@10.30.30.81 -p 2222
+mc
+exit
+ssh -vvv mikro_bak@10.30.30.81 -p 2222
+mc
+ssh -vvv mikro_bak@10.30.30.81 -p 2222
+
+\['
+p[ol-0]\p;l-]\
+p;ki
+exit
+mc
+ssh -vvv mikro_bak@10.30.30.81 -p 2222
+ssh mikro_bak@10.30.30.10 -p 2222
+mc
+exit
+chmod +x get_backups.sh
+./test.sh
+./get_backups.sh
+ssh mikro_bak@10.30.30.10 -p 2222
+ssh mikro_bak@10.30.30.81 -p 2222
+mc
+exit
+./test.sh 
+exit
+bash --version
+readarray -t lines < "$mikrotik_file"
+readarray -t lines < "../.mikrotik"
+readarray -t lines < "../.mikrotiks"
+echo $lines
+readarray lines < "../.mikrotiks"
+echo $lines
+readarray lines < "../.mikrotiks"
+echo $lines
+readarray lines < "../.mikrotiks"
+echo $lines
+./test.sh 
+./test.sh
+./ROSdiff.sh 
+/ROSdiff.sh 
+ROSdiff.sh 
+./ROSdiff.sh 
+exit
+bash -V
+bash --version
+./test.sh 
+./test.sh
+/bin/bash
+./ROSdiff.sh 
+exit
+./test.sh
+sshpass -h
+./test.sh
+bash -v
+exit
+./test.sh 
+mc
+exit
+./ROSdiff.sh 
+exit
+mc
+./ROSdiff.sh 
+exit
+./ROSdiff.sh 
+./ROSdiff.sh 
+ls -la .
+mc
+./ROSdiff.sh 
+mc
+exit
+./ROSdiff.sh 
+exit
+./ROSdiff.sh 
+exit
+./ROSdiff.sh 
+sed -i 's/\r//' ./ROSdiff.sh 
+./ROSdiff.sh 
+./ROSdiff.sh
+touch rdiff.sh
+chmod +x rdiff.sh 
+./ROSdiff.sh
+exit
+./ROSdiff.sh
+exit
+./ROSdiff.sh
+mc
+./ROSdiff.sh
+exit
+chmod +x ros-deploy.sh 
+exit
+./ROSdiff.sh
+mc
+exit
+chmod +x ros-deploy.sh 
+./ros-deploy.sh -h
+./ros-deploy.sh -H
+git clone https://github.com/tarikin/ros-deploy.git
+./ros-deploy.sh --help
+./ROSdiff.sh
+./ros-deploy.sh -h mikro_bak@10.30.30.81:2222 -s ./scripts/mktmpfs.rsc -i ../.ssh/id_rsa
+./ROSdiff.sh
+ssh-add
+ssh -i
+./ROSdiff.sh
+ssh -i ../.ssh/id_rsa
+mc
+exit
+./ROSdiff.sh
+scp
+./ROSdiff.sh
+wget https://github.com/tarikin/ros-deploy/archive/refs/tags/v1.2.1.tar.gz
+mc
+exit
+./ROSdiff.sh
+diff ./get_backups.sh ../.bak/get_backups.sh >> get_backups.diff
+touch config
+./ROSdiff.sh
+source ~/.bashrc
+mc
+./ROSdiff.sh
+mc
+./ROSdiff.sh
+mc
+./ROSdiff.sh
+mc
+./ROSdiff.sh
+mc
+./ROSdiff.sh
+mc
+./rdiff.sh
+mc
+./rdiff.sh
+mc
+ssh mikro_bak@10.30.30.86 -p 2222
+mc
+./ros-diff.sh 
+./ros-diff.sh
+mc
+./ros-diff.sh
+crontab -e */20 * * * * /var/www/ros.vados.ru/Mikrotiks/.bin/ros-diff.sh
+crontab -u $whoami -e */20 * * * * /var/www/ros.vados.ru/Mikrotiks/.bin/ros-diff.sh
+crontab -l
+crontab -e
+crontab -l
+mc
+./run.sh
+vigr
+./run.sh
+passwd mikro_bak
+./run.sh
+git init
+git config --global amster-dev
+git config --global vados-dev
+git branch -m main
+git add MikroNUC/
+git add MikroMaster/
+git add MikroYellow/
+git commit MikroNUC/ -m "Initial Commit"
+git commit MikroMaster/ -m "Initial Commit"
+git commit MikroYellow/ -m "Initial Commit"
+git push ~/backups
+git push ~/backups main
+git config --global init.defaultBranch main
+git config core.repositoryformatversion 0
+git config core.filemode true
+git config core.bare true
+git config receive.denyCurrentBranch ignore
+git remote add origin git@amster-nuc:/.data/nfs/git/Mikrotiks/backup_configs.git
+git push origin main
+git remote add origin git@amster-nuc:/.data/nfs/git/Mikrotiks/backup_configs.git
+git push origin main
+ssh git@amster-nuc
+cat id_rsa.mikro_bak.pub 
+ssh git@amster-nuc
+ssh-keygen -t rsa -b 2048
+cat id_rsa.mikro_bak.pub 
+ssh git@amster-nuc
+ssh mikro_bak@amster-nuc
+ssh git@amster-nuc
+ssh -vvv git@amster-nuc
+ssh-keygen -t rsa -b 2048
+ssh-keygen -t rsa -b 2048 -C "git@amster-nuc"
+cat id_rsa.mikro_bak.amster-nuc.pub 
+ssh -vvv git@amster-nuc
+cat id_rsa.pub 
+ssh -vvv git@amster-nuc
+git push origin main
+git init
+git add .
+git commit -m 'Initial commit'
+git remote add origin git@amster-nuc:/.data/nfs/git/Mikrotiks/backup_configs.git
+git push origin main
+git clone git@amster-nuc:/.data/nfs/git/Mikrotiks/backup_configs.git
+ls -la .
+git commit -m 'Add .gitignore'
+git add .
+git commit -m 'Add .gitignore'
+git push origin main
+git clone git@amster-nuc:/.data/nfs/git/Mikrotiks/backup_configs.git
+git push origin main
+git clone git@amster-nuc:/.data/nfs/git/Mikrotiks/backup_configs.git
+git add .
+git commit -m 'Edit .gitignore'
+git push origin main
+git clone git@amster-nuc:/.data/nfs/git/Mikrotiks/backup_configs.git
+git config --global user.name mikro_bak
+git config --global user.email vados@vados.ru
+git init
+git add .
+git commit -m 'Initial commit'
+git init
+git add .
+git commit -m 'Initial commit'
+git remote add origin mikro_bak@amster-nuc:/.data/nfs/git/Mikrotiks/backups.git
+git push origin main
+git remote add origin git@amster-nuc:/.data/nfs/git/Mikrotiks/backups.git
+git remote remove origin mikro_bak@amster-nuc:/.data/nfs/git/Mikrotiks/backups.git
+git remote remove origin mikro_bak@amster-nuc
+git remote remove mikro_bak@amster-nuc
+git remote remove origin mikro_bak@amster-nuc
+git init
+git add .
+git commit -m 'Initial commit'
+git remote add origin git@amster-nuc:/.data/nfs/git/Mikrotiks/backups.git
+git push origin main
+git clone git@amster-nuc:/.data/nfs/git/Mikrotiks/backup_configs.git
+git clone git@amster-nuc:/.data/nfs/git/Mikrotiks/backups.git
+./ros-diff.sh
+cat authorized_keys 
+cat id_rsa
+cat id_rsa.pub 
+git init
+git add .
+git commit -m 'Initial commit'
+ssh git@vados-nuc
+git remote add origin git@vados-nuc:/backups.git
+git push origin main
+git init
+git add .
+git commit -am 'Initial commit'
+git remote add origin git@vados-nuc:d:/.bak/Mikrotiks/backups.git
+git push origin main
+git config --local remote.origin.receivepack "powershell git-receive-pack"
+git config --local remote.origin.uploadpack "powershell git-upload-pack"
+git push origin main
+$env:GIT_SSH_COMMAND = '"C:\WINDOWS\System32\OpenSSH\ssh.exe" -T'
+git config --local remote.origin.receivepack "powershell git-receive-pack"
+git config --local remote.origin.uploadpack "powershell git-upload-pack"
+git fetch origin
+git remote add origin git@vados-nuc:/d/.bak/Mikrotiks/backups.git
+git init
+git add .
+git commit -am 'Initial commit'
+git remote add origin git@vados-nuc:/d/.bak/Mikrotiks/backups.git
+git fetch origin
+git config --local remote.origin.uploadpack "powershell git-upload-pack"
+git config --local remote.origin.receivepack "powershell git-receive-pack"
+git fetch origin
+git push origin
+git push --set-upstream origin main
+git push --set-upstream origin main -vvv
+ping vados-nuc
+git push --set-upstream origin main -vvv
+git push --set-upstream origin main
+source ~/.bashrc
+git push --set-upstream origin main
+source ~/.bashrc
+git push --set-upstream origin main
+source ~/.bashrc
+git push --set-upstream origin main
+ssh git@vados-nuc
+ssh-add
+git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe" eval $(ssh-agent)
+git push --set-upstream origin main
+ls -al ~/.ssh
+source ~/.bashrc
+git push --set-upstream origin main
+source ~/.bashrc
+git push --set-upstream origin main
+source ~/.bashrc
+git push --set-upstream origin main
+git fetch origin
+GIT_SSH_COMMAND="ssh -v" git fetch
+git fetch origin
+git init
+git add .
+git commit -am 'Initial commit'
+git fetch origin
+git push --set-upstream origin main
+git remote add origin git@vados-nuc:/d/.bak/Mikrotiks/ba
+git push --set-upstream origin main
+git fetch origin
+git push --set-upstream origin main
+git fetch origin
+git push --set-upstream origin main
+git fetch origin
+git push --set-upstream origin main
+git fetch origin
+git fetch origin -v
+git -vvv fetch origin
+git -v fetch origin
+GIT_SSH_COMMAND="ssh -v" git fetch
+git fetch origin
+GIT_SSH_COMMAND="ssh -v" git fetch
+git fetch origin
+GIT_SSH_COMMAND="ssh -v" git fetch
+git fetch origin
+GIT_SSH_COMMAND="ssh -v" git fetch
+git config --list
+git config --global protocol.version 2
+git config --list
+git fetch origin
+GIT_SSH_COMMAND="ssh -v" git fetch
+git config --local remote.origin.receivepack "powershell git-receive-pack"
+git config --local remote.origin.uploadpack "powershell git-upload-pack"
+git fetch origin
+GIT_SSH_COMMAND="ssh -v" git fetch
+git fetch origin
+GIT_SSH_COMMAND="ssh -v" git fetch
+git fetch origin
+GIT_SSH_COMMAND="ssh -v" git fetch
+git fetch origin
+git push --set-upstream origin main
+git fetch origin
+./ros-diff.sh
+git add .
+git init
+git add .
+git commit 'Reinit commit'
+git commit -m 'Reinit commit'
+git fetch origin
+git push --set-upstream origin main
+git add .
+./ros-diff.sh
+git fetch origin
+git config --local remote.origin.uploadpack "powershell git-upload-pack"
+git config --local remote.origin.receivepack "powershell git-receive-pack"
+git fetch origin
+GIT_SSH_COMMAND="ssh -v" git fetch
+git fetch origin
+GIT_SSH_COMMAND="ssh -v" git fetch
+./ros-diff.sh
+set -o
+set -o piperfail
+set -o pipefail
+set -o
+set +o pipefail
+set -o
+set +o pipefail
+set -o
+set +e
+set -o
+set -e
+set -o
+./ros-diff.sh
+rm -d tmp/
+./ros-diff.sh
+cat ros-deploy.sh 
+./ros-diff.sh
+\date '+%d.%m.%Y'date '+%d.%m.%Y'date '+%d.%m.%Y'date '+%d.%m.%Y'date '+%d.%m.%Y'date '+%d.%m.%Y'
+\date '+%d.%m.%Y'date '+%d.%m.%Y'date '+%d.%m.%Y'date '+%d.%m.%Y'date '+%d.%m.%Y'date '+%d.%m.%Y'date '+%d.%m.%Y'
+(date '+%d.%m.%Y')
+(date '%H:%M:%S %z')
+(date '+%H:%M:%S %z')
+./ros-diff.sh
+kdialog --password "Пожалуйста, введите свой пароль:"kdialog --password "Пожалуйста, введите свой пароль:"kdialog --password "Пожалуйста, введите свой пароль:"
+clear
+reset
+dialog --title " Уведомление " --msgbox "\n Свершилось что-то страшное!" 6 50
+echo -n "Продолжить? (y/n) "
+read item
+case "$item" in     y|Y) echo "Ввели «y», продолжаем...";         ;;     n|N) echo "Ввели «n», завершаем...";         exit 0;         ;;     *) echo "Ничего не ввели. Выполняем действие по умолчанию...";         ;; esac
+$(tput setf 4)
+echo tt
+echo -n "$(tput setf 4) ee"
+echo -n "$(tput setf 2) ee"
+./test.sh
+./ros-diff.sh
+'\033[0m'
+\033[0m
+reset
+./ros-diff.sh
+gg
+./ros-diff.sh
+git clone https://github.com/lnfnunes/bash-progress-indicator.git
+./ros-diff.sh
+main.sh spinner 1
+./main.sh spinner 1
+./main.sh spinner1
+cd ..
+./ros-diff.sh
+clear
+./ros-diff.sh
+ssh -i /var/www/ros.vados.ru/Mikrotiks/.ssh/id_rsa -o PasswordAuthentication=no -p 2222 mikro_bak@10.30.30.10 export >Config.rsc
+ssh -i /var/www/ros.vados.ru/Mikrotiks/.ssh/id_rsa -o PasswordAuthentication=no -p 2222 mikro_bak@10.30.30.10 export >Config.rsc 2>1
+ssh -i /var/www/ros.vados.ru/Mikrotiks/.ssh/id_rsa -o PasswordAuthentication=no -p 2222 mikro_bak@10.30.30.10 export >Config.rsc >&1
+ssh -i /var/www/ros.vados.ru/Mikrotiks/.ssh/id_rsa -o PasswordAuthentication=no -p 2222 mikro_bak@10.30.30.10 export >Config.rsc >&2
+ssh -i /var/www/ros.vados.ru/Mikrotiks/.ssh/id_rsa -o PasswordAuthentication=no -p 2222 mikro_bak@10.30.30.10 export
+./ros-diff.sh
+clear
+./ros-diff.sh
+diff --help
+./ros-diff.sh
+ros-diff.sh
+./ros-diff.sh
+1
+./ros-diff.sh
+/usr/bin/ssh -2 -4 -p 2222 -l mikro_bak 10.30.30.10 -i /var/www/ros.vados.ru/Mikrotiks/.ssh/id_rsa -o PasswordAuthentication=no export > "/var/www/ros.vados.ru/Mikrotiks/backups/MikroNUC/tmp/Config.rsc"
+`
+
+q
+./ros-diff.sh
+cd ..
+cd backups/
+git fetch
+git add .
+git push
+./ros-diff.sh
+git init
+git add .
+git config --global --add safe.directory /var/www/ros.vados.ru
+git add .
+git commit -m 'Initial commit'
+git push
+git push origin
+git fetch origin
+git config --global --add safe.directory /var/www/ros.vados.ru
+git init
+git config --global --add safe.directory /var/www/ros.vados.ru
+git add .
+git commit -m 'Initial commit'
+git init
+git commit -m 'Initial commit'
+git add .
+git commit -m 'Initial commit'
+git init
+cat config
+git add .
+git commit -m 'Initial commit'
+git fetch origin
+git push origin
+git pull
+git push
+git init
+git add .
+git commit -m 'Initial commit 1'
+git fetch
+git push
+git init
+git add .
+git commit -m 'Initial commit'
+git push
+./ros-diff.sh
+./test.sh
+./ros-diff.sh
+touch ~/.gnupg/gpg-agent.conf
+gpg --import vados-dev_public.gpg 
+gpg --delete vados-dev
+gpg --import vados-dev.pub 
+gpg -k
+git config --global user.signingkey ABE4CF61A85CDD6E15F2C02DFCC5E4C7407B8BF7
+git config --global commit.gpgsign true
+cd ~
+mc
+exit
+gpg -k
+clear
+gpg -k
+gpg --delete ABE4CF61A85CDD6E15F2C02DFCC5E4C7407B8BF7
+cd Mikrotiks/
+gpg --import gpg_vados-dev.pub 
+gpg --import gpg_vados-dev.asc 
+ls -la
+gpg --import gpg_vados-dev.asc
+gpg -k
+gpg --delete 0x1FD2694762FCE438
+gpg --import gpg_vados-dev.asc
+cd ~
+gpg --import gpg_vados-dev.asc
+gpg --import gpg_vados-dev.asc 
+gpg -k
+exit
+mc
+cd ~
+source ~/.bashrc
+mc
+cd ~/.ssh/
+cat authorized_keys 
+cd ../
+gpg -k
+gpg --delete 0x1FD2694762FCE438
+exit
+git fetch
+git push
+git push main
+git push --set-upstream main main
+git commit -m 'Mikro_bak Initial Commit.'
+git config --global --add safe.directory /var/www/ros.vados.ru
+git commit -m 'Mikro_bak Initial Commit.'
+mc
+exit
diff --git a/Mikrotiks/.bashrc b/Mikrotiks/.bashrc
new file mode 100644 (file)
index 0000000..969098e
--- /dev/null
@@ -0,0 +1,127 @@
+# ~/.bashrc: executed by bash(1) for non-login shells.
+# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
+# for examples
+
+# If not running interactively, don't do anything
+case $- in
+    *i*) ;;
+      *) return;;
+esac
+
+# don't put duplicate lines or lines starting with space in the history.
+# See bash(1) for more options
+HISTCONTROL=ignoreboth
+
+# append to the history file, don't overwrite it
+shopt -s histappend
+
+# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
+HISTSIZE=1000
+HISTFILESIZE=2000
+
+# check the window size after each command and, if necessary,
+# update the values of LINES and COLUMNS.
+shopt -s checkwinsize
+
+# If set, the pattern "**" used in a pathname expansion context will
+# match all files and zero or more directories and subdirectories.
+#shopt -s globstar
+
+# make less more friendly for non-text input files, see lesspipe(1)
+[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
+
+# set variable identifying the chroot you work in (used in the prompt below)
+if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
+    debian_chroot=$(cat /etc/debian_chroot)
+fi
+
+# set a fancy prompt (non-color, unless we know we "want" color)
+case "$TERM" in
+    xterm-color|*-256color) color_prompt=yes;;
+esac
+
+# uncomment for a colored prompt, if the terminal has the capability; turned
+# off by default to not distract the user: the focus in a terminal window
+# should be on the output of commands, not on the prompt
+#force_color_prompt=yes
+
+if [ -n "$force_color_prompt" ]; then
+    if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
+       # We have color support; assume it's compliant with Ecma-48
+       # (ISO/IEC-6429). (Lack of such support is extremely rare, and such
+       # a case would tend to support setf rather than setaf.)
+       color_prompt=yes
+    else
+       color_prompt=
+    fi
+fi
+
+if [ "$color_prompt" = yes ]; then
+    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
+else
+    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
+fi
+unset color_prompt force_color_prompt
+
+# If this is an xterm set the title to user@host:dir
+case "$TERM" in
+xterm*|rxvt*)
+    PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
+    ;;
+*)
+    ;;
+esac
+
+# enable color support of ls and also add handy aliases
+if [ -x /usr/bin/dircolors ]; then
+    test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
+    alias ls='ls --color=auto'
+    #alias dir='dir --color=auto'
+    #alias vdir='vdir --color=auto'
+
+    alias grep='grep --color=auto'
+    alias fgrep='fgrep --color=auto'
+    alias egrep='egrep --color=auto'
+fi
+
+# colored GCC warnings and errors
+#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
+
+# some more ls aliases
+alias ll='ls -alF'
+alias la='ls -A'
+alias l='ls -CF'
+
+# Add an "alert" alias for long running commands.  Use like so:
+#   sleep 10; alert
+alias alert='n8otify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'
+
+# Alias definitions.
+# You may want to put all your additions into a separate file like
+# ~/.bash_aliases, instead of adding them here directly.
+# See /usr/share/doc/bash-doc/examples in the bash-doc package.
+
+if [ -f ~/.bash_aliases ]; then
+    . ~/.bash_aliases
+fi
+
+# enable programmable completion features (you don't need to enable
+# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
+# sources /etc/bash.bashrc).
+if ! shopt -oq posix; then
+  if [ -f /usr/share/bash-completion/bash_completion ]; then
+    . /usr/share/bash-completion/bash_completion
+  elif [ -f /etc/bash_completion ]; then
+    . /etc/bash_completion
+  fi
+fi
+
+# Terminal Prompt full paths
+export PROMPT_DIRTRIM=5
+export force_color_prompt=yes
+#export GIT_SSH=$(which ssh)
+#'"c:\WINDOWS\System32\OpenSSH\ssh.exe" -T'
+#eval $(ssh-agent)
+#ssh-add
+GPG_TTY=`tty`
+export GPG_TTY
diff --git a/Mikrotiks/.bin/bash-progress-indicator/LICENSE b/Mikrotiks/.bin/bash-progress-indicator/LICENSE
new file mode 100644 (file)
index 0000000..830c241
--- /dev/null
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Leandro Nunes
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Mikrotiks/.bin/bash-progress-indicator/README.md b/Mikrotiks/.bin/bash-progress-indicator/README.md
new file mode 100644 (file)
index 0000000..8bbeb1a
--- /dev/null
@@ -0,0 +1,28 @@
+# bash-progress-indicator
+Pure Bash script progress indicators
+
+## Usage
+```
+chmod +x main.sh
+./main.sh {progress_indicator_type}
+```
+
+## progress_indicator_type
+#### progress
+![type_progress](https://user-images.githubusercontent.com/2450417/37887129-6a7fafec-3096-11e8-8bc0-7768e958119e.gif)
+
+#### progress_advanced
+![type_progress_advanced](https://user-images.githubusercontent.com/2450417/37887130-6ddce736-3096-11e8-9696-25b86becbce3.gif)
+
+#### spinner1
+![type_spinner1](https://user-images.githubusercontent.com/2450417/37887236-0e8ccf0c-3097-11e8-9c6d-c6f4fc60b712.gif)
+#### spinner2
+![type_spinner2](https://user-images.githubusercontent.com/2450417/37887241-12e1c814-3097-11e8-91b2-e43e96ff8cd0.gif)
+#### spinner3
+![type_spinner3](https://user-images.githubusercontent.com/2450417/37887242-13012e98-3097-11e8-80cb-ebca61da3109.gif)
+
+## I wrote an article about this technique [pt-br]
+[Medium - [Bash] Construindo indicador de progresso](https://medium.com/meninunes/bash-construindo-indicador-de-progresso-8de94c37683)
+
+## License
+[MIT License](LICENSE) © [Leandro Nunes](https://lnfnunes.com.br)
diff --git a/Mikrotiks/.bin/bash-progress-indicator/main.sh b/Mikrotiks/.bin/bash-progress-indicator/main.sh
new file mode 100755 (executable)
index 0000000..cf565e7
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/bash -e
+# shellcheck disable=SC1091
+
+declare -rx STEPS=(
+  'pre-install'
+  'install'
+  'post-install'
+)
+declare -rx CMDS=(
+  'sleep 10'
+  'sleep 10'
+  'sleep 10'
+)
+
+case $1 in
+  progress)
+    . 'progress.sh'
+    ;;
+  progress_advanced)
+    . 'progress_advanced.sh'
+    ;;
+  spinner*)
+    . 'spinner.sh'
+    ;;
+  *)
+    echo "Invalid option $1!"
+    exit 1
+esac
+
+start
diff --git a/Mikrotiks/.bin/bash-progress-indicator/progress.sh b/Mikrotiks/.bin/bash-progress-indicator/progress.sh
new file mode 100644 (file)
index 0000000..b5ca26e
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/bash -e
+
+declare -rx BAR_SIZE="##########"
+declare -rx CLEAR_LINE="\\033[K"
+
+start() {
+  local MAX_STEPS=${#STEPS[@]}
+  local MAX_BAR_SIZE="${#BAR_SIZE}"
+
+  tput civis -- invisible
+
+  echo -ne "\\r[${BAR_SIZE:0:0}] 0 %$CLEAR_LINE"
+  for step in "${!STEPS[@]}"; do
+    ${CMDS[$step]}
+
+    perc=$(((step + 1) * 100 / MAX_STEPS))
+    percBar=$((perc * MAX_BAR_SIZE / 100))
+    echo -ne "\\r[${BAR_SIZE:0:percBar}] $perc %$CLEAR_LINE"
+  done
+  echo ""
+
+  tput cnorm -- normal
+}
diff --git a/Mikrotiks/.bin/bash-progress-indicator/progress_advanced.sh b/Mikrotiks/.bin/bash-progress-indicator/progress_advanced.sh
new file mode 100644 (file)
index 0000000..ad4f151
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/bash -e
+
+start() {
+  local MAX_STEPS=${#STEPS[@]}
+  local BAR_SIZE="##########"
+  local MAX_BAR_SIZE="${#BAR_SIZE}"
+  local CLEAR_LINE="\\033[K"
+
+  tput civis -- invisible
+
+  for step in "${!STEPS[@]}"; do
+    perc=$((step * 100 / MAX_STEPS))
+    percBar=$((perc * MAX_BAR_SIZE / 100))
+    echo -ne "\\r- ${STEPS[step]} [ ]$CLEAR_LINE\\n"
+    echo -ne "\\r[${BAR_SIZE:0:percBar}] $perc %$CLEAR_LINE"
+
+    ${CMDS[$step]}
+
+    perc=$(((step + 1) * 100 / MAX_STEPS))
+    percBar=$((perc * MAX_BAR_SIZE / 100))
+    echo -ne "\\r\\033[1A- ${STEPS[step]} [✔]$CLEAR_LINE\\n"
+    echo -ne "\\r[${BAR_SIZE:0:percBar}] $perc %$CLEAR_LINE"
+  done
+  echo ""
+
+  tput cnorm -- normal
+}
diff --git a/Mikrotiks/.bin/bash-progress-indicator/spinner.sh b/Mikrotiks/.bin/bash-progress-indicator/spinner.sh
new file mode 100644 (file)
index 0000000..b68cf89
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/bash -e
+
+declare -x FRAME
+declare -x FRAME_INTERVAL
+
+set_spinner() {
+  case $1 in
+    spinner1)
+      FRAME=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
+      FRAME_INTERVAL=0.1
+      ;;
+    spinner2)
+      FRAME=("-" "\\" "|" "/")
+      FRAME_INTERVAL=0.25
+      ;;
+    spinner3)
+      FRAME=("◐" "◓" "◑" "◒")
+      FRAME_INTERVAL=0.5
+      ;;
+    spinner4)
+      FRAME=(":(" ":|" ":)" ":D")
+      FRAME_INTERVAL=0.5
+      ;;
+    spinner5)
+      FRAME=("◇" "◈" "◆")
+      FRAME_INTERVAL=0.5
+      ;;
+    spinner6)
+      FRAME=("⚬" "⚭" "⚮" "⚯")
+      FRAME_INTERVAL=0.25
+      ;;
+    spinner7)
+      FRAME=("░" "▒" "▓" "█" "▓" "▒")
+      FRAME_INTERVAL=0.25
+      ;;
+    spinner8)
+      FRAME=("☉" "◎" "◉" "●" "◉")
+      FRAME_INTERVAL=0.1
+      ;;
+    spinner9)
+      FRAME=("❤" "♥" "♡")
+      FRAME_INTERVAL=0.15
+      ;;
+    spinner10)
+      FRAME=("✧" "☆" "★" "✪" "◌" "✲")
+      FRAME_INTERVAL=0.1
+      ;;
+    spinner11)
+      FRAME=("●" "◕" "☯" "◔" "◕")
+      FRAME_INTERVAL=0.25
+      ;;
+    *)
+      echo "No spinner is defined for $1"
+      exit 1
+  esac
+}
+
+start() {
+  local step=0
+
+#  tput civis -- invisible
+
+  while [ "$step" -lt "${#CMDS[@]}" ]; do
+    ${CMDS[$step]} & pid=$!
+
+    while ps -p $pid &>/dev/null; do
+      echo -ne "\\r[   ] ${STEPS[$step]} ..."
+
+      for k in "${!FRAME[@]}"; do
+        echo -ne "\\r[ ${FRAME[k]} ]"
+        sleep $FRAME_INTERVAL
+      done
+    done
+
+    echo -ne "\\r[ ✔ ] ${STEPS[$step]}\\n"
+    step=$((step + 1))
+  done
+
+#  tput cnorm -- normal
+}
+
+set_spinner "$1"
diff --git a/Mikrotiks/.bin/logs b/Mikrotiks/.bin/logs
new file mode 120000 (symlink)
index 0000000..927bfe5
--- /dev/null
@@ -0,0 +1 @@
+/var/log/Mikrotiks
\ No newline at end of file
diff --git a/Mikrotiks/.bin/ros-conf/README.md b/Mikrotiks/.bin/ros-conf/README.md
new file mode 100644 (file)
index 0000000..973a2a7
--- /dev/null
@@ -0,0 +1,41 @@
+# RouterOS Configuration Scripts
+
+Personal RouterOS configuration scripts for home network management.
+
+## Overview
+
+This repository contains RouterOS scripts to configure and diagnose a MikroTik router. Configuration scripts set up router modes (routing, bridging, NAT, DHCP). Check scripts display router settings and diagnostics.
+
+## Quick Start
+
+### Running Scripts
+
+Run scripts on the router using forward slashes:
+
+```routeros
+/system script run config/firewall
+/system script run check/nat
+```
+
+## Network Configuration
+
+The `combo1` port is renamed to `combo1-WAN` for routing mode or `combo1-bridge` for switching mode. The `bridge` interface is used for the internal network.
+
+The WAN interface uses `192.168.200.2/24` with gateway `192.168.200.1`. The bridge interface uses `192.168.88.1/24` as the gateway for the internal network. DHCP serves addresses from `192.168.88.100-192.168.88.199`.
+
+## Hardware
+
+These scripts are tested on CRS106, a MikroTik switch/router.
+
+## Repository Structure
+
+```text
+routeros-config/
+├── scripts/
+│   ├── config/          # Configuration scripts
+│   ├── check/           # Diagnostic scripts
+│   └── README.md        # Format and syntax guidelines
+└── README.md            # This file
+```
+
+For format and syntax guidelines, see [scripts/README.md](scripts/README.md).
diff --git a/Mikrotiks/.bin/ros-conf/setup-ssh-keys.sh b/Mikrotiks/.bin/ros-conf/setup-ssh-keys.sh
new file mode 100755 (executable)
index 0000000..6653373
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/bash
+# Setup SSH keys for RouterOS router
+
+ROUTER_IP="10.30.30.81"
+ROUTER_USER="mikro_bak"
+KEY_NAME=$ROUTER_USER"_rsa@"$ROUTER_IP
+KEY_PATH="$HOME/.ssh/$KEY_NAME"
+
+# Check/create .ssh directory
+if [ ! -d "$HOME/.ssh" ]; then
+    mkdir -p "$HOME/.ssh"
+    chmod 700 "$HOME/.ssh"
+fi
+
+# Generate SSH key if it doesn't exist
+if [ ! -f "$KEY_PATH" ]; then
+    ssh-keygen -t rsa -b 2048 -f "$KEY_PATH" -N "" -C "$ROUTER_USER@$ROUTER_IP"
+fi
+
+# Upload and import key on router
+scp "$KEY_PATH.pub" "$ROUTER_USER@$ROUTER_IP -p 2222":/
+ssh "$ROUTER_USER@$ROUTER_IP -p 2222" "/user ssh-keys import public-key-file=$KEY_NAME.pub user=$ROUTER_USER"
+
+# Verify key works
+if ! ssh -i "$KEY_PATH" -o PasswordAuthentication=no "$ROUTER_USER@$ROUTER_IP -p 2222" "/system identity print" >/dev/null 2>&1; then
+    echo "Error: SSH key authentication failed"
+    exit 1
+fi
diff --git a/Mikrotiks/.bin/ros-conf/sync-script.sh b/Mikrotiks/.bin/ros-conf/sync-script.sh
new file mode 100755 (executable)
index 0000000..ca4c1de
--- /dev/null
@@ -0,0 +1,139 @@
+#!/bin/bash
+# Upload all scripts and sync to RouterOS
+
+ROUTER_IP="10.30.30.10"
+ROUTER_USER="mikro_bak"
+SSH_KEY="$HOME/.ssh/mikro_bak_rsa"
+RSC_FILE="import_scripts.rsc"
+
+# Require router-specific SSH key
+if [ ! -f "$SSH_KEY" ]; then
+    echo "Error: SSH key not found at $SSH_KEY"
+    echo "Run ./setup-ssh-keys.sh to generate the key"
+    exit 1
+fi
+
+SSH_OPTS="-i $SSH_KEY -o PasswordAuthentication=no"
+
+# Generate .rsc import file
+echo "Generating import file..."
+
+# Get git commit hash and dirty status
+GIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
+if [ -z "$(git status --porcelain 2>/dev/null)" ]; then
+    GIT_STATUS="$GIT_HASH"
+else
+    GIT_STATUS="$GIT_HASH-dirty"
+fi
+
+# Get current date with timezone offset
+GEN_DATE=$(date '+%Y-%m-%d %H:%M:%S %z')
+
+# Write header
+cat > "$RSC_FILE" << RSC_HEADER
+# RouterOS script import file
+# Generated by sync-script.sh
+# Date: $GEN_DATE
+# Git: $GIT_STATUS
+
+RSC_HEADER
+
+# Write header for creating scripts section
+printf ':put "=== Creating scripts ==="\n' >> "$RSC_FILE"
+printf '\n' >> "$RSC_FILE"
+
+# Create function for script creation
+printf ':global createScriptIfMissing do={\n' >> "$RSC_FILE"
+printf '    :if ([/system script print count-only where name=$scriptName] = 0) do={\n' >> "$RSC_FILE"
+printf '        :put ("Creating script: " . $scriptName);\n' >> "$RSC_FILE"
+printf '        /system script add name=$scriptName\n' >> "$RSC_FILE"
+printf '    }\n' >> "$RSC_FILE"
+printf '}\n\n' >> "$RSC_FILE"
+
+# Write all add commands first
+for SCRIPT_FILE in scripts/config/*.rsc scripts/check/*.rsc; do
+    if [ ! -f "$SCRIPT_FILE" ]; then
+        continue
+    fi
+    
+    SCRIPT_NAME=$(sed -n '1p' "$SCRIPT_FILE" | sed 's/^# //')
+    POLICY=$(sed -n '3p' "$SCRIPT_FILE" | sed -n 's/^# policy=\(.*\)/\1/p')
+    
+    if [ -z "$POLICY" ]; then
+        echo "Error: Missing policy in $SCRIPT_FILE (line 3 should be '# policy=read,write')"
+        exit 1
+    fi
+    
+    # Call function with script name
+    printf '$createScriptIfMissing scriptName="%s"\n' "$SCRIPT_NAME" >> "$RSC_FILE"
+done
+
+# Empty line between sections
+printf '\n' >> "$RSC_FILE"
+printf ':put ""\n' >> "$RSC_FILE"
+printf ':put "=== Updating scripts ==="\n' >> "$RSC_FILE"
+printf '\n' >> "$RSC_FILE"
+
+# Write all set commands
+for SCRIPT_FILE in scripts/config/*.rsc scripts/check/*.rsc; do
+    if [ ! -f "$SCRIPT_FILE" ]; then
+        continue
+    fi
+    
+    SCRIPT_NAME=$(sed -n '1p' "$SCRIPT_FILE" | sed 's/^# //')
+    COMMENT=$(sed -n '2p' "$SCRIPT_FILE" | sed 's/^# //')
+    POLICY=$(sed -n '3p' "$SCRIPT_FILE" | sed -n 's/^# policy=\(.*\)/\1/p')
+    
+    # Escape comment for RouterOS (escape quotes)
+    ESCAPED_COMMENT=$(echo "$COMMENT" | sed 's/"/\\"/g')
+    
+    # Read script content and escape for RouterOS .rsc format
+    # Loop through file line by line, escape special chars, join with \n
+    SCRIPT_CONTENT=""
+    while IFS= read -r line || [ -n "$line" ]; do
+        # Escape backslashes, dollar signs, and quotes
+        line=$(echo "$line" | sed 's/\\/\\\\/g' | sed 's/\$/\\$/g' | sed 's/"/\\"/g')
+        if [ -z "$SCRIPT_CONTENT" ]; then
+            SCRIPT_CONTENT="$line"
+        else
+            SCRIPT_CONTENT="$SCRIPT_CONTENT\\n$line"
+        fi
+    done < "$SCRIPT_FILE"
+    
+    # Write set command directly to file
+    printf ':put "Updating script: %s"; /system script set "%s" source="' "$SCRIPT_NAME" "$SCRIPT_NAME" >> "$RSC_FILE"
+    echo "$SCRIPT_CONTENT" | sed 's/\\n/\\n\\\n    /g' >> "$RSC_FILE"
+    printf '" comment="%s" policy=%s\n\n' "$ESCAPED_COMMENT" "$POLICY" >> "$RSC_FILE"
+done
+
+# Delete the helper function
+printf '\n:set createScriptIfMissing;\n' >> "$RSC_FILE"
+
+# Print all scripts (excluding source/contents)
+printf ':put ""\n:put "=== All system scripts ==="\n/system script print proplist=name,comment,owner,policy,dont-require-permissions,run-count,last-started,invalid\n' >> "$RSC_FILE"
+
+# Show generated .rsc file and ask for confirmation
+echo ""
+echo "=== Generated import file ($RSC_FILE) ==="
+head -20 "$RSC_FILE"
+echo "..."
+echo ""
+read -p "Upload and import this file? (y/N): " -n 1 -r
+echo
+if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+    echo "Aborted"
+    rm -f "$RSC_FILE"
+    exit 1
+fi
+
+# Upload .rsc file
+echo "Uploading import file..."
+scp $SSH_OPTS "$RSC_FILE" "$ROUTER_USER@$ROUTER_IP":/
+
+# Import on router
+echo "Importing scripts..."
+ssh $SSH_OPTS "$ROUTER_USER@$ROUTER_IP" "/import file-name=$RSC_FILE"
+
+# Cleanup
+rm -f "$RSC_FILE"
+echo "Done!"
diff --git a/Mikrotiks/.bin/ros-diff.sh b/Mikrotiks/.bin/ros-diff.sh
new file mode 100755 (executable)
index 0000000..1babd04
--- /dev/null
@@ -0,0 +1,665 @@
+#!/bin/bash
+#set +e -u
+#o pipefail
+# Add to cron
+# crontab -e 
+#*/20 * * * * /var/www/ros.vados.ru/Mikrotiks/.bin/ros-diff.sh
+#set +eo pipefail
+#set -x
+#GEN_DATE=$(date '+%Y-%m-%d %H:%M:%S %z')
+#GEN_DATE_TIME=$(date '+%d.%m.%Y %T %Z')
+#
+#ASK_NEXT=true
+#ask_forNext() {
+#    local ask $1
+#    local item $2
+#    echo -n $ask
+#read item
+#case "$item" in
+#    y|Y) echo "Ввели «y», продолжаем..."
+#        ;;
+#    n|N) echo "Ввели «n», завершаем..."
+#       exit 0
+#        ;;
+#    *) echo "Ничего не ввели. Выполняем действие по умолчанию..."
+#        ;;
+#esac
+#}
+#${CMD_SSH} ${SSH_OPTS} ${SSHuser}@${SSHhost} system script run MkTmpfs;
+#
+#### Utils #############################################################################################
+CMD_FIND=$(which find)
+CMD_MV=$(which mv)
+CMD_GZ=$(which gzip)
+CMD_CHO=$(which chown)
+CMD_CHM=$(which chmod)
+CMD_MKD=$(which mkdir)" -p "
+CMD_RM=$(which rm)
+CMD_DATE=$(date +%Y%m%d_%H%M) # date in format YYYYMMDD_HHmm
+CMD_SSL=$(which openssl)
+CMD_SSH=$(which ssh)
+CMD_SCP=$(which scp)
+CMD_SFTP=$(which sftp)
+
+ScriptName=$(basename -- "$0")
+# HomeDir=$HOME
+HomeDir="/var/www/ros.vados.ru/Mikrotiks"
+HostsFile=${HomeDir}"/.config/.mikrotiks"
+BinDir=${HomeDir}"/.bin"
+GitDir=${HomeDir}"/backups"
+
+# Set SSH defaults:
+DEFAULT_SSH_PORT="22"
+DEFAULT_SSH_USER="admin"
+DEFAULT_CONNECT_ATTEMPTS=5
+DEFAULT_CONNECT_TIMEOUT=5
+DEFAULT_SSH_KEY=${HomeDir}"/.ssh/id_rsa"
+# Set default diff args:
+DEFAULT_DIFF_ARGS=("-I \"RouterOS\"")
+#
+# Set debug and log options:
+DEBUG_OUT=true
+SAVE_LOG=true
+# Set default LogFile:
+DEFAULT_LOG="/var/log/Mikrotiks/ros-diff.log"
+#
+# Default backup variables:
+DEFAULT_BIN_CRYPT=true
+DEFAULT_BINPWD="BinPwd123"
+DEFAULT_EXP_CRYPT=false
+DEFAULT_EXPPWD="ExpPwd321"
+
+# Handy tput commands:
+#tput bold - Bold effect    #'\033[1m'
+#tput rev - Display inverse colors
+#tput sgr0 - Reset everything    #'\033[0m'
+#tput setaf {CODE}- Set foreground color, see color {CODE} below
+#tput setab {CODE}- Set background color, see color {CODE} below
+# Colors:
+#0    Black
+#1    Red    #'\033[1;31m'
+#2    Green    #'\033[1;32m'
+#3    Yellow    #'\033[1;33m'
+#4    Blue    # '\033[1;34m'
+#5    Magenta    # '\033[1;35m'
+#6    Cyan    # '\033[1;36m'
+#7    White    # '\033[1;37m'
+if [ -t 1 ] && $DEBUG_OUT; then
+    reset=$(tput sgr0)
+    bold=$(tput bold)
+    red=$(tput setaf 1)
+    green=$(tput setaf 2)
+    yellow=$(tput setaf 3)
+    blue=$(tput setaf 4)
+    magenta=$(tput setaf 5)
+    cyan=$(tput setaf 6)
+    white=$(tput setaf 7)
+    toend=$(tput hpa $(tput cols))$(tput cub 6)
+else
+    reset='' bold='' red='' green='' yellow='' blue='' magenta='' cyan='' white='' toend=''
+fi
+
+info() {
+    echo -e "\n${blue}ℹ  $*${reset}"
+}
+success() {
+    echo -e "\n${green}✅  $*${reset}"
+}
+error() {
+    echo -e "\n${red}❌ $*${reset}" >&2
+}
+warning() {
+    echo -e "${yellow}⚠  $*${reset}" >&2
+}
+section() {
+    echo -e "\n${magenta}................[$(tput rev) $* ${reset}${magenta}]................${reset}\n"
+}
+
+FRAME=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
+FRAME_INTERVAL=0.1
+
+start() {
+  local step=0
+  while [ "$step" -lt "${#CMDS[@]}" ]; do
+    ${CMDS[$step]} & pid=$!
+    while ps -p $pid &>/dev/null; do
+      echo -ne "\\r[   ] ${STEPS[$step]} ..."
+      for k in "${!FRAME[@]}"; do
+        echo -ne "\\r[ ${FRAME[k]} ]"
+        sleep $FRAME_INTERVAL
+      done
+    done
+    echo -ne "\\r[ ✔ ] ${STEPS[$step]}\\n"
+    step=$((step + 1))
+  done
+}
+#echo '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
+spinner()
+{
+    local pid=$!
+    local delay=0.75
+    local spinstr='|/-\'
+    #'⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
+    while [ "$(ps a | awk '{print $1}' | grep $pid)" ]; do
+        local temp=${spinstr#?}
+        printf " [%c] " "${spinstr}"
+        local spinstr=$temp${spinstr%"$temp"}
+        sleep $delay
+        printf "\b\b\b\b\b\b"
+    done
+    printf "    \b\b\b\b"
+}
+
+save_log() {
+    local log="${DEFAULT_LOG}"
+    if [ -n "${LogFile}" ]; then
+        log="${LogFile}"
+    fi
+    if ${SAVE_LOG}; then
+        echo "$(date '+%b %d %T') - $*" >> ${log}
+        return 0
+    else
+        return 1
+    fi
+}
+
+get_connections_str() {
+    local opts
+    local log_msg
+    local host
+    local ssh_str
+    local scp_str
+
+# Require router-specific SSH key
+    if [ -n "$DEFAULT_SSH_KEY" ]; then
+        opts=("-i ${DEFAULT_SSH_KEY}" "-o PasswordAuthentication=no")
+    else
+    log_msg="- Error: SSH key not found at $DEFAULT_SSH_KEY\nRun ./setup-ssh-keys.sh to generate the key"
+        if [ -t 1 ] && ${DEBUG_OUT}; then
+            error "${RouterName} ${log_msg}"
+        fi
+    save_log "${log_msg}"
+    exit 1
+    fi
+#    if [ -n "$DEFAULT_CONNECT_ATTEMPTS" ]; then
+#        opts+=("-o ConnectionAttempts=${DEFAULT_CONNECT_ATTEMPTS}")
+#    fi
+#    if [ -n "$DEFAULT_CONNECT_TIMEOUT" ]; then
+#        opts+=("-o ConnectTimeout=${DEFAULT_CONNECT_TIMEOUT}")
+#    fi
+    #opts+=("-o PreferredAuthentications=publickey" "-o StrictHostKeyChecking=no" "-o UserKnownHostsFile=/dev/null" \
+    #"-o GlobalKnownHostsFile=/dev/null" "-o CheckHostIP=no")
+SSH_OPTS="${opts[*]}"
+
+# Require router host IP
+    if [ -n "${SSHhost}" ]; then
+        host="${SSHhost}"
+    else
+        log_msg="- Error! SSH Host not found. Exit 1."
+        if [ -t 1 ] && ${DEBUG_OUT}; then
+            error "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+        exit 1
+    fi
+
+    ssh_str=("$CMD_SSH" "-2" "-4")    
+    scp_str=("${CMD_SCP}" "-2" "-4" "-B ${SSH_OPTS}")
+    
+    if [ -n "${SSHport}" ]; then
+        ssh_str+=("-p ${SSHport}")
+        scp_str+=("-P ${SSHport}")
+    else
+        ssh_str+=("-p ${DEFAULT_SSH_PORT}")
+        scp_str+=("-P ${DEFAULT_SSH_PORT}")
+    fi
+    
+    if [ -n "${SSHuser}" ]; then
+        ssh_str+=("-l ${SSHuser}")
+        scp_str+=("${SSHuser}@${host}")
+    else
+        ssh_str+=("-l ${DEFAULT_SSH_USER}")
+        scp_str+=("${DEFAULT_SSH_USER}@${host}")
+    fi
+
+    ssh_str+=("${host}")
+
+ssh_str+=("${SSH_OPTS}")
+SSH_STR="${ssh_str[*]}"
+SCP_STR="${scp_str[*]}"
+#SFTP_STR="${sftp_str[*]}"
+#SFTP_OPTS="-i ${DEFAULT_SSH_KEY} -o PasswordAuthentication=no -oPort=${SSHport}"
+}
+
+#>/dev/null 2>&1
+###############################
+# Backup listing 
+###############################
+# Get Names Addresses and Ports in $HostsFile
+#declare -rx STEPS=(
+#  'download'
+#  'check'
+#  'backup'
+#)
+#IFS=$'\n'
+run_ssh_cmd() {
+    local run="${1}"
+    warning "${run}"
+    if ${SSH_STR} ${run}; then
+        return 0
+    else
+        return 1
+    fi
+}
+
+download_config() {
+    local conf="${1}"
+    local log_msg
+
+    if ${SSH_STR} export >${conf}; then
+        log_msg="- Config download success!"
+        if [ -t 1 ] && $DEBUG_OUT; then
+            success "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+        return 0
+    else
+        log_msg="- Config download ${conf} error! Exit Error 1."
+        if [ -t 1 ] && $DEBUG_OUT; then
+            error "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+        exit 1
+    fi
+}
+
+compare_config() {
+    local config_file="${1}"
+    local precedent_file="${2}" 
+    local diff_file="${3}"
+    local diff_args=${DEFAULT_DIFF_ARGS}
+    local log_msg
+
+    diff ${diff_args} ${config_file} ${precedent_file} >${diff_file}
+    if [ "$?" -ne "0" ]; then
+        log_msg="- There is a difference! Create backup..."
+        if [ -t 1 ] && $DEBUG_OUT; then
+            section " CREATE BACKUP "
+            sleep 1
+            info "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+        return 0
+    else
+        log_msg="- The files are identical. No backup is required."
+        if [ -t 1 ] && $DEBUG_OUT; then
+            section " START CLEANUP "
+            sleep 1
+            info "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+        log_msg="- Remove ${diff_file} file."
+        if [ -t 1 ] && $DEBUG_OUT; then
+            info "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+        ${CMD_RM} -f ${diff_file}
+        log_msg="- Remove ${config_file} file."
+        if [ -t 1 ] && $DEBUG_OUT; then
+            info "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+        ${CMD_RM} -f ${config_file}
+
+        return 1
+    fi
+}
+
+check_directory() {
+    local chk_dir="${1}"
+    local log_msg
+
+ if [[ -d ${chk_dir} && -r ${chk_dir} ]]
+  then
+   return 0
+  else
+    if $CMD_MKD ${chk_dir}; then
+        log_msg="- Direcory ${chk_dir} created."
+        if [ -t 1 ] && $DEBUG_OUT; then
+            success "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+        return 0
+    else
+        log_msg="- Error create direcory ${chk_dir}! Exit 1."
+        if [ -t 1 ] && $DEBUG_OUT; then
+            error "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+        exit 1
+    fi
+# This script must be run by a special user in his home directory.
+# Therefore, the following lines are not needed.
+#   $CMD_CHO root:root $dir
+#   $CMD_CHM 755 $ST_FULL
+ fi
+}
+
+#${SSH_STR} /system script environment get [find name=BackupPassword] value;
+#exit 0
+backup_binary() {
+    local bin_name="${1}"
+    local crypt="${DEFAULT_BIN_CRYPT}"
+    local bin_pass="${DEFAULT_BINPWD}"
+    local log_msg
+    local crypt_str
+    #${SSH_STR} :put [/system script environment get [find name=BackupPassword] value];
+
+    if ${SSH_STR} /system/script environment find where name=BackupEncrypt && ${SSH_STR} /system/script environment find where name=BackupPassword; then
+        #passwd="qqwe"
+        bin_pass=(${SSH_STR} [system script environment get [system script environment find name=BackupPassword] value])
+        crypt_str="dont-encrypt=no password=${bin_pass}"
+        warning "${crypt_str}"
+    else
+        if [ -n ${crypt} ] && [ -n {passwd} ]; then
+            crypt_str="dont-encrypt=no password=${passwd}"
+        else
+            crypt_str="dont-encrypt=yes"
+        fi
+
+    fi
+ #   if [ -n $]; then
+
+#    local BKPSTR="system backup save name=${bin_name} dont-encrypt=no password=$BKP_BINPWD"
+# T_BKPCLN="file remove [find name=$TGT_BKPNAME_BIN]"
+}
+
+readarray -t lines < $HostsFile
+#declare -r STEPS=($lines)
+#declare -r MAX_STEPS=${#lines[@]}
+#declare -r BAR_SIZE="##########"
+#declare -r MAX_BAR_SIZE=${#BAR_SIZE}
+
+for HR in "${lines[@]}"; do
+#  perc=$(((HR + 1) * 100 / MAX_STEPS))
+#  percBar=$((perc * MAX_BAR_SIZE / 100))
+# declare -rx CMDS=(
+#  "${CMD_SSH} ${SSH_OPTS} ${SSHuser}@${SSHhost} export >$current_config"
+#  'sleep 10'
+#  'sleep 1'
+#)
+while IFS=$' ' read -a HR ; do
+#|| [ -n "$HR" ]; do
+[[ -z ${HR[0]} ]] && continue
+
+DStamp=$(date '+%Y%m%d')
+DTStamp=$(date '+%Y%m%d%H%M%S')
+
+RouterName=${HR[0]}
+SSHhost=${HR[1]}
+SSHport=${HR[2]}
+SSHuser=${HR[3]}
+SSHpassword=${HR[4]}
+
+get_connections_str
+#backup_binary "BinaryTest.backup"
+
+#exit 0
+#warning "SSH_STR = ${SSH_STR}"
+#warning "SCP_STR = ${SCP_STR}"
+#exit 0
+
+BaseDir=${HomeDir}"/backups/${RouterName}/"
+TempDir=${BaseDir}"tmp/"
+HistoryDir=${BaseDir}"history/"
+LogFile="/var/log/Mikrotiks/${RouterName}.log"
+log_msg="undefined"
+
+log_msg="- Start backup script ${ScriptName} for ${RouterName}"
+if [ -t 1 ] && $DEBUG_OUT; then
+    info "${RouterName} ${log_msg}"
+    echo
+fi
+save_log "${log_msg}"
+
+log_msg="- Check and creatre services directories for ${RouterName} if not exsist..."
+if [ -t 1 ] && $DEBUG_OUT; then
+    section " CHECK DIRS "
+    info "${RouterName} ${log_msg}"
+fi
+save_log "${log_msg}"
+
+CheckDirs=("${BaseDir}" "${TempDir}" "${HistoryDir}")
+for dir in "${CheckDirs[@]}"; do
+(check_directory "${dir[@]}")
+done
+
+log_msg="All Directories for ${RouterName} was created sucessfully!"
+if [ -t 1 ] && $DEBUG_OUT; then
+    success "${log_msg}"
+fi
+save_log "- ${log_msg}"
+
+log_msg="Set variables for ${RouterName}..."
+if [ -t 1 ] && $DEBUG_OUT; then
+    info "${log_msg}"
+fi
+save_log "- ${log_msg}"
+
+precedent_config=${BaseDir}"Precedent-Config.rsc"
+current_config=${TempDir}"Config.rsc"
+diff_config=${TempDir}"Diff-Config.rsc"
+current_backup="Binary.backup"
+
+log_msg="Set variables for ${RouterName} success!"
+if [ -t 1 ] && $DEBUG_OUT; then
+    success "${log_msg}"
+fi
+save_log "- ${log_msg}"
+
+section " CHECK CONFIG "
+log_msg="- Download ${current_config} file to Temp Directory..."
+if [ -t 1 ] && $DEBUG_OUT; then
+    info "${RouterName} ${log_msg}"
+fi
+save_log "- ${log_msg}"
+
+download_config "${current_config}"
+
+log_msg="- Comparing with the previous config..."
+if [ -t 1 ] && $DEBUG_OUT; then
+    info "${RouterName} ${log_msg}"
+fi
+save_log "- ${log_msg}"
+
+if compare_config ${current_config} ${precedent_config} ${diff_config} ${RouterName} ${LogFile}; then
+    BackupDir=${BaseDir}"${DStamp}/"
+    ${CMD_MKD} ${BackupDir}
+
+    save_config=${BackupDir}"${DTStamp}-Config.rsc"
+    save_backup=${BackupDir}"${DTStamp}-Binary.backup"
+
+    log_msg="- Copy ${current_config} file to ${BackupDir} directory..."
+    if [ -t 1 ] && $DEBUG_OUT; then
+        info "${RouterName} ${log_msg}"
+    fi
+    save_log "- ${log_msg}"
+
+    cp ${current_config} ${save_config}
+
+    ${CMD_RM} -f ${precedent_config}
+
+    ${CMD_MV} -f ${current_config} ${precedent_config}
+
+    log_msg="- Copy configuration to ${BackupDir}/${save_config} and ${precedent_config} success."
+    if [ -t 1 ] && $DEBUG_OUT; then
+        success "${RouterName} ${log_msg}"
+    fi
+    save_log "- ${log_msg}"
+
+    log_msg="Create ${current_backup} file on ${RouterName}..."
+    if [ -t 1 ] && $DEBUG_OUT; then
+        info "${log_msg}"
+    fi
+    save_log "- ${log_msg}"
+#   if [ $? -eq 0 ]; then
+    if ${SSH_STR} system backup save name=${current_backup}; then
+        log_msg="Create binary backup ${current_backup} on ${RouterName} success."
+        if [ -t 1 ] && $DEBUG_OUT; then
+            success "${log_msg}"
+        fi
+        save_log "- ${log_msg}"
+
+        log_msg="- Start download ${current_backup} file to ${BackupDir}..."
+        if [ -t 1 ] && $DEBUG_OUT; then
+            info "${RouterName} ${log_msg}"
+        fi
+        save_log "- ${log_msg}"
+
+#       if ${SFTP_STR}:$current_backup; then
+        #   ${CMD_SFTP} ${SFTP_OPTS} ${SSHuser}@${SSHhost}:$current_backup
+        if ${SCP_STR}:/${current_backup} ${save_backup}; then
+            sleep 2
+            log_msg="- Binary Backup download complete!"
+            if [ -t 1 ] && $DEBUG_OUT; then
+                success "${RouterName} ${log_msg}"
+            fi
+            save_log "${log_msg}"
+        else
+            log_msg="- Error! Save binary backup on ${RoutrName} failed!"
+            if [ -t 1 ] && $DEBUG_OUT; then
+                error "${log_msg}"
+            fi
+            save_log "${log_msg}"
+        fi
+    else
+        log_msg="Error create binary backup ${current_backup} on ${RouterName}!"
+        if [ -t 1 ] && $DEBUG_OUT; then
+            error "${log_msg}"
+        fi
+        save_log "- ${log_msg}"
+    fi
+
+    log_msg="- Remove ${current_backup} file on ${RouterName}."
+    if [ -t 1 ] && $DEBUG_OUT; then
+        section "CLEANUP"
+        info "${log_msg}"
+    fi
+    save_log "- ${log_msg}"
+
+    if ${SSH_STR} /file remove ${current_backup}; then
+        log_msg="- Remove ${current_backup} file on ${RouterName} complete succesfully!"
+        if [ -t 1 ] && $DEBUG_OUT; then
+            success "${RouterName} ${log_msg}"
+        fi
+    save_log "${log_msg}"
+    else
+        log_msg="- Error Remove ${current_backup} file on ${RouterName}!"
+        if [ -t 1 ] && $DEBUG_OUT; then
+            error "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+    fi
+
+    cd "${BaseDir}"
+
+    CurrentHistoryDir=${HistoryDir}"${DStamp}/"
+    ${CMD_MKD} -p ${CurrentHistoryDir}
+    diff_config_history=${CurrentHistoryDir}"${DTStamp}-Diff-Config.rsc"
+    log_msg="- Move ${diff_config} file to ${CurrentHistoryDir}."
+    if [ -t 1 ] && $DEBUG_OUT; then
+        info "${RouterName} ${log_msg}"
+    fi
+    save_log "${log_msg}"
+    
+    if ${CMD_MV} -f ${diff_config} ${diff_config_history}; then
+        log_msg="- Move ${diff_config} file to ${CurrentHistoryDir} complete succesfully!"
+        if [ -t 1 ] && $DEBUG_OUT; then
+            success "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+    else
+        log_msg="- Error move ${diff_config} file to ${CurrentHistoryDir}!"
+        if [ -t 1 ] && $DEBUG_OUT; then
+            error "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+    fi
+
+    log_msg="- Go to Git directory (${GitDir}), add backups files and commit him."
+    if [ -t 1 ] && $DEBUG_OUT; then
+        section " COMMIT "
+        info "${RouterName} ${log_msg}"
+    fi
+    save_log "${log_msg}"
+    cd "${GitDir}/"
+    git add .
+    log_msg="- Git add commit..."
+    if [ -t 1 ] && $DEBUG_OUT; then
+        info "${RouterName} ${log_msg}"
+    fi
+    save_log "${log_msg}"
+    if git commit -m "Backup for ${RouterName} sucessfully creatd at $(date '+%b %d %T')."; then
+        log_msg=" - Git add commit success."
+        if [ -t 1 ] && $DEBUG_OUT; then
+            success "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+    else
+        log_msg="- Git add commit Error!"
+        if [ -t 1 ] && $DEBUG_OUT; then
+            error "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+    fi
+    log_msg="- Git push commit..."
+    if [ -t 1 ] && $DEBUG_OUT; then
+        info "${RouterName} ${log_msg}"
+    fi
+    save_log "${log_msg}"
+    if git push origin main; then
+        log_msg=" - Git push commit success."
+        if [ -t 1 ] && $DEBUG_OUT; then
+            success "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+    else
+        log_msg="- Git push commit Error!"
+        if [ -t 1 ] && $DEBUG_OUT; then
+            error "${RouterName} ${log_msg}"
+        fi
+        save_log "${log_msg}"
+    fi
+else
+    log_msg="- ${RouterName} backup complete!"
+    if [ -t 1 ] && $DEBUG_OUT; then
+        info "${RouterName} ${log_msg}"
+    fi
+        save_log "${log_msg}"
+fi
+
+log_msg="- Remove Temp directory."
+if [ -t 1 ] && $DEBUG_OUT; then
+    info "${RouterName} ${log_msg}"
+fi
+    save_log "${log_msg}"
+
+${CMD_RM} -d ${TempDir}
+log_msg="- backup complete!"
+if [ -t 1 ] && $DEBUG_OUT; then
+    success "${RouterName} ${log_msg}"
+fi
+save_log "${log_msg}"
+
+done <<<"$(echo -e $HR)"
+LogFile="${DEFAULT_LOG}"
+#  echo -ne "\\r[${BAR_SIZE:0:percBar}] $perc %"
+#echo "[] $perc %"
+done
+log_msg="All Done! Exit 0."
+if [ -t 1 ] && $DEBUG_OUT; then
+    section " DONE "
+    success "${log_msg}"
+fi
+save_log "${log_msg}"
+exit 0
diff --git a/Mikrotiks/.bin/scripts/mktmpfs.rsc b/Mikrotiks/.bin/scripts/mktmpfs.rsc
new file mode 100644 (file)
index 0000000..dbe979c
--- /dev/null
@@ -0,0 +1,3 @@
+:global MkDir;
+:put [($MkDir /usb1/tmpfs)];
+:log warning ("\$MkDir = " . $MkDir . ".");
diff --git a/Mikrotiks/.bin/test.sh b/Mikrotiks/.bin/test.sh
new file mode 100755 (executable)
index 0000000..67b9dd5
--- /dev/null
@@ -0,0 +1,237 @@
+#!/bin/bash
+# Add to cron
+# crontab -e 
+#*/20 * * * * /var/www/ros.vados.ru/Mikrotiks/.bin/ros-diff.sh
+#set +eo pipefail
+#set -x
+#
+DEBUG_OUT=true
+if [ -t 1 ] && $DEBUG_OUT; then
+    reset='\033[0m'
+    bold=$(tput bold)
+    #'\033[1m'
+    red=$(tput setaf 7)$(tput setab 1)
+    #'\033[1;31m'
+    green=$(tput setaf 4)$(tput setab 7)$(tput bold)
+    #'\033[1;32m'
+    yellow='\033[1;33m'
+    blue='\033[1;34m'
+    magenta='\033[1;35m'
+    cyan='\033[1;36m'
+    white='\033[1;37m'
+else
+    reset='' bold='' red='' green='' yellow='' blue='' cyan='' magenta='' white=''
+fi
+
+info() {
+    if $DEBUG_OUT; then    
+    echo -e "${blue}ℹ $*${reset}"
+    fi    
+}
+success() {
+    if $DEBUG_OUT; then
+    echo -e "${bold}${green}✅ $*${reset}"
+    fi
+}
+error() {
+    if $DEBUG_OUT; then
+    echo -e "${red}❌ Error: $*${reset}" >&2
+    fi
+}
+warning() {
+    if $DEBUG_OUT; then
+    echo -e "${yellow}⚠ $*${reset}" >&2
+    fi
+}
+section() {
+    if $DEBUG_OUT; then
+    echo -e "\n${magenta}====$*====${reset}"
+    fi
+}
+toend=$(tput hpa $(tput cols))$(tput cub 6)
+toend_ok() {
+    if $DEBUG_OUT; then
+    echo -n "${toend}${white}[${green}OK${white}]"
+    fi
+}
+toend_error() {
+    if $DEBUG_OUT; then
+    echo -n "${toend}${white}[${red}ERROR${white}]"
+    fi
+}
+BR() {
+    if $DEBUG_OUT; then
+    echo -e "\n"
+    fi
+}
+
+error "error"
+success "success"
+exit 0
+
+# HomeDir=$HOME
+HomeDir="/var/www/ros.vados.ru/Mikrotiks"
+HostsFile=${HomeDir}"/.config/.mikrotiks"
+BinDir=${HomeDir}"/.bin"
+GitDir=${HomeDir}"/backups"
+
+SSH_KEY=${HomeDir}"/.ssh/id_rsa"
+# Require router-specific SSH key
+if [ ! -f "$SSH_KEY" ]; then
+    error "Error: SSH key not found at $SSH_KEY"
+    error "Run ./setup-ssh-keys.sh to generate the key"
+    exit 1
+fi
+
+###############################
+# Backup listing 
+###############################
+# Get Names Addresses and Ports in $HostsFile
+
+readarray -t lines < $HostsFile
+
+for HR in "${lines[@]}"; do
+while IFS=$' ' read -a HR ; do
+#|| [ -n "$HR" ]; do
+[[ -z ${HR[0]} ]] && continue
+
+#GEN_DATE=$(date '+%Y-%m-%d %H:%M:%S %z')
+GEN_DATE=$(date '+%d.%m.%Y')
+GEN_TIME=$(date '+%b %d %T')
+GEN_DATE_TIME=$(date '+%d.%m.%Y %T %Z')
+DStamp=$(date '+%Y%m%d')
+
+RouterName=${HR[0]}
+SSHhost=${HR[1]}
+SSHport=${HR[2]}
+SSHuser=${HR[3]}
+SSHpassword=${HR[4]}
+
+BaseDir=${HomeDir}"/backups/"${RouterName}
+TempDir=${BaseDir}"/tmp"
+HistoryDir=${BaseDir}"/history"
+LogFile="/var/log/Mikrotiks/"${RouterName}".log"
+
+SSH_OPTS="-i $SSH_KEY -o PasswordAuthentication=no -p ${SSHport}"
+SFTP_OPTS="-i $SSH_KEY -o PasswordAuthentication=no -oPort=${SSHport}"
+echo "${GEN_DATE_TIME} - Start backup script ${ARGV[0]} for ${RouterName}" >> ${LogFile}
+section "Create dirs"
+info "Create servise directories for ${RouterName}..."
+echo "${GEN_TIME} - Create services directories for ${RouterName}..." >> ${LogFile}
+mkdir -p ${BaseDir}
+mkdir -p ${TempDir}
+mkdir -p ${HistoryDir}
+echo "${GEN_TIME} - All Directories for ${RouterName} was created sucessfully!" >> ${LogFile}
+success "All directories for ${RouterName} was created sucessfully!"
+echo "${GEN_TIME} - Set variables for ${RouterName}..." >> ${LogFile}
+info "Set variables for ${RouterName}..."
+precedent_config=${BaseDir}"/Precedent-Config.rsc"
+current_config="Config.rsc"
+current_backup="Binary.backup"
+diff_config=${TempDir}"/Diff-Config.rsc"
+echo "${GEN_TIME} - Set variables for ${RouterName} success!" >> ${LogFile}
+success "Set variables for ${RouterName} success!"
+#ssh ${SSH_OPTS} ${SSHuser}@${SSHhost} system script run MkTmpfs;
+cd ${TempDir}
+section ""
+BR 
+section "Start checkout process"
+info "Download ${current_config} file to Temp Directory..."
+echo "${GEN_TIME} - Download ${current_config} file to ${TempDir}" >> ${LogFile}
+ssh ${SSH_OPTS} ${SSHuser}@${SSHhost} export >$current_config
+if [ $? -eq 0 ]; then
+echo
+toend_ok
+echo
+echo "${GEN_TIME} - Download complete!" >> ${LogFile}
+else
+echo
+toend_error
+echo
+echo "${GEN_TIME} - Download ${current_config} failed!" >> ${LogFile}
+exit 1
+fi
+info "Comparing with the previous config..."
+echo "${GEN_TIME} - Comparing with the previous config..." >> ${LogFile}
+diff -I "RouterOS" ${current_config} ${precedent_config} >${diff_config}
+if [ "$?" -ne "0" ]; then
+echo "${GEN_TIME} - There is a difference! Create backup..." >> ${LogFile}
+section "Create backup for ${RouterName}"
+BackupDir=${BaseDir}"/"${DStamp}
+mkdir -p ${BackupDir}
+DTStamp=$(date '+%Y%m%d%H%M%S')
+save_config=${DTStamp}"-Config.rsc"
+save_backup=${DTStamp}"-Binary.backup"
+diff_config_history=${DTStamp}"-Diff-Config.rsc"
+echo "${GEN_TIME} - Copy ${current_config} file to ${BackupDir} directory..." >> ${LogFile}
+cd ${TempDir}
+rm -f ${precedent_config}
+cp ${current_config} ${BackupDir}"/"${save_config}
+mv -f ${current_config} ${precedent_config}
+echo "${GEN_TIME} - Copy configuration to ${BackupDir}/${save_config} and ${precedent_config} success." >> ${LogFile}
+success "Copy configuration to ${BackupDir}/${save_config} and ${precedent_config} success."
+info "Create ${current_backup} file on ${RouterName}..."
+echo "${GEN_TIME} - Create ${current_backup} file on ${RouterName}..." >> ${LogFile}
+ssh ${SSH_OPTS} ${SSHuser}@${SSHhost} system backup save name=${current_backup}
+if [ $? -eq 0 ]; then
+echo
+toend_ok
+echo
+echo "${GEN_TIME} - Create binary backup ${current_backup} on ${RouterName} success." >> ${LogFile}
+info "Start download ${current_backup} file to ${BackupDir}..."
+echo "${GEN_TIME} - Start download ${current_backup} file to ${BackupDir}..." >> ${LogFile}
+sftp ${SFTP_OPTS} ${SSHuser}@${SSHhost}:$current_backup
+sleep 1
+success "Download complete!"
+echo "${GEN_TIME} - Download complete!" >> ${LogFile}
+cd ${BackupDir}
+sleep 2
+mv -f ${TempDir}"/"${current_backup} ${save_backup}
+echo "${GEN_TIME} - Remove ${current_backup} file on ${RouterName}." >> ${LogFile}
+ssh ${SSH_OPTS} ${SSHuser}@${SSHhost} /file remove ${current_backup}
+else
+echo
+toend_error
+echo
+echo "${GEN_TIME} - Create binary backup ${current_backup} failed!" >> ${LogFile}
+ssh ${SSH_OPTS} ${SSHuser}@${SSHhost} /file remove $current_backup
+exit 1
+fi
+cd ${TempDir}
+CurrentHistoryDir=${HistoryDir}"/"${DStamp}
+mkdir -p ${CurrentHistoryDir}
+echo "${GEN_TIME} - Move ${diff_config} file to ${CurrentHistoryDir}." >> ${LogFile}
+mv -f ${diff_config} ${CurrentHistoryDir}"/"${diff_config_history}
+echo "${GEN_TIME} - Move ${diff_config} file to ${CurrentHistoryDir} complete." >> ${LogFile}
+echo "${GEN_TIME} - Go to Git directory (${GitDir}), add backups files and commit him." >> ${LogFile}
+rm -d ${TempDir}
+cd ${GitDir}
+git add .
+echo "${GEN_TIME} - Git add commit..." >> ${LogFile}
+git commit -m "Backup for ${RouterName} sucessfully creatd at ${GEN_TIME}."
+echo "${GEN_TIME} - Done." >> ${LogFile}
+echo "${GEN_TIME} - Git push..." >> ${LogFile}
+git push origin main
+echo "${GEN_TIME} - Done." >> ${LogFile}
+echo "${GEN_TIME} - ${RouterName} backup complete!"  >> ${LogFile}
+success "${RouterName} backup complete!"
+fi
+section "The files are identical. No backup is required."
+echo "${GEN_TIME} - The files are identical. No backup is required." >> ${LogFile}
+echo "${GEN_TIME} - Remove ${diff_config} file." >> ${LogFile}
+info "Remove ${diff_config} file."
+rm -f ${diff_config}
+echo "${GEN_TIME} - Remove ${current_config} file." >> ${LogFile}
+info "Remove ${current_config} file."
+rm -f ${current_config}
+echo "${GEN_TIME} - Remove Temp directory." >> ${LogFile}
+info "Remove Temp directory."
+rm -d ${TempDir}
+section ""
+success "${RouterName} backup complete!"
+echo "${GEN_TIME} - ${RouterName} backup complete!" >> ${LogFile}
+done <<<"$(echo -e $HR)"
+done
+section "All Done!"
+echo "All Done!" >> ${LogFile}
+exit 0
diff --git a/Mikrotiks/.bin/vados-dev.pub b/Mikrotiks/.bin/vados-dev.pub
new file mode 100644 (file)
index 0000000..f12f291
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBGmljUMBEACxefwXx+q16488USN0uBplV3ZXRqZVSz7B92L0tSGjuND75WFS
+xLty5U1EnAu18Fgh3pKGsznHo4wFbzsOX7EG9DWff7PZAeexlO72iB8xeLdsBPVM
+rXCnYCend3k7PfsHHf9ymhfNho1sasCO792/03cVdSxpEJeiHUW3BgvPHynk87Fm
+I1LW4mhlhBvPAuVOyTz1t+iAJoVcxSAhKFDJ7eMx1yK/bUL0aRTJcy8MmFi4lg3i
+Hh7va8gCHlznPnlMaFJm37T3eaXzjKReSL2f0BVBkFYGHSiEie2k7vXz2fXN7url
+qwKL73fbkOPh60QnH5m5uPjIFk74kxTnNhro80krPFwIx85aRL2L23+qBMznP6cg
+jk2/xX1hP/fIg0YLmmJBwgrNpHJAwfpsaj8BQeNnq2bNHSwqvJdmGDxiuTL7OKu3
+YY+03E0FvZwHpFmm55BwuFyjRGCLoDuShgleZAIfE8rWaApW1YBxUCnp8BMH5kCB
+UE8cpd5qzPvwlc/50ZAchXJ553w5Um6d1ys3X3O4fr2SksTr3QBy7G8rwUHVqYqB
+FuPHyD9X4wBeGKkQlm/BvLdrqXLobosS+gHKK/AYm+Fe+GB+XAIf+Th89Zd0t3Ca
+cTZcezVNmTrW27VRICNet6enePZ+HcLBRT9oP/VO3q4wty86cyUXPlGSFQARAQAB
+tDh2YWRvcy1kZXYgKEdQRyBLZXkgZm9yIHZhZG9zLWRldiA0MDk2LikgPHZhZG9z
+QHZhZG9zLnJ1PokCUQQTAQgAOxYhBKvkz2GoXN1uFfLALfzF5MdAe4v3BQJppY1D
+AhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEPzF5MdAe4v3lMcQAKHn
+TkFfZlCTU1M2KAMzrmXseu9rO7WhYOsYAUehThYU4z6BM1jHv8HLtK+A9i1ncRdm
+qhSL6xWiYE3YT9Bhb2wDaYWLAnRILJtevSCtlqssvjFRfT+9rs3QNxgagQNQuwKq
+UCdnpC1pVj9anVd6H01uJKrr1V++uAca1YxiQwOGnIbw+Z++02eFjvmkqzQo3nwM
+rpKoyo6axqUiBwl9iXJH34qBjtwGiCt2UYl0RCZj5O1j0gpKgqgRHB4B+F2Tp1Jw
+VvfwKmtHymycf0VgvKybhy4PNJcmmFnQOCjJ5ttp7Je3M9jGwtAxHoVcrXtQNTPv
+CZfu8dPX/0GJCmxePW3NTxXhSeOZ/j5nKXdgXjsgcq9tajPBe98dNobfGu1ySWf6
+3vTx1B1hBQeZWTT56RLxzX6u1v9N3WbKjOxVcxRoxWw38zLw3LkXCdGwWgDfyWPF
+v/M7U2sncdCBkmfgcL6dwAjeD+3T/W+iter5POXK+AJ/dy1Zj74sgVfY6rA9A4sI
+9/H2YR4auNMsnpw10qRn+3bPmQkWWR1DrNU5YtQT+Hv+wMpxQYIWyFZWQ00c66SQ
+mfAianad00R5LfR+FpOxgHDZi6qUP1zRjNqjBfQ/OGn4IQZU3CCxK4bKRjMbORc4
+uxwWphbhLLOq3BBS9e7M4RErPtguP1s26vdJLUXauQINBGmljUMBEADBEmQjup5c
+uyjgUBRYvheFNo8ufoD3u08vEybjN68bhdX31NLWUVNL4uoQkYQHI+iKEybkEOF9
+Ac/c5IoDOQ4f5u16d/Aupf1gbDZn7Dy1tNK34qxJjzC5PnWXoy4k1UfuO7/aDfsl
+9Vkl41UwTf4Pc88JxK0DJqFqGvGcL/Z7bHGVjuFmfoeLDrHk4yuXhqHlHQLeMQRW
+wpsZSayzRqNFNlgIFUSyf2WYfuA9AbLc6MseIbEI6zzVCuCq1zLAMkB+fyhijP8i
+s5Z1p+pT0zShV4JVnBcWceSV4Jhc2518+MvWaRtulLSyEJ+uuG2AOXbSmU45MR81
+jRAozUe31ObUzZb1eYJpsIekSCubDEfRaTdQnZ5dn+Vr2qAnLoZljWNrkyn4ABe4
+lPW/nDpPDUwOgiTQFs0BMpWDdceuBzCS7tkuGi7trm817DcaCKwpr/bJziLCPvRe
+vWjenHAykVK88k0jdTk9Hz09wVNOMJTRsUrgY2TtbklW3W35TF3mzgiQHwyNJ9Ya
+C4rr1orhmpDYNYIJhK5VItbEwGV64s2rSDAayYsMIFsSemPdyq9SxNzbtP8PSJgH
+Uyd0CWFwcJxz/06qblaK08mHSWhpuBMVzpjoJrtKTSlGU0TTX2ulUhyDpZy9tYkZ
+wscPc1nIV3KBLtg9iND4r2S6TbqQbJD8oQARAQABiQI2BBgBCAAgFiEEq+TPYahc
+3W4V8sAt/MXkx0B7i/cFAmmljUMCGwwACgkQ/MXkx0B7i/ecug/9H0yWseXEcd9t
+qUiQfKOEwzYhC/RCKikDb17aOuk0W43qhGy68bVk0no4D6ZMlGghwh//NO/6S49p
+16mlHSlOq1bo97Ux1v6uUq/o/JZRwaxMROUxGqMtybhrLwf7rtK9pAhvteUHJpd2
+OIiwO0n/UPTHPa1ZeUllgCTtrNzO8CHr71S/7RRQr1cN8kf4MMqRcAK7Du8DncC5
+WiFl5eOY6qyHEjAhRVr3aO2zUuWLUpaopKlpEYMFxBSaZdBrCsPbX6CMZrE/OUwr
+e0szl33gKb33B4rRpLOFpMT5OySe7ggyfC2gLQvZjWYkVAIAquTvYqsC5d6fWNY8
+9KOEYBjM5ZzHOYenGHiQPOMMCV5nWyZOHYwwvV+EUgSLo42zHW5wWA+yTpVAmHQy
+dJWodLwZviEyyeSUbEluzYzRvpO2s0avaqIhPCkv1p7iuaRBsGt9ro4I/9Kfl3OZ
+v0iWd1iJE6xOceIx6MMI0bIBndXvkbiyyP5AGk52hsmHkxmz8D7IAEFnT27tsly+
+T69wTo+lihCKAMAMMYKz4a6FoTfMwJdSmQU/XIc5mUUm01Phtavibzsunkc8/HAI
+8ZWS7f0TqjQwiTUDQHaiUwOtr4yVcd001xZK8agEBuFSoxZ2gGbR3MppS5SuIY/N
+xxhbToemQWa7K4H99vyLZtG1OhOC0lk=
+=wMLT
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/Mikrotiks/.config/.mikrotiks b/Mikrotiks/.config/.mikrotiks
new file mode 100644 (file)
index 0000000..53a2c1b
--- /dev/null
@@ -0,0 +1,3 @@
+MikroNUC 10.30.30.10 2222 mikro_bak Peskar55
+MikroMaster 10.30.30.81 2222 mikro_bak Peskar55
+MikroYellow 10.30.30.86 2222 mikro_bak Peskar55
diff --git a/Mikrotiks/.config/mc/hotlist b/Mikrotiks/.config/mc/hotlist
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Mikrotiks/.config/mc/ini b/Mikrotiks/.config/mc/ini
new file mode 100644 (file)
index 0000000..ef05681
--- /dev/null
@@ -0,0 +1,165 @@
+[Midnight-Commander]
+verbose=true
+shell_patterns=true
+auto_save_setup=true
+preallocate_space=false
+auto_menu=false
+use_internal_view=true
+use_internal_edit=true
+clear_before_exec=true
+confirm_delete=true
+confirm_overwrite=true
+confirm_execute=false
+confirm_history_cleanup=true
+confirm_exit=false
+confirm_directory_hotlist_delete=false
+confirm_view_dir=false
+safe_delete=false
+safe_overwrite=false
+use_8th_bit_as_meta=false
+mouse_move_pages_viewer=true
+mouse_close_dialog=false
+fast_refresh=false
+drop_menus=true
+wrap_mode=true
+old_esc_mode=true
+cd_symlinks=true
+show_all_if_ambiguous=false
+use_file_to_guess_type=true
+alternate_plus_minus=false
+only_leading_plus_minus=true
+show_output_starts_shell=false
+xtree_mode=false
+file_op_compute_totals=true
+classic_progressbar=true
+use_netrc=true
+ftpfs_always_use_proxy=false
+ftpfs_use_passive_connections=true
+ftpfs_use_passive_connections_over_proxy=false
+ftpfs_use_unix_list_options=true
+ftpfs_first_cd_then_ls=true
+ignore_ftp_chattr_errors=true
+editor_fill_tabs_with_spaces=false
+editor_return_does_auto_indent=false
+editor_backspace_through_tabs=false
+editor_fake_half_tabs=true
+editor_option_save_position=true
+editor_option_auto_para_formatting=false
+editor_option_typewriter_wrap=false
+editor_edit_confirm_save=true
+editor_syntax_highlighting=false
+editor_persistent_selections=true
+editor_drop_selection_on_copy=true
+editor_cursor_beyond_eol=false
+editor_cursor_after_inserted_block=false
+editor_visible_tabs=true
+editor_visible_spaces=true
+editor_line_state=false
+editor_simple_statusbar=false
+editor_check_new_line=false
+editor_show_right_margin=false
+editor_group_undo=true
+editor_state_full_filename=true
+editor_ask_filename_before_edit=false
+nice_rotating_dash=true
+mcview_remember_file_position=false
+auto_fill_mkdir_name=true
+copymove_persistent_attr=true
+pause_after_run=2
+mouse_repeat_rate=100
+double_click_speed=250
+old_esc_mode_timeout=1000000
+max_dirt_limit=10
+num_history_items_recorded=60
+vfs_timeout=60
+ftpfs_directory_timeout=900
+ftpfs_retry_seconds=30
+fish_directory_timeout=900
+editor_tab_spacing=8
+editor_word_wrap_line_length=72
+editor_option_save_mode=0
+editor_backup_extension=~
+editor_filesize_threshold=64M
+editor_stop_format_chars=-+*\\,.;:&>
+mcview_eof=
+skin=default
+
+filepos_max_saved_entries=1024
+shadows=true
+shell_directory_timeout=900
+
+[Layout]
+output_lines=0
+left_panel_size=118
+top_panel_size=0
+message_visible=false
+keybar_visible=false
+xterm_title=true
+command_prompt=true
+menubar_visible=false
+free_space=true
+horizontal_split=false
+vertical_equal=true
+horizontal_equal=true
+
+[Misc]
+timeformat_recent=%b %e %H:%M
+timeformat_old=%b %e  %Y
+ftp_proxy_host=gate
+ftpfs_password=anonymous@
+display_codepage=ASCII
+source_codepage=ASCII
+autodetect_codeset=
+spell_language=en
+clipboard_store=
+clipboard_paste=
+
+[Colors]
+base_color=
+xterm-256color=
+color_terminals=
+linux=
+xterm=
+
+[Panels]
+simple_swap=false
+show_mini_info=true
+kilobyte_si=false
+mix_all_files=false
+show_backups=true
+show_dot_files=true
+fast_reload=false
+fast_reload_msg_shown=false
+mark_moves_down=true
+reverse_files_only=true
+auto_save_setup_panels=false
+navigate_with_arrows=true
+panel_scroll_pages=true
+panel_scroll_center=false
+mouse_move_pages=true
+filetype_mode=true
+permission_mode=false
+torben_fj_mode=false
+quick_search_mode=2
+select_flags=6
+
+[FindFile]
+file_case_sens=true
+file_shell_pattern=true
+file_find_recurs=true
+follow_symlinks=true
+file_skip_hidden=false
+file_all_charsets=false
+content_case_sens=true
+content_regexp=false
+content_first_hit=false
+content_whole_words=false
+content_all_charsets=false
+ignore_dirs_enable=true
+ignore_dirs=
+
+[Panelize]
+Find SUID and SGID programs=find . \\( \\( -perm -04000 -a -perm /011 \\) -o \\( -perm -02000 -a -perm /01 \\) \\) -print
+Modified git files=git ls-files --modified
+Find rejects after patching=find . -name \\*.rej -print
+Find *.orig after patching=find . -name \\*.orig -print
diff --git a/Mikrotiks/.config/mc/panels.ini b/Mikrotiks/.config/mc/panels.ini
new file mode 100644 (file)
index 0000000..92992b2
--- /dev/null
@@ -0,0 +1,35 @@
+[New Left Panel]
+display=listing
+reverse=false
+case_sensitive=true
+exec_first=false
+sort_order=name
+list_mode=full
+brief_cols=2
+user_format=half type name | size | perm
+user_status0=half type name | size | perm
+user_status1=half type name | size | perm
+user_status2=half type name | size | perm
+user_status3=half type name | size | perm
+user_mini_status=false
+list_format=full
+
+[New Right Panel]
+display=listing
+reverse=false
+case_sensitive=true
+exec_first=false
+sort_order=name
+list_mode=full
+brief_cols=2
+user_format=half type name | size | perm
+user_status0=half type name | size | perm
+user_status1=half type name | size | perm
+user_status2=half type name | size | perm
+user_status3=half type name | size | perm
+user_mini_status=false
+list_format=full
+
+[Dirs]
+current_is_left=true
+other_dir=/mnt/d/dst/D41/vbmeta-tools
diff --git a/Mikrotiks/.gitconfig b/Mikrotiks/.gitconfig
new file mode 100644 (file)
index 0000000..c9c53a1
--- /dev/null
@@ -0,0 +1,23 @@
+[user]
+       name = vados-dev
+       email = vados@vados.ru
+       signingkey = 1FD2694762FCE438
+[color]
+       ui = auto
+[safe]
+       directory = /home/vados/sl8541e_cus_go
+       directory = /var/www/ros.vados.ru/html/mikrotik-routeros-scripts
+       directory = /var/www/ros.vados.ru/Mikrotiks/backups
+       directory = /var/www/ros.vados.ru
+[init]
+       defaultBranch = main
+[core]
+       autocrlf = false
+[http]
+       cookiefile = %USERPROFILE%\\.gitcookies
+[protocol]
+       version = 2
+[commit]
+       gpgsign = true
+[gpg]
+       program = /usr/bin/gpg
diff --git a/Mikrotiks/.lesshst b/Mikrotiks/.lesshst
new file mode 100644 (file)
index 0000000..4d1c30b
--- /dev/null
@@ -0,0 +1 @@
+.less-history-file:
diff --git a/Mikrotiks/.profile b/Mikrotiks/.profile
new file mode 100644 (file)
index 0000000..52115e0
--- /dev/null
@@ -0,0 +1,31 @@
+# ~/.profile: executed by the command interpreter for login shells.
+# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
+# exists.
+# see /usr/share/doc/bash/examples/startup-files for examples.
+# the files are located in the bash-doc package.
+
+# the default umask is set in /etc/profile; for setting the umask
+# for ssh logins, install and configure the libpam-umask package.
+#umask 022
+
+# if running bash
+if [ -n "$BASH_VERSION" ]; then
+    # include .bashrc if it exists
+    if [ -f "$HOME/.bashrc" ]; then
+       . "$HOME/.bashrc"
+    fi
+fi
+
+# set PATH so it includes user's private bin if it exists
+if [ -d "$HOME/bin" ] ; then
+    PATH="$HOME/bin:$PATH"
+fi
+
+if [ -d "$HOME/.bin" ] ; then
+    PATH="$HOME/.bin:$PATH"
+fi
+
+# set PATH so it includes user's private bin if it exists
+if [ -d "$HOME/.local/bin" ] ; then
+    PATH="$HOME/.local/bin:$PATH"
+fi
diff --git a/Mikrotiks/.repo_.gitconfig.json b/Mikrotiks/.repo_.gitconfig.json
new file mode 100644 (file)
index 0000000..5d757aa
--- /dev/null
@@ -0,0 +1,20 @@
+{
+  "user.name": [
+    "vados-dev"
+  ],
+  "user.email": [
+    "vados@vados.ru"
+  ],
+  "color.ui": [
+    "auto"
+  ],
+  "safe.directory": [
+    "/var/www/ros.vados.ru/Mikrotiks"
+  ],
+  "init.defaultbranch": [
+    "android-9.0"
+  ],
+  "core.autocrlf": [
+    "false"
+  ]
+}
\ No newline at end of file
diff --git a/Mikrotiks/.selected_editor b/Mikrotiks/.selected_editor
new file mode 100644 (file)
index 0000000..dbc0072
--- /dev/null
@@ -0,0 +1,2 @@
+# Generated by /usr/bin/select-editor
+SELECTED_EDITOR="/usr/bin/mcedit"
diff --git a/Mikrotiks/.wget-hsts b/Mikrotiks/.wget-hsts
new file mode 100644 (file)
index 0000000..33cdbf5
--- /dev/null
@@ -0,0 +1,5 @@
+# HSTS 1.0 Known Hosts database for GNU Wget.
+# Edit at your own risk.
+# <hostname>   <port>  <incl. subdomains>      <created>       <max-age>
+github.com     0       1       1771536570      31536000
+codeload.github.com    0       0       1771536571      31536000
diff --git a/Mikrotiks/gpg_vados-dev.asc b/Mikrotiks/gpg_vados-dev.asc
new file mode 100644 (file)
index 0000000..e281411
--- /dev/null
@@ -0,0 +1,106 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lQcYBGmlw3QBEADpoLGtWkwdPQZ+4U/3VoGuTxwJvo15NFUWl/hwIFJ5BbEGmH3U
+IyFfd+IDtlPVj45FBpi7qHzpuiqGGCaXKATBIz0Dc8TGltYcgqlHYtYvgAyZ/2i9
+rP9dRfC0NIT2r4pOoTQr0tCbtz7zrjfEiB5cGIevAWrDUY2EPLEpKodVy5Ss42Ds
+LZuGDFHVqfg7qR0mzCovIyWE0n9Ki/pgNdBuyCU7kwdIuqHTiP1jUJ2498zaZLTz
+lturorWV4/9pSwMmMbfNyg0xFL3MJSDq4CAo/D5de78ur6buLQnQiQTSYzFzZXiy
+KdWAg/JbmI8oyu7UT6RnEe1waFrF+WB53BS9GsIVN8xaehcpxEAvuWc5x3/kwbHQ
+2empkWhnj3RLr4WjYy8GtCY95Vtmo+YNJk40QlelMcfNS5JHgXry+8hKve8AwgOM
+qd8bnEo0cq9Y3AIQAuIEeAa8aZEpItqMNYWdBCgtrsu5Yj9t8gfkbVT2MkbF233P
+7ku4c+FIy0KvXXpfGsREyudGknStXV4ZdAvYQH/My+UJx9uvs8F8QF5RQi94qAfM
+/fPg0lITd8S8SHy1a/PZWiM7Yh30gUZo4Nce1kdKwddSFSyepHshV+fDdBX5foVd
+rXiLX21E/yUXFA/gN7AUf6m8GpTwU06u6iLZI43krVGJp6vMYb8BRNpdowARAQAB
+AA/7B7JKiCp0XXY4M4/aoYxRA1wJ0WxcnsSRlQf2oEMAVIpSdaQqaQWrQMjjQjoM
+cvmitzVrbKs1SkHeysIa61nfJjmx9d4nel9XuGK5jud7pN70C1K1saH0W69FKbqd
+Mr8/sC1xWYw9YRPdWAvCPJA7Z89LEQHb8YlLPzL8px2GjdYCFNv8wmsSN41KWN6b
+leORWUjajuj6gHCqztrjqa28Kxoe1+KVF9lGIYDe0BnEyGYpcnxk1a1/mRzoHIMB
+VAerl+OFaZshah+DzSjf97zgclk0+MFt3+zbc/c53ovVA9VOuL18B2qHZQ9zuX7C
+IJbm3UmuYdLeXpihfOolOn58lFofhqbLfXxk60IoSpleWU9Ro6FCyiQNhViryV10
+VWWdDDpaa/Dc2WDtWFuXOdssMzIUE0S3B/rhnCyEmL8LH5QqcjkgppyHj4Y+Ecl3
+OD9t1phKqt0zZSXOdYiYrfdpWm8sAnGka+H/URwaaAxApLQZFrQuRgcqGN141T91
+ligW4/HhzlEo7jLLelF2YlU/2uZYEsCCpBMyy2okVoHazNcoDCUxW4cWiP2Ie8vZ
+8AkFwkkaIpGUco8KjUqHYWONOtjLn5Tw1PrEMyk+LqX2J7e7uvB8wPmW4C2Z2Wqu
+TWSP3tVtLgmeIuDJUcwSADUZ/BQEi6wPTWPsXV8cQB5kgmkIAOpPIRS/S5CED8sn
+MmOZn7/zDUlif7X4glqJesMTn1m3i4aTNf4HS8Q89efHX04xolffk6tn2jtovKN/
+fvaDctOmYfvz1yBBn9MBrk+sqySCJ6jEfZe20oNUCppmZTDx+dW6DMl/1e7CGdoh
+1RC6TS1gSxjs4REmnRjX6LSVASr7aWZJbuPaCsj5j7EaDWjCTaSGzThrFKh2DOgz
+LCaUpRctw9AIuElyW3DF7fEWTul/CVzCOCgn4LEiF3xmZzkAIKqsUouTY9NoB0W0
+unA5SkYUUguk9nrVk1iAwc2twepT1EZ6YAyGRUQe1v5sKgR0oVoOKrGdkTHmJGnI
+PEjVnIcIAP9Baqwa5eZTO0h6AvLM7anDv0r3DPq39BYg91KjcpigssonGqrhnWA7
+k12qiu+zeGO8zMb2N9iZlnjdx01CKTjlF0t4rXQDr/tekzwlsRQYY+Pd1Z/lZLev
+nvECnGUOvCNZqS2GC7dokMDN+MEw5eDCk0XUYGe+ECRo+o4Z+8hd/gg44pgNbZ7V
+N+J9OUOWW/x+noOKfQbeJwMT3j9Wz6BoWu/FIbICKfPt7jsuf/3nWAHdIDieu2Op
+LaOvC5+0FcNxL9/lFNdEimSpuFkPn339ivcfT4+dqR6ESMrQnBYFKNbELNNmR+RP
+nKopv7KNeGjJom8T9i4uy7iwyLPZ+QUIANSkU7C8xVpvwOfZaCtVaaoizvw6KsrC
+Y16rVENlw2s0qg0ULCG7UjTuIOW+Phu405Mt2VgYJ31N22g3V/YIRqLxaWWh7Vwd
+nqghD2jvhVKs2fO2Os63J9uV+g4AHckko/XZwplDLsODKLRghYNgXaYaBG3JmG09
+I/9oIxOBDJSjQrplTRhcwOBkc/NTFS2LuHfoT7u3VPgvBt0GrKo5lq6DWdPNJrlb
+xD9h7LQoV87Iyn6ZyqyIQWe8lIggybsu8XjxpmGwWlgoii5uR86O28UGN9Weuf2Q
+tfZztTctRBn1dlH+m+caNw0w+oIt4rPH2dpQl4Onprvb16TNCJFUG71+MrRLdmFk
+b3MtZGV2IChHUEcgS2V5IHZhZG9zLWRldiA0MDk2IGdlbmVyYXRlZCBvbiB2YWRv
+cy1sZW4uKSA8dmFkb3NAdmFkb3MucnU+iQJOBBMBCgA4FiEE3RZHuTqcHrg9EcOc
+H9JpR2L85DgFAmmlw3QCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQH9Jp
+R2L85DjOuBAAuOKtvOnRwNFmZ0wfXuqGV2SHpjXV6E7u91Ai2Q9c8p8zGQE6j1nc
+5GCzC9XlVH5/lL7Bzo0dRWrH/xg/cLmzCfCLURdpcuMwuzTCAXTrPIdjUvqL4sHg
+vu6xutVUVjmeMvX4h/mkrF7yxX2XD6zDOaaMGGIo8y7LxmtjYz43McL8Q9zMA4w6
+hwk8DuRmCoKAO7YfFkK36jQTtIFg2ZjlglbFbHM+vJNFIHySxyjaGgqIO2tvrUrG
+NomyQ+Pl36zBsvLZhJnNgoX13Y281SHogEwyVY3RNlO7f9e6bRPQ5PPr02kBHI2K
+c1Twa0jjYJ1rrV4Tk7m2uoNh7d2UgcrFHux7S6EzwDTvH1NTIGdUcu0tX3EPkakE
+nRvyxOg7NAW632EWw4r1olbFMLCZtxBLTJnEAN7GQLI1oEBAvndwKh9Q+kLZtexU
+PEY0Nq9jhwP8FhT1aENntT8FAgCcNE5QWx5RTRfIsQOjoWxOdqaPujVtTRRYROBH
+acuMrgc0N2Ca61TtK1NtmF/19HJXJ2l3NiDGAks93anodyDpC9Gh7ZbFD+jbB9sH
+vKkRR31IBBB0hMeTbOantXS0XYn1oePl/v21AZTHGEBAervRt7TxqWJygBS9bKye
++uJ6hI9QFAcawzkxaDojRPPQE/eztLIczVToLpUr2zbcHhd/+XyAGYmdBxgEaaXD
+dAEQALm4IiCThbaLlvs6i6b88+/NROIYNy1Tnzi2MO+w7bEUBRcK9Luz232aeTF7
+EWum7yN7dELkUZ4gaK13+RV/oY8hePU3SXQbAqNBvMmAfZvSZNiysGoNd/jTJtL3
+h0DEROSMhFawKoftlUzIBurdqwUDWdxem7Bt1FFFbI7iR1Uak4I0W3Wl6v/IibDc
+oIoF6ZK7E8VZnGK2nDXozE6bYv/xW1vQnAOMUGUcEXClM8UZl6yoeSV2y6b5nTDW
+efP829o8OxWuS2/ccPDSnOD1uZuVRQFgmi8s9Vq8STLSwAgUv+flUnaUKlYxl2oP
+fu2zur0qbinODjWxAPkQ8uBIK/He0MGknr5uOXFwLDRz9z5WEd1ptGypyNlL8exS
+kplc874k5Awd5vl/93eC+SzNZzscl0jeiFV2tyqyQEj4PE/AVVhPiwoPJkWgTIJp
+bRl8S8oixxt4YBnV03wyDVBUVr9MwNiPS3P643veTKv9MK5L/9JnPkeb7bH0NmN5
+w9iEdMtvfnvmi/VmChRDis0kCvhM9B3YDwzsZdUyNET4cMbi7TfCt5JnV76/AMWW
+aPTe6Gyg+5DziHGalnfZNX5Tf22jSk7UvEHvD5EF/bB3y3erme6clGslVZtHWRF/
+Xy6KI++sx81bYbhy9L7N8AOonK0ytZwQrgitZ7FH0FtoeU2FABEBAAEAD/0VaHLS
+o3612KhqREjl0mHCIjzO/TCbEnkXnfTNJG15zI97pQEqA+UIuP5AZGnRKXiGdAwm
+S9BWtHCsklhNkJ1MdPTO+3UFo7ltnT3IUn3oMNv8GLj6OJw8PAbNKchXAGjamZIF
+th/o9EuSArq9Zxj4Jnc8zlEPCNIBHkoRvGaxmjMnvqZOTPZmTrUEhKIi1uSiS9tP
+E/HRdTBTjIbEhufmDenUjxr0079OsGoYPIrXrQgNWIxCc8us7zgd8ZM0O1ESen/r
+E2DRv6zrLbP0wI6JLy85peuQBZQwxeSALcgCBnZKomYbkh/BOM015hxhBSdA9/uj
+6QM9UC6HB1uf2+HouJ9Ad9p4WpryEf9Gb9iFvCmESqqFGdRbtaQbW13i6zIp09KR
+9s0Yt0JyWXra8nENCxmHSzeuOsBMQy3rcN5UeCE7tqQTloOQIKuD5qkglwrUTAS6
+af8U+RI9IaQyNdBoEEzfFCylLmSRXHR3fIgT3nCKo2c6a9l1lWYD463fM6vE2SiS
+CJYCcvwmZW1hnS7c+mVpCuj5U5tU5S85T0YC5ToxxFVWxkSddfFACvz1pG1AX1Wf
+lnmjLDeNoffThJEYCXLt2NHQKJTvuG/4lmcxUiv8NCF+1SXdi+pxfMdP7I18iugU
+ooH/AWz4SJXRxkaAz9e1g9zAQpv8kMWmRajQoQgAxdwRsmoLWPdE6AMn97sdR7Dz
+E1Zp3HNGelhNTGjzf6gyOtv2uCP5XcuFvBV4Nl+KAGk6ja3w+/1iZ7FEsV3136Vz
+yGydKPnwj8UlNuaSrZYbEl6EW12GtCH4zEckgCYu7AKamoo00bLfxJ/ysbzj6f+f
+JmO3zwRJlCRXcPPo38WEbKfM5+GqTEEWbT1LfmDRSJVCXOkhODfKwEnwwCbxh/+P
+HoWwgYDzXjlxDuRwThENhdho1qtX3HtS5JaIV1x8LSr8UBEs2Y2c+H1HAT6z1YEj
+MdGhlATYI4ef7VVHNjdvVOA/7VI9XDbQePv72jWsaiD5Z/08zRAxX4A+r8CNUQgA
+8ErO6lRGeO1dldsgrYq+kvQJqOfWbpqzrBw42vt3kmbgLMaR9C/lZsVDS3c8PzHd
+ZTiZMZfXDc/KpazD92CRVdPv2bEdZZ4NdAiKCnyoLjTMvMY9UmY4gymV1hCXn5N3
+s3sMSwHBdJjfKX0l+H6amfflLepXlEGvk61b1Pkc6j0pdsk5UQsS3YN56X2KPD4h
+wXswZCe5xLpzrkf55Nj3fQQGQObv/cAIEV/BOycr4xsVr8zU/ACkoj5BqD2nkuV3
+8NzPFChhb2gnrxpGg0grgGLTPbOFwTVKSf4GfydG4IY+a2+s7MsyqGgJfZA83scA
+kYfDW4mivf0mltuoYHFf9QgAt/a1RmZu4oXxmvcYXEeEyzBljFbnNMX6eXYXJKFV
+f93DzEqQRF1OEpr5tvJwJtoYcRt0JjjpF1Z9v5vtI4D9DUWg1nIUiKsbiBCBKy6Z
+AXaB9X7Xw+1OHhyxOyBo7FF+QLUR/nW8iASrZ4IwsMHDqS54WhujlZSHMtY4bR1C
+LJKN4YBrhvVKzjqAXQruR20T3+ABC9YjMxLkgRM6ltxrzSXtzbdVSMk6wiwbyRGc
+I2HLWgMM7kFNI7cnpK+aBc+wjuMR1tVh8nfjUTB6scyF+mvdhFc462weNy4Yuowx
+mLoHjTswfCnIVwVzZxSbIz0gA4oRgKYJNu35fjDuL01ThnzAiQI2BBgBCgAgFiEE
+3RZHuTqcHrg9EcOcH9JpR2L85DgFAmmlw3QCGwwACgkQH9JpR2L85DjvMg/9EqNi
+e88kql3/XHXIHAYQvUV703DAF61bskv3QdhI/SHhdT5YlOqirlGcH86Egsu7rKsb
+Ie1v2l951UP4GEZm0mtVJtn7PYwKxhGsPI/WFMdIQZw8FgbcLOMuVBvGgkLFvP7s
+cRnQeKxqsV1oD1FpYttFr3q3efwXCkBa/M7JJoWIDUHqwjbdWMwn8Mmv08f3ey0N
+Dpgn22Ixsy/Wz0pcDNlt3kDunJS7T4SgPX1IoKt2B7V3mUkOnlmBcGMnaxf62fRw
+85/nCXx4YelytGGmu9ofXMRh3kRT0k3YPDfS8/9abHzZm99o2WyCvgfeEoRRGZeB
+BUD7h87yMwBrCBBgnTLyN7klAhl/vaA8x3XncLplLqVARd5Qa0P3zGRRKeu3a0kP
+aeK4IyL6zjg1nW6+/G6PL0FDA5mvRKhOP4z4V6fN0GuA4rDw485jPlG9JkUnBpvR
++WuWTXGRKHZdOgpNhcOTKfROTq3ninQZ2dNATb0bHuKTsJm6+Txp5N5jLIggxCUg
+qlXX+38KeM+Um6AT3pJVWSI58NEQZXR0R65SQXlp363O9JqJnhQSPWLM4Y+Yo+Aq
+IYNZS6cYYW5KFe1OJkJ/R6SOQqegl3zQzuBBNaGIqcihW/m8v45gjpVLIipzeE3h
+JAxMli/egC+h3mICcE6tOPn+N3I2dpnRyMhIBco=
+=zin/
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/Mikrotiks/gpg_vados-dev.pub b/Mikrotiks/gpg_vados-dev.pub
new file mode 100644 (file)
index 0000000..ea2ff13
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBGmlw3QBEADpoLGtWkwdPQZ+4U/3VoGuTxwJvo15NFUWl/hwIFJ5BbEGmH3U
+IyFfd+IDtlPVj45FBpi7qHzpuiqGGCaXKATBIz0Dc8TGltYcgqlHYtYvgAyZ/2i9
+rP9dRfC0NIT2r4pOoTQr0tCbtz7zrjfEiB5cGIevAWrDUY2EPLEpKodVy5Ss42Ds
+LZuGDFHVqfg7qR0mzCovIyWE0n9Ki/pgNdBuyCU7kwdIuqHTiP1jUJ2498zaZLTz
+lturorWV4/9pSwMmMbfNyg0xFL3MJSDq4CAo/D5de78ur6buLQnQiQTSYzFzZXiy
+KdWAg/JbmI8oyu7UT6RnEe1waFrF+WB53BS9GsIVN8xaehcpxEAvuWc5x3/kwbHQ
+2empkWhnj3RLr4WjYy8GtCY95Vtmo+YNJk40QlelMcfNS5JHgXry+8hKve8AwgOM
+qd8bnEo0cq9Y3AIQAuIEeAa8aZEpItqMNYWdBCgtrsu5Yj9t8gfkbVT2MkbF233P
+7ku4c+FIy0KvXXpfGsREyudGknStXV4ZdAvYQH/My+UJx9uvs8F8QF5RQi94qAfM
+/fPg0lITd8S8SHy1a/PZWiM7Yh30gUZo4Nce1kdKwddSFSyepHshV+fDdBX5foVd
+rXiLX21E/yUXFA/gN7AUf6m8GpTwU06u6iLZI43krVGJp6vMYb8BRNpdowARAQAB
+tEt2YWRvcy1kZXYgKEdQRyBLZXkgdmFkb3MtZGV2IDQwOTYgZ2VuZXJhdGVkIG9u
+IHZhZG9zLWxlbi4pIDx2YWRvc0B2YWRvcy5ydT6JAk4EEwEKADgWIQTdFke5Opwe
+uD0Rw5wf0mlHYvzkOAUCaaXDdAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAK
+CRAf0mlHYvzkOM64EAC44q286dHA0WZnTB9e6oZXZIemNdXoTu73UCLZD1zynzMZ
+ATqPWdzkYLML1eVUfn+UvsHOjR1Fasf/GD9wubMJ8ItRF2ly4zC7NMIBdOs8h2NS
++oviweC+7rG61VRWOZ4y9fiH+aSsXvLFfZcPrMM5powYYijzLsvGa2NjPjcxwvxD
+3MwDjDqHCTwO5GYKgoA7th8WQrfqNBO0gWDZmOWCVsVscz68k0UgfJLHKNoaCog7
+a2+tSsY2ibJD4+XfrMGy8tmEmc2ChfXdjbzVIeiATDJVjdE2U7t/17ptE9Dk8+vT
+aQEcjYpzVPBrSONgnWutXhOTuba6g2Ht3ZSBysUe7HtLoTPANO8fU1MgZ1Ry7S1f
+cQ+RqQSdG/LE6Ds0BbrfYRbDivWiVsUwsJm3EEtMmcQA3sZAsjWgQEC+d3AqH1D6
+Qtm17FQ8RjQ2r2OHA/wWFPVoQ2e1PwUCAJw0TlBbHlFNF8ixA6OhbE52po+6NW1N
+FFhE4Edpy4yuBzQ3YJrrVO0rU22YX/X0clcnaXc2IMYCSz3dqeh3IOkL0aHtlsUP
+6NsH2we8qRFHfUgEEHSEx5Ns5qe1dLRdifWh4+X+/bUBlMcYQEB6u9G3tPGpYnKA
+FL1srJ764nqEj1AUBxrDOTFoOiNE89AT97O0shzNVOgulSvbNtweF3/5fIAZibkC
+DQRppcN0ARAAubgiIJOFtouW+zqLpvzz781E4hg3LVOfOLYw77DtsRQFFwr0u7Pb
+fZp5MXsRa6bvI3t0QuRRniBorXf5FX+hjyF49TdJdBsCo0G8yYB9m9Jk2LKwag13
++NMm0veHQMRE5IyEVrAqh+2VTMgG6t2rBQNZ3F6bsG3UUUVsjuJHVRqTgjRbdaXq
+/8iJsNygigXpkrsTxVmcYracNejMTpti//FbW9CcA4xQZRwRcKUzxRmXrKh5JXbL
+pvmdMNZ58/zb2jw7Fa5Lb9xw8NKc4PW5m5VFAWCaLyz1WrxJMtLACBS/5+VSdpQq
+VjGXag9+7bO6vSpuKc4ONbEA+RDy4Egr8d7QwaSevm45cXAsNHP3PlYR3Wm0bKnI
+2Uvx7FKSmVzzviTkDB3m+X/3d4L5LM1nOxyXSN6IVXa3KrJASPg8T8BVWE+LCg8m
+RaBMgmltGXxLyiLHG3hgGdXTfDINUFRWv0zA2I9Lc/rje95Mq/0wrkv/0mc+R5vt
+sfQ2Y3nD2IR0y29+e+aL9WYKFEOKzSQK+Ez0HdgPDOxl1TI0RPhwxuLtN8K3kmdX
+vr8AxZZo9N7obKD7kPOIcZqWd9k1flN/baNKTtS8Qe8PkQX9sHfLd6uZ7pyUayVV
+m0dZEX9fLooj76zHzVthuHL0vs3wA6icrTK1nBCuCK1nsUfQW2h5TYUAEQEAAYkC
+NgQYAQoAIBYhBN0WR7k6nB64PRHDnB/SaUdi/OQ4BQJppcN0AhsMAAoJEB/SaUdi
+/OQ47zIP/RKjYnvPJKpd/1x1yBwGEL1Fe9NwwBetW7JL90HYSP0h4XU+WJTqoq5R
+nB/OhILLu6yrGyHtb9pfedVD+BhGZtJrVSbZ+z2MCsYRrDyP1hTHSEGcPBYG3Czj
+LlQbxoJCxbz+7HEZ0HisarFdaA9RaWLbRa96t3n8FwpAWvzOySaFiA1B6sI23VjM
+J/DJr9PH93stDQ6YJ9tiMbMv1s9KXAzZbd5A7pyUu0+EoD19SKCrdge1d5lJDp5Z
+gXBjJ2sX+tn0cPOf5wl8eGHpcrRhprvaH1zEYd5EU9JN2Dw30vP/Wmx82ZvfaNls
+gr4H3hKEURmXgQVA+4fO8jMAawgQYJ0y8je5JQIZf72gPMd153C6ZS6lQEXeUGtD
+98xkUSnrt2tJD2niuCMi+s44NZ1uvvxujy9BQwOZr0SoTj+M+FenzdBrgOKw8OPO
+Yz5RvSZFJwab0flrlk1xkSh2XToKTYXDkyn0Tk6t54p0GdnTQE29Gx7ik7CZuvk8
+aeTeYyyIIMQlIKpV1/t/CnjPlJugE96SVVkiOfDREGV0dEeuUkF5ad+tzvSaiZ4U
+Ej1izOGPmKPgKiGDWUunGGFuShXtTiZCf0ekjkKnoJd80M7gQTWhiKnIoVv5vL+O
+YI6VSyIqc3hN4SQMTJYv3oAvod5iAnBOrTj5/jdyNnaZ0cjISAXK
+=GLTN
+-----END PGP PUBLIC KEY BLOCK-----
\ No newline at end of file
diff --git a/bin/checksums.log b/bin/checksums.log
new file mode 100644 (file)
index 0000000..e81eb56
--- /dev/null
@@ -0,0 +1,62 @@
+.bin/checksums.sh > checksums.json
+.bin/checksums.sh > checksums.json
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+.bin/checksums.sh > checksums.json
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
+make: Nothing to be done for 'checksums'.
diff --git a/bin/checksums.sh b/bin/checksums.sh
new file mode 100755 (executable)
index 0000000..7f3f589
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+# generate a checksums file as used by $ScriptInstallUpdate
+amsBin="/var/www/ros.vados.ru/bin/"
+amsScripts="/var/www/ros.vados.ru/html/"
+#amsFind="*.rsc"
+#amsSums="checksums.json"
+
+cd $amsScripts
+
+set -e
+
+#ls -la .
+
+make checksums 2>/dev/null
+#> "$amsBin/checksums.log"
+
+#md5sum $(find -name '*.rsc' | sort) | \
+#    sed -e "s| \./||" -e 's|.rsc$||' | \
+#    jq --raw-input --null-input '[ inputs | split (" ") | { (.[1]): (.[0]) }] | add'
+#md5sum $(find -name '$amsFind' | sort) | sed -e "s| \./||" -e 's|.rsc$||' | jq --raw-input --null-input '[ inputs | split (" ") | { (.[1]): (.[0]) }] | add' > '$amsSums'
diff --git a/bin/get_backups.sh b/bin/get_backups.sh
new file mode 100644 (file)
index 0000000..6176dd8
--- /dev/null
@@ -0,0 +1,91 @@
+#!/bin/bash
+date=$(date +%d-%m-%Y)
+time=$(date +%H-%M)
+username="mikro_bak"
+mikrotik=$HOME"/.mikrotiks"
+backup_path=$HOME"/.bak"
+tmp=$HOME"/tmp/"
+log=$tmp"/log.txt"
+#-------------------------------#
+smb_path_backup="//192.168.1.2/Backups"
+domain="WORKGROUP"
+usrname="backuper"
+passwd="root.mikrots.backuper"
+################
+# Backup listing
+################
+# Get addresses
+for i in $( cat $mikrotik ); do
+mkdir -p $tmp"/"$i
+# Get Devices Names
+RESULT=$(ssh "ssh://"$username"@"$i":2222" "system identity print" | awk ' {print $2} ');
+echo "Start backup Devices"
+echo "Start backup Devices Mikrotiks ($time) $RESULT" > $log
+echo "Create Backup $i..."
+ssh "ssh://"$username"@"$i":2222" "system backup save name=binary.backup";
+if [ $? -eq 0 ]; then
+echo -n "$(tput hpa $(tput cols))$(tput cub 6)[OK]"
+echo "Create Backup $i success ($time)" >> $log
+echo 
+else
+echo -n "$(tput hpa $(tput cols))$(tput cub 6)[ERROR]"
+echo "Createbackup $i failed ($time)" >> $log
+echo
+fi
+
+echo "Create configuration $i..."
+ssh "ssh://"$username"@"$i":2222" "export file=export.rsc";
+if [ $? -eq 0 ]; then
+echo -n "$(tput hpa $(tput cols))$(tput cub 6)[OK]"
+echo "Create configuration $i success ($time)" >> $log
+echo
+else
+echo -n "$(tput hpa $(tput cols))$(tput cub 6)[ERROR]"
+echo "Create configuration $i failed ($time)" >> $log
+echo
+fi
+echo "Create backups directory..."
+mkdir -p $tmp/$i/$date/
+echo "Backups directory created $i ($time)" >> $log
+echo
+echo "Download backup files $i..."
+sftp "ssh://"$username"@"$i":2222/binary.backup" $tmp/$i/$date/$i"-"$time".backup";
+sftp "ssh://"$username"@"$i":2222/export.rsc" $tmp/$i/$date/$i"-"$time".rsc";
+if [ $? -eq 0 ]; then
+echo -n "$(tput hpa $(tput cols))$(tput cub 6)[OK]"
+echo "Download backups $i success ($time)" >> $log
+echo
+else
+echo -n "$(tput hpa $(tput cols))$(tput cub 6)[ERROR]"
+echo "Download backups $i failed ($time)" >> $log
+echo
+fi
+
+echo "Compress backups..."
+cd $tmp/$i/
+RESULT=$(tar -czvf $date".tar.gz" $date)
+echo "Backups compressed ($time)" >> $log
+
+#echo "Connect to network share $smb_path_backup..."
+#RESULT=$(cat /home/Mikrotiks/sudos | sudo -S -u root mount -t cifs $smb_path_backup /mnt/samba -o username=$usrname,password=$passwd,domain=$domain)
+#echo "Networkshare $smb_path_backup connected ($time)" >> $log
+
+echo "Create directory for backups device $i..."
+#RESULT=$(cat /home/Mikrotiks/sudos | sudo -S -u root mkdir -p /mnt/samba/Mikrotik/$i)
+RESULT=$(mkdir -p $backup_path/$i)
+echo "Directory for backup device $i created in network share ($time)" >> $log
+
+echo "Place backups in network share"
+RESULT=$(cat /home/Mikrotiks/sudos | sudo -S -u root mv $tmp/$i/$date".tar.gz" "/mnt/samba/Mikrotik/$i/$date.tar.gz")
+echo "Backups $i moved to "$smb_path_backup"/$i ($time)" >> $log
+echo "" >> $log
+
+RESULT=$(cat /home/Mikrotiks/sudos | sudo -S -u root mv $log "/mnt/samba/MIkrotik/$i/$date.log.txt")
+
+echo "Remove local backup files device $i"
+ssh $username"@"$i "file remove binary.backup";
+ssh $username"@"$i "file remove export.rsc";
+rm -r -f $tmp
+
+cat /home/Mikrotiks/sudos | sudo -S -u root umount $smb_path_backup
+done
diff --git a/bin/ros-conf/README.md b/bin/ros-conf/README.md
new file mode 100644 (file)
index 0000000..973a2a7
--- /dev/null
@@ -0,0 +1,41 @@
+# RouterOS Configuration Scripts
+
+Personal RouterOS configuration scripts for home network management.
+
+## Overview
+
+This repository contains RouterOS scripts to configure and diagnose a MikroTik router. Configuration scripts set up router modes (routing, bridging, NAT, DHCP). Check scripts display router settings and diagnostics.
+
+## Quick Start
+
+### Running Scripts
+
+Run scripts on the router using forward slashes:
+
+```routeros
+/system script run config/firewall
+/system script run check/nat
+```
+
+## Network Configuration
+
+The `combo1` port is renamed to `combo1-WAN` for routing mode or `combo1-bridge` for switching mode. The `bridge` interface is used for the internal network.
+
+The WAN interface uses `192.168.200.2/24` with gateway `192.168.200.1`. The bridge interface uses `192.168.88.1/24` as the gateway for the internal network. DHCP serves addresses from `192.168.88.100-192.168.88.199`.
+
+## Hardware
+
+These scripts are tested on CRS106, a MikroTik switch/router.
+
+## Repository Structure
+
+```text
+routeros-config/
+├── scripts/
+│   ├── config/          # Configuration scripts
+│   ├── check/           # Diagnostic scripts
+│   └── README.md        # Format and syntax guidelines
+└── README.md            # This file
+```
+
+For format and syntax guidelines, see [scripts/README.md](scripts/README.md).
diff --git a/bin/ros-conf/setup-ssh-keys.sh b/bin/ros-conf/setup-ssh-keys.sh
new file mode 100755 (executable)
index 0000000..7fcd9f4
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/bash
+# Setup SSH keys for RouterOS router
+
+ROUTER_IP="10.30.30.10"
+ROUTER_USER="mikro_bak"
+KEY_NAME="mikro_bak_rsa"
+KEY_PATH="$HOME/.ssh/$KEY_NAME"
+
+# Check/create .ssh directory
+if [ ! -d "$HOME/.ssh" ]; then
+    mkdir -p "$HOME/.ssh"
+    chmod 700 "$HOME/.ssh"
+fi
+
+# Generate SSH key if it doesn't exist
+if [ ! -f "$KEY_PATH" ]; then
+    ssh-keygen -t rsa -b 4096 -f "$KEY_PATH" -N "" -C "routeros-$ROUTER_IP"
+fi
+
+# Upload and import key on router
+scp "$KEY_PATH.pub" "$ROUTER_USER@$ROUTER_IP":/
+ssh "$ROUTER_USER@$ROUTER_IP" "/user ssh-keys import public-key-file=$KEY_NAME.pub user=$ROUTER_USER"
+
+# Verify key works
+if ! ssh -i "$KEY_PATH" -o PasswordAuthentication=no "$ROUTER_USER@$ROUTER_IP" "/system identity print" >/dev/null 2>&1; then
+    echo "Error: SSH key authentication failed"
+    exit 1
+fi
diff --git a/bin/ros-conf/sync-script.sh b/bin/ros-conf/sync-script.sh
new file mode 100755 (executable)
index 0000000..f559052
--- /dev/null
@@ -0,0 +1,139 @@
+#!/bin/bash
+# Upload all scripts and sync to RouterOS
+
+ROUTER_IP="192.168.88.1"
+ROUTER_USER="admin"
+SSH_KEY="$HOME/.ssh/id_rsa_routeros"
+RSC_FILE="import_scripts.rsc"
+
+# Require router-specific SSH key
+if [ ! -f "$SSH_KEY" ]; then
+    echo "Error: SSH key not found at $SSH_KEY"
+    echo "Run ./setup-ssh-keys.sh to generate the key"
+    exit 1
+fi
+
+SSH_OPTS="-i $SSH_KEY -o PasswordAuthentication=no"
+
+# Generate .rsc import file
+echo "Generating import file..."
+
+# Get git commit hash and dirty status
+GIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
+if [ -z "$(git status --porcelain 2>/dev/null)" ]; then
+    GIT_STATUS="$GIT_HASH"
+else
+    GIT_STATUS="$GIT_HASH-dirty"
+fi
+
+# Get current date with timezone offset
+GEN_DATE=$(date '+%Y-%m-%d %H:%M:%S %z')
+
+# Write header
+cat > "$RSC_FILE" << RSC_HEADER
+# RouterOS script import file
+# Generated by sync-script.sh
+# Date: $GEN_DATE
+# Git: $GIT_STATUS
+
+RSC_HEADER
+
+# Write header for creating scripts section
+printf ':put "=== Creating scripts ==="\n' >> "$RSC_FILE"
+printf '\n' >> "$RSC_FILE"
+
+# Create function for script creation
+printf ':global createScriptIfMissing do={\n' >> "$RSC_FILE"
+printf '    :if ([/system script print count-only where name=$scriptName] = 0) do={\n' >> "$RSC_FILE"
+printf '        :put ("Creating script: " . $scriptName);\n' >> "$RSC_FILE"
+printf '        /system script add name=$scriptName\n' >> "$RSC_FILE"
+printf '    }\n' >> "$RSC_FILE"
+printf '}\n\n' >> "$RSC_FILE"
+
+# Write all add commands first
+for SCRIPT_FILE in scripts/config/*.rsc scripts/check/*.rsc; do
+    if [ ! -f "$SCRIPT_FILE" ]; then
+        continue
+    fi
+    
+    SCRIPT_NAME=$(sed -n '1p' "$SCRIPT_FILE" | sed 's/^# //')
+    POLICY=$(sed -n '3p' "$SCRIPT_FILE" | sed -n 's/^# policy=\(.*\)/\1/p')
+    
+    if [ -z "$POLICY" ]; then
+        echo "Error: Missing policy in $SCRIPT_FILE (line 3 should be '# policy=read,write')"
+        exit 1
+    fi
+    
+    # Call function with script name
+    printf '$createScriptIfMissing scriptName="%s"\n' "$SCRIPT_NAME" >> "$RSC_FILE"
+done
+
+# Empty line between sections
+printf '\n' >> "$RSC_FILE"
+printf ':put ""\n' >> "$RSC_FILE"
+printf ':put "=== Updating scripts ==="\n' >> "$RSC_FILE"
+printf '\n' >> "$RSC_FILE"
+
+# Write all set commands
+for SCRIPT_FILE in scripts/config/*.rsc scripts/check/*.rsc; do
+    if [ ! -f "$SCRIPT_FILE" ]; then
+        continue
+    fi
+    
+    SCRIPT_NAME=$(sed -n '1p' "$SCRIPT_FILE" | sed 's/^# //')
+    COMMENT=$(sed -n '2p' "$SCRIPT_FILE" | sed 's/^# //')
+    POLICY=$(sed -n '3p' "$SCRIPT_FILE" | sed -n 's/^# policy=\(.*\)/\1/p')
+    
+    # Escape comment for RouterOS (escape quotes)
+    ESCAPED_COMMENT=$(echo "$COMMENT" | sed 's/"/\\"/g')
+    
+    # Read script content and escape for RouterOS .rsc format
+    # Loop through file line by line, escape special chars, join with \n
+    SCRIPT_CONTENT=""
+    while IFS= read -r line || [ -n "$line" ]; do
+        # Escape backslashes, dollar signs, and quotes
+        line=$(echo "$line" | sed 's/\\/\\\\/g' | sed 's/\$/\\$/g' | sed 's/"/\\"/g')
+        if [ -z "$SCRIPT_CONTENT" ]; then
+            SCRIPT_CONTENT="$line"
+        else
+            SCRIPT_CONTENT="$SCRIPT_CONTENT\\n$line"
+        fi
+    done < "$SCRIPT_FILE"
+    
+    # Write set command directly to file
+    printf ':put "Updating script: %s"; /system script set "%s" source="' "$SCRIPT_NAME" "$SCRIPT_NAME" >> "$RSC_FILE"
+    echo "$SCRIPT_CONTENT" | sed 's/\\n/\\n\\\n    /g' >> "$RSC_FILE"
+    printf '" comment="%s" policy=%s\n\n' "$ESCAPED_COMMENT" "$POLICY" >> "$RSC_FILE"
+done
+
+# Delete the helper function
+printf '\n:set createScriptIfMissing;\n' >> "$RSC_FILE"
+
+# Print all scripts (excluding source/contents)
+printf ':put ""\n:put "=== All system scripts ==="\n/system script print proplist=name,comment,owner,policy,dont-require-permissions,run-count,last-started,invalid\n' >> "$RSC_FILE"
+
+# Show generated .rsc file and ask for confirmation
+echo ""
+echo "=== Generated import file ($RSC_FILE) ==="
+head -20 "$RSC_FILE"
+echo "..."
+echo ""
+read -p "Upload and import this file? (y/N): " -n 1 -r
+echo
+if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+    echo "Aborted"
+    rm -f "$RSC_FILE"
+    exit 1
+fi
+
+# Upload .rsc file
+echo "Uploading import file..."
+scp $SSH_OPTS "$RSC_FILE" "$ROUTER_USER@$ROUTER_IP":/
+
+# Import on router
+echo "Importing scripts..."
+ssh $SSH_OPTS "$ROUTER_USER@$ROUTER_IP" "/import file-name=$RSC_FILE"
+
+# Cleanup
+rm -f "$RSC_FILE"
+echo "Done!"
diff --git a/html/.bin/checksums.sh b/html/.bin/checksums.sh
new file mode 100755 (executable)
index 0000000..3f69f02
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# generate a checksums file as used by $ScriptInstallUpdate
+amsScripts="/var/www/ros.vados.ru/html/"
+cd $amsScripts
+set -e
+
+md5sum $(find -name '*.rsc' | sort) | \
+       sed -e "s| \./||" -e 's|.rsc$||' | \
+       jq --raw-input --null-input '[ inputs | split (" ") | { (.[1]): (.[0]) }] | add'
diff --git a/html/.bin/commitinfo.sh b/html/.bin/commitinfo.sh
new file mode 100755 (executable)
index 0000000..21faf9f
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+sed \
+       -e "/^:global CommitId/c :global CommitId \"${COMMITID:-unknown}\";" \
+       -e "/^:global CommitInfo/c :global CommitInfo \"${COMMITINFO:-unknown}\";" \
+       < "${1}"
diff --git a/html/.bin/html.sh b/html/.bin/html.sh
new file mode 100755 (executable)
index 0000000..4cefc3f
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+set -e
+
+RELTO="$(dirname "${1}")"
+
+sed \
+       -e "s|__TITLE__|$(head -n1 "${1}")|" \
+       -e "s|__INCLUDE__|$(realpath --relative-to="${RELTO}" .include/)|" \
+       -e "s|__ROOT__|$(realpath --relative-to="${RELTO}" ./)|" \
+       < ".tmpl/head.html"
+
+markdown -f toc,idanchor "${1}" | sed \
+       -e 's/href="\([-_\./[:alnum:]]*\)\.md\(#[-[:alnum:]]*\)\?"/href="\1.html\2"/g' \
+       -e '/<h[1234] /s| id="\(.*\)">| id="\L\1">|' \
+       -e '/<h[1234] /s|-2[1789cd]-||g' -e '/<h[1234] /s|--26-amp-3b-||g' \
+       -e '/^<pre>/s|pre|pre class="code" onclick="CopyToClipboard(this)"|g' \
+       -e '/The above link may be broken on code hosting sites/s|blockquote|blockquote style="display: none;"|'
+
+sed \
+       -e "s|__DATE__|${DATE:-$(date --rfc-email)}|" \
+       -e "s|__VERSION__|${VERSION:-unknown}|" \
+       < ".tmpl/foot.html"
diff --git a/html/.bin/static-html.sh b/html/.bin/static-html.sh
new file mode 100755 (executable)
index 0000000..7acf104
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+set -e
+
+sed -i \
+       -e '/href=/s|\.md|\.html|' \
+       -e '/blockquote/s|/\* display \*/|display: none;|' \
+       -e '/<!-- badges here \/\/-->/r badges.html' \
+       -e '/<!-- badges here \/\/-->/d' \
+       "${@}"
diff --git a/html/.bin/template-capsman.sh b/html/.bin/template-capsman.sh
new file mode 100755 (executable)
index 0000000..5771b53
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+set -e
+
+sed \
+       -e '/\/interface\/wifi\//d' \
+       -e '/\/interface\/wireless\//d' \
+       -e 's|%TEMPL%|.capsman|' \
+       -e '/^# NOT \/caps-man\/ #$/,/^# NOT \/caps-man\/ #$/d' \
+       -e '/^# !!/,/^# !!/c # !! Do not edit this file, it is generated from template!' \
+       < "${1}"
diff --git a/html/.bin/template-local.sh b/html/.bin/template-local.sh
new file mode 100755 (executable)
index 0000000..bc5b327
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+set -e
+
+sed \
+       -e '/\/caps-man\//d' \
+       -e '/\/interface\/wifi\//d' \
+       -e 's|%TEMPL%|.local|' \
+       -e '/^# NOT \/interface\/wireless\/ #$/,/^# NOT \/interface\/wireless\/ #$/d' \
+       -e '/^# !!/,/^# !!/c # !! Do not edit this file, it is generated from template!' \
+       < "${1}"
diff --git a/html/.bin/template-wifi.sh b/html/.bin/template-wifi.sh
new file mode 100755 (executable)
index 0000000..5e297d9
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+set -e
+
+sed \
+       -e '/\/caps-man\//d' \
+       -e '/\/interface\/wireless\//d' \
+       -e 's|%TEMPL%|.wifi|' \
+       -e '/^# NOT \/interface\/wifi\/ #$/,/^# NOT \/interface\/wifi\/ #$/d' \
+       -e '/^# !!/,/^# !!/c # !! Do not edit this file, it is generated from template!' \
+       < "${1}"
diff --git a/html/.gitignore b/html/.gitignore
new file mode 100644 (file)
index 0000000..8abdc28
--- /dev/null
@@ -0,0 +1,16 @@
+# backup and temporary files
+*~
+
+# patches and related files
+*.orig
+*.patch
+*.rej
+
+# html files (as generated from markdown)
+*.html
+
+# checksums file as used by $ScriptInstallUpdate
+checksums.json
+
+# Mac OS X folder settings file
+.DS_Store
diff --git a/html/.include/css/style.css b/html/.include/css/style.css
new file mode 100644 (file)
index 0000000..78c586e
--- /dev/null
@@ -0,0 +1,176 @@
+    html {
+      color-scheme: light dark;
+    }
+
+    body {
+      background-color: transparent;
+      width: 70%;
+      margin: 10px left;
+      font-family: Tahoma, Verdana, Arial, sans-serif;
+      font-size: 10pt;
+      line-height: 1.6;
+    }
+
+    h1 {
+      border-bottom: 1px solid #6c5d53;
+      line-height: 1.6;
+    }
+
+    h2 {
+      color: #ccc;
+    }
+
+    a {
+      text-decoration: none;
+    }
+
+    a:hover {
+      text-decoration: underline;
+    }
+
+    blockquote {
+      border-left: 4px solid #ccc;
+      padding: 0 10px;
+      color: #555;
+    }
+
+    code {
+      margin: 0 2px;
+      padding: 2px 5px;
+      border: 1px solid #ccc;
+      background-color: transparent;
+      border-radius: 3px;
+    }
+
+    div.notification {
+      position: relative;
+      float: none;
+      width: 600px;
+      border: 3px outset #6c5d53;
+      /* border-radius: 5px; */
+      padding: 10px;
+      background-color: #e6e6e6;
+    }
+
+    div.content {
+      padding-left: 60px;
+    }
+
+    hr {
+      clear: both;
+    }
+
+    img.logo {
+      color-scheme: light dark;
+      background-color: #555;
+      float: left;
+      border-radius: 50%;
+    }
+
+    p.foot {
+      color: #777;
+      text-align: center;
+    }
+
+    p.heading {
+      font-size: 120%;
+      margin: 0px;
+      font-weight: bold;
+      text-decoration: underline;
+    }
+
+    p.hint {
+      display: none;
+    }
+
+    pre {
+      font-family: fira-mono, monospace;
+      white-space: pre-wrap;
+    }
+
+    pre.code {
+      background-color: #f8f8f8;
+      border: 1px solid #ccc;
+      overflow: auto;
+      padding: 6px 10px;
+      border-radius: 3px;
+    }
+
+    pre code {
+      margin: 0;
+      padding: 0;
+      border: 0;
+    }
+
+    pre.code::before {
+      content: "📋 Copy!";
+      float: right;
+      border: 1px solid #ccc;
+      border-radius: 3px;
+    }
+
+    span.link {
+      color: #863600;
+    }
+
+    td.head {
+      line-height: 1.2;
+      padding: 0 2em;
+    }
+
+    td.head .top {
+      font-size: 250%;
+      font-weight: bold;
+    }
+
+    td.head .bottom {
+      font-size: 125%;
+      color: #555;
+    }
+
+    div.root {
+      border-bottom: 1px solid #6c5d53;
+      color: #ccc;
+      margin: 0 left;
+      max-width: 50%;
+    }
+
+    div.menu {
+      border-bottom: 1px solid #6c5d53;
+      font-size: 100%;
+      color: #ccc;
+      margin: 0 left;
+      max-width: 50%;
+      line-height: 1;
+    }
+
+    /* #root {
+      color-scheme: light dark;
+      font-family: sans-serif;
+      font-size: 18px;
+      color: #ffffff;
+      margin: 0 auto;
+      max-width: 50%;
+    }
+
+    #menu {
+      color-scheme: light dark;
+      font-family: sans-serif;
+      font-size: 20px;
+      color: #ffffff;
+      margin: 0 auto;
+      max-width: 50%;
+    } */
+
+    @media only screen and (orientation: landscape) {
+      body {
+        margin-left: 10vw;
+        margin-right: 10vw;
+      }
+
+      div.notification {
+        float: right;
+        margin: 10px;
+      }
+
+    }
\ No newline at end of file
diff --git a/html/.include/img/browser-01.avif b/html/.include/img/browser-01.avif
new file mode 100644 (file)
index 0000000..3dc0a1f
Binary files /dev/null and b/html/.include/img/browser-01.avif differ
diff --git a/html/.include/img/browser-02.avif b/html/.include/img/browser-02.avif
new file mode 100644 (file)
index 0000000..1867fbe
Binary files /dev/null and b/html/.include/img/browser-02.avif differ
diff --git a/html/.include/img/browser-03.avif b/html/.include/img/browser-03.avif
new file mode 100644 (file)
index 0000000..dc24bbb
Binary files /dev/null and b/html/.include/img/browser-03.avif differ
diff --git a/html/.include/img/eworm-meadow.avif b/html/.include/img/eworm-meadow.avif
new file mode 100644 (file)
index 0000000..f592d59
Binary files /dev/null and b/html/.include/img/eworm-meadow.avif differ
diff --git a/html/.include/img/logo.avif b/html/.include/img/logo.avif
new file mode 100644 (file)
index 0000000..956fea8
Binary files /dev/null and b/html/.include/img/logo.avif differ
diff --git a/html/.include/img/logo.png b/html/.include/img/logo.png
new file mode 100644 (file)
index 0000000..7bec10e
Binary files /dev/null and b/html/.include/img/logo.png differ
diff --git a/html/.include/img/logo.svg b/html/.include/img/logo.svg
new file mode 100644 (file)
index 0000000..a30e04e
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg" width="96" height="96" version="1.1" viewBox="0 0 25.4 25.4" xmlns="http://www.w3.org/2000/svg">
+ <defs id="defs">
+  <radialGradient id="radGradDark" cx="7.5006" cy="9.4015" r="9.7854" gradientTransform="matrix(1.6107 1.0797 -.58681 .87543 .93614 -7.022)" gradientUnits="userSpaceOnUse">
+   <stop id="dark-1" stop-color="#222" offset="0"/>
+   <stop id="dark-2" stop-color="#444" offset="1"/>
+  </radialGradient>
+  <radialGradient id="radGradRed" cx="14.501" cy="10.029" r="2.6711" gradientTransform="matrix(1.3827 .62837 -.44627 .98203 -1.0744 -8.9965)" gradientUnits="userSpaceOnUse">
+   <stop id="red-1" stop-color="#a00" offset="0"/>
+   <stop id="red-2" stop-color="#c22" offset="1"/>
+  </radialGradient>
+ </defs>
+ <g id="layer1">
+  <rect id="background" x="4.5766e-15" width="25.4" height="25.4" ry="5.1528" fill="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="13.229"/>
+  <path id="hexagon" d="m17.758 12.437-1.3326 0.77071a0.94166 0.94328 0 0 1-0.94166 0l-1.3326-0.77071a0.94166 0.94328 0 0 1-0.47083-0.8169v-1.5414a0.94166 0.94328 0 0 1 0.47083-0.8169l1.3326-0.77071a0.94166 0.94328 0 0 1 0.94166 0l1.3326 0.77071a0.94166 0.94328 0 0 1 0.47083 0.8169v1.5414a0.94166 0.94328 0 0 1-0.47083 0.8169z" fill="url(#radGradRed)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width=".79375"/>
+  <g id="text" fill="url(#radGradDark)" stroke="#000" stroke-width=".1">
+   <g id="shebang" aria-label="#!">
+    <path id="path904" d="m13.767 4.8761v-1.6506h-0.88032l0.26724-2.0593h-2.0593l-0.26724 2.0593h-1.572l0.26724-2.0593h-2.0593l-0.26724 2.0593h-1.1318v1.6506h0.91175l-0.393 3.0182h-1.1004v1.6506h0.88031l-0.29868 2.2479h2.0593l0.29868-2.2479h1.572l-0.29868 2.2479h2.0593l0.29868-2.2479h1.1318v-1.6506h-0.91175l0.393-3.0182zm-3.5527 3.0182h-1.572l0.393-3.0182h1.572z"/>
+    <path id="path906" d="m17.209 0.89898h-2.6409l0.3144 6.8853s0.66885-0.28785 1.0123-0.28746c0.33937 3.865e-4 0.99985 0.28746 0.99985 0.28746z"/>
+   </g>
+   <g id="rsc" aria-label="rsc">
+    <path id="path910" d="m7.5809 12.875c-0.92632 0-1.716 0.66817-2.0804 1.7919l-0.2126-1.5641h-2.0804v8.0636h2.3993v-4.0546c0.27334-1.1997 0.68335-1.9134 1.6704-1.9134 0.25816 0 0.47075 0.04556 0.72891 0.1063l0.37964-2.3234c-0.27334-0.075928-0.50112-0.1063-0.80484-0.1063z"/>
+    <path id="path912" d="m11.954 12.845c-2.0349 0-3.2953 1.0782-3.2953 2.4601 0 1.2452 0.78965 2.0652 2.3841 2.5208 1.4578 0.41001 1.7008 0.57705 1.7008 1.1237 0 0.48594-0.44038 0.75928-1.1693 0.75928-0.78965 0-1.5337-0.3189-2.1412-0.78965l-1.1693 1.306c0.78965 0.71372 1.9741 1.1997 3.3712 1.1997 2.0045 0 3.5838-0.98706 3.5838-2.6575 0-1.4426-0.89595-2.1108-2.4904-2.5664-1.4426-0.4252-1.6552-0.60742-1.6552-1.0326 0-0.36445 0.3189-0.60742 0.97188-0.60742 0.69854 0 1.3667 0.22778 1.9893 0.62261l0.88076-1.3515c-0.7441-0.60742-1.7919-0.98706-2.9612-0.98706z"/>
+    <path id="path914" d="m19.896 12.845c-2.4145 0-3.9483 1.7919-3.9483 4.3583 0 2.5512 1.5186 4.2216 3.9938 4.2216 1.1085 0 1.9741-0.36446 2.7182-0.95669l-1.0478-1.4882c-0.57705 0.36445-0.97188 0.54668-1.5489 0.54668-0.95669 0-1.5945-0.54668-1.5945-2.3386 0-1.8071 0.59224-2.5056 1.6249-2.5056 0.54668 0 1.0174 0.18223 1.5337 0.57705l1.0326-1.4274c-0.77446-0.65298-1.64-0.98706-2.7638-0.98706z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/html/.include/img/qr-code.png b/html/.include/img/qr-code.png
new file mode 100644 (file)
index 0000000..fd5e877
Binary files /dev/null and b/html/.include/img/qr-code.png differ
diff --git a/html/.include/js/clipboard.js b/html/.include/js/clipboard.js
new file mode 100644 (file)
index 0000000..851fb1d
--- /dev/null
@@ -0,0 +1,7 @@
+function CopyToClipboard(element) {
+  element.style.filter = 'invert(1)';
+  navigator.clipboard.writeText(element.firstElementChild.textContent);
+  setTimeout(function() {
+    element.style.filter = 'invert(0)';
+  }, 100);
+}
diff --git a/html/.include/js/color.js b/html/.include/js/color.js
new file mode 100644 (file)
index 0000000..82cc204
--- /dev/null
@@ -0,0 +1,12 @@
+function invertHex(hex) {
+  return (Number("0x1" + hex) ^ 0xffffff).toString(16).substr(1);
+}
+
+function color() {
+  var svg = document.querySelector(".logo").getSVGDocument();
+  svg.getElementById("dark-1").setAttribute("stop-color", document.getElementById("color1").value);
+  svg.getElementById("dark-2").setAttribute("stop-color", document.getElementById("color2").value);
+  var background = document.getElementById("color3").value;
+  svg.getElementById("background").setAttribute("fill", background);
+  svg.getElementById("hexagon").setAttribute("stroke", "#" + invertHex(background.substring(1)));
+}
diff --git a/html/.include/js/notif.js b/html/.include/js/notif.js
new file mode 100644 (file)
index 0000000..91741fd
--- /dev/null
@@ -0,0 +1,6 @@
+function visible(cb, element) {
+  document.getElementById(element).style.display = cb.checked ? "block" : "none";
+}
+function update(cb, element) {
+  document.getElementById(element).innerHTML = cb.value;
+}
diff --git a/html/.tmpl/readme.md b/html/.tmpl/readme.md
new file mode 100644 (file)
index 0000000..1985f53
--- /dev/null
@@ -0,0 +1,101 @@
+## ngx_markdown_filter_module
+
+The `ngx_markdown_filter_module` module is a filter that transforms markdown files to html format.
+
+This module utilizes the [cmark](https://github.com/commonmark/cmark) library.
+
+### Example configuration
+
+```
+location ~ \.md {
+    markdown_filter on;
+    markdown_template html/template.html;
+}
+```
+
+This works on proxy locations as well.
+
+### Directives
+
+```
+Syntax:  markdown_filter on|off;
+Context: location
+```
+
+```
+Syntax:  markdown_template html/template.html;
+Context: location
+```
+
+```
+# enable `unsafe` mode for cmark
+Syntax:  markdown_unsafe on|off;
+Context: location;
+```
+
+```
+# enable `tagfilter` extension for cmark-gfm
+Syntax:  markdown_gfm_tagfilter on|off;
+Context: location;
+```
+
+```
+# enable `tasklist` extension for cmark-gfm
+Syntax:  markdown_gfm_tasklist on|off;
+Context: location;
+```
+
+```
+# enable `strikethrough` extension for cmark-gfm
+Syntax:  markdown_gfm_strikethrough on|off;
+Context: location;
+```
+
+```
+# enable `autolink` extension for cmark-gfm
+Syntax: markdown_gfm_autolink on|off;
+Context: location;
+```
+
+### Build
+
+1. Clone this repo
+
+2. Install `cmark` lib with development headers
+
+```
+dnf install cmark-devel
+```
+
+3. Download [nginx src archive](http://nginx.org/en/download.html) and unpack it
+
+4. Run `configure` script (see nginx src) and build nginx
+
+```
+> ./configure --add-module=/path/to/ngx_markdown_filter_module
+> make
+```
+
+5. Apply markdown directives to nginx conf and run it
+
+### Build with cmark-gfm (tables support)
+
+Original cmark library doesn't support tables. But there is [cmark-gfm](https://github.com/github/cmark-gfm)
+fork with table extension, supported by Github.
+
+1. Clone this repo
+
+2. Rename `config_gfm` to `config`
+
+3. Install `cmark-gfm` lib
+
+4. Download [nginx src archive](http://nginx.org/en/download.html) and unpack it
+
+5. Run `configure` script (see nginx src) and build nginx
+
+```
+> ./configure --add-module=/path/to/ngx_markdown_filter_module --with-cc-opt=-DWITH_CMARK_GFM=1
+> make
+```
+
+6. Apply markdown directives to nginx conf and run it
diff --git a/html/AM-Backup-UpdateEmail.rsc b/html/AM-Backup-UpdateEmail.rsc
new file mode 100644 (file)
index 0000000..211cf9e
--- /dev/null
@@ -0,0 +1,568 @@
+#!rsc by Vados\r
+# RouterOS script: AM-Backup-UpdateEmail\r
+# Script comment: Update and Backup send notification to EMail\r
+#\r
+#\r
+# requires RouterOS, version>=6.43.7\r
+# requires device-mode, fetch\r
+#\r
+# Updated: 15/04/2025\r
+# Website: https://github.com/beeyev\r
+# Notification e-mail (Make sure you have configured Email settings in Tools -> Email)\r
+:local ExitOK false;\r
+:onerror Err {\r
+  :global GlobalConfReady; :global GlobalFuncReady;\r
+  :retry { :if ($GlobalConfReady != true || $GlobalFuncReady != true) \\r
+      do={ :error ("Global config and/or functions not ready."); }; } delay=500ms max=50;\r
+  :local ScriptName [ :jobname ];\r
+\r
+:global LogPrint;\r
+:global OSUpdateMode;\r
+\r
+# Add scheduler\r
+:if ([ :len [ /system/scheduler/find where name=$ScriptName ] ] = 0) do={\r
+  $LogPrint warning $ScriptName ("SystemScheduler NOT SET!");\r
+  /system/scheduler/add name=$ScriptName on-event="/system/script { run $ScriptName; }" comment="Scheduler for $ScriptName" interval="7d 00:00:00" policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-time="03:41:27"; \r
+  :set ExitOK true;\r
+  :error false;\r
+}\r
+# Add global Function OSUpdateMode\r
+:if ([ :len $OSUpdateMode ] = 0 || ![$OSUpdateMode]) do={\r
+  :set OSUpdateMode do={\r
+    :local NewMode [ :tostr $1 ];\r
+    :global BackupAndUpdateMode;\r
+    :global LogPrint;\r
+    :global OldMode [ :tostr $BackupAndUpdateMode ];\r
+    :if ([ $NewMode ] != false && $NewMode != $OldMode && $NewMode = "updateOnce") do={\r
+      :set BackupAndUpdateMode $NewMode;\r
+      $LogPrint warning $0 ("/nOk. /$OsUpdateMode = " . $NewMode . ".\nYou're a good boy! ))\nI'm launching the update in 5 seconds.");\r
+      :delay 5;\r
+      /system/script { run AM-Backup-UpdateEmail; };\r
+    } else={\r
+    $LogPrint warning $0 ("/nFunction /$OsUpdateMode is " . $NewMode . " but is may be only \"updateOnce\" value!");\r
+    :return false;\r
+    }\r
+    :error false;\r
+}\r
+\r
+  :if ([ :len [ /system/scheduler/find where name="running-from-backup-partition" ] ] > 0) do={\r
+    $LogPrint warning $ScriptName ("Running from backup partition, refusing to act.");\r
+    :set ExitOK true;\r
+    :error false;\r
+  }\r
+\r
+:global EmailGeneralTo;\r
+:global BackupAndUpdateMode;\r
+:global forceBackup;\r
+:global BackupPassword;\r
+:global BackupSens;\r
+:global updateChannel;\r
+\r
+:global DevInfoIncludeIP;\r
+\r
+# Installs patch updates only (BackupAndUpdateMode = "osupdate").\r
+# Works for `stable` and `long-term` channels.\r
+# Updates only if MAJOR.MINOR match (e.g. 6.43.2 > 6.43.6 allowed, 6.44.1 skipped).\r
+# Sends info if a newer (non-patch) version is found.\r
+:local installOnlyPatchUpdates false\r
+:local scriptVersion "26.02.12"\r
+\r
+#Script messages prefix\r
+:local SMP "BkpAndUpdate:";\r
+\r
+:local exitErrorMessage "$SMP script stopped due to an error. Please check logs for more details.";\r
+:log info "\n\n$SMP Script \"Mikrotik RouterOS automatic backup & update\" v.$scriptVersion started.";\r
+:log info "$SMP Script Mode: `$BackupAndUpdateMode`, Update channel: `$updateChannel`, Force backup: `$forceBackup`, Install only patch updates: `$installOnlyPatchUpdates`";\r
+\r
+## vv FUNCTIONS vv ##\r
+:global WaitCheckUpdates; $WaitCheckUpdates;\r
+:global MiniDateTimeStamp;\r
+:local MDTSFix [$MiniDateTimeStamp];\r
+:global Identity;\r
+:global IdentityShort;\r
+:global devBoardName;\r
+:global runningVersion;\r
+:global runningChannel;\r
+:global devModel;\r
+:global devRbSerialNumber;\r
+:global ROsVerAvail;\r
+:global PkgUpdStatus;\r
+\r
+# Checks if two RouterOS version strings differ only by the patch version\r
+# :put [$FuncIsPatchUpdateOnly "6.2.1" "6.2.4"]  # Output: true\r
+# :put [$FuncIsPatchUpdateOnly "6.2.1" "6.3.1"]  # Output: false\r
+:local FuncIsPatchUpdateOnly do={\r
+  :local ver1 $1;\r
+  :local ver2 $2;\r
+  # Extract the major and minor components from a version\r
+  :local extractMajorMinor do={\r
+    :local ver $1;\r
+    :local dot1 [:find $ver "."];\r
+    :if ($dot1 = -1) do={:return $ver}\r
+    :local major [:pick $ver 0 $dot1];\r
+    :local rest [:pick $ver ($dot1 + 1) [:len $ver]];\r
+    :local dot2 [:find $rest "."];\r
+    :local minor $rest;\r
+    :if ($dot2 >= 0) do={:set minor [:pick $rest 0 $dot2]}\r
+    :return ($major . "." . $minor);\r
+  }\r
+\r
+# Compare the major and minor components of both version strings\r
+:if ([$extractMajorMinor $ver1] = [$extractMajorMinor $ver2]) do={:return true}\r
+ :return false;\r
+}\r
+# Creates backups and returns array of names\r
+# Possible arguments:\r
+#  $1 - file name, without extension\r
+#  $2 - password (optional)\r
+#  $3 - sensitive data in config (optional, default: false)\r
+# Example:\r
+#:put [$FuncCreateBackups $backupName]\r
+:local FuncCreateBackups do={\r
+  :local backupName [ :tostr $1 ];\r
+  :local BackPass $2;\r
+  :local BackSens $3;\r
+\r
+  #Script messages prefix\r
+  :local SMP "BkpAndUpdate:";\r
+  :local exitErrorMessage "$SMP script stopped due to an error. Please check logs for more details.";\r
+  :log info ("$SMP global function `FuncCreateBackups` started, input: `$backupName`");\r
+\r
+  # validate required parameter: backupName\r
+  :if ([:typeof $backupName] != "str" or [:len $backupName] = 0) do={\r
+    :log error "$SMP parameter 'backupName' is required and must be a non-empty string";\r
+#    :log warning "$SMP parameter '\$backupName' is required and must be a non-empty string.\nI set '\$backupName' to 'default-backup'";\r
+#    :set $backupName "default-backup";\r
+    :error $exitErrorMessage;\r
+  }\r
+\r
+  :local backupFileSys "$backupName.backup";\r
+  :local backupFileConfig "$backupName.rsc";\r
+  :local backupNames {$backupFileSys;$backupFileConfig};\r
+\r
+  ## Perform system backup\r
+  :if ([:len $BackPass] = 0) do={\r
+    :log info ("$SMP starting backup without password, backup name: `$backupName`");\r
+    /system backup save dont-encrypt=yes name=$backupName;\r
+  } else={\r
+    :log info ("$SMP starting backup with password, backup name: `$backupName`");\r
+    /system backup save password=$BackPass name=$backupName;\r
+  }\r
+  :log info ("$SMP system backup created: `$backupFileSys`");\r
+    ## Export config file\r
+  :if ($BackSens = true) do={\r
+    :log info ("$SMP starting export config with sensitive data, backup name: `$backupName`");\r
+    # Since RouterOS v7 it needs to be explicitly set that we want to export sensitive data\r
+    :if ([:pick [/system resource get version] 0 1] < 7) do={\r
+      :execute "/export compact terse file=$backupName";\r
+    } else={\r
+      :execute "/export compact show-sensitive terse file=$backupName";\r
+    }\r
+  } else={\r
+    :log info ("$SMP starting export config without sensitive data, backup name: `$backupName`");\r
+    /export compact hide-sensitive terse file=$backupName;\r
+  }\r
+  :log info ("$SMP Config export complete: `$backupFileConfig`");\r
+  :log info ("$SMP Waiting a little to ensure backup files are written");\r
+  :delay 20;\r
+  :if ([:len [/file find name=$backupFileSys]] > 0) do={\r
+    :log info ("$SMP system backup file successfully saved to the file system: `$backupFileSys`");\r
+  } else={\r
+    :log error ("$SMP system backup was not created, file does not exist: `$backupFileSys`");\r
+    :error $exitErrorMessage;\r
+  }\r
+  :if ([:len [/file find name=$backupFileConfig]] > 0) do={\r
+    :log info ("$SMP config backup file successfully saved to the file system: `$backupFileConfig`");\r
+  } else={\r
+    :log error ("$SMP config backup was not created, file does not exist: `$backupFileConfig`");\r
+    :error $exitErrorMessage;\r
+  }\r
+  :log info ("$SMP global function `FuncCreateBackups` finished. Created backups, system: `$backupFileSys`, config: `$backupFileConfig`")\r
+  :return $backupNames;\r
+}\r
+# Sends an email\r
+# Parameters:\r
+#  $1 - to (email address)\r
+#  $2 - subject\r
+#  $3 - body\r
+#  $4 - file attachments (optional; pass "" if not needed)\r
+#\r
+# Example:\r
+# $FuncSendEmailSafe "admin@domain.com" "Backup Done" "Backup complete." "backup1.backup"\r
+:local FuncSendEmailSafe do={\r
+  :global EmailGeneralTo;\r
+  :local emailSubject $2;\r
+  :local emailBody $3;\r
+  :local emailAttachments $4;\r
+  :local SMP "Bkp&Upd:";\r
+  :local exitErrorMessage "$SMP script stopped due to an error. Please check logs for more details.";\r
+  :log info "$SMP Attempting to send email to `$EmailGeneralTo`";\r
+  # SAFETY: wait for any previously queued email to finish\r
+  :local waitTimeoutPre 60;\r
+  :local waitCounterPre 0;\r
+  :while (([/tool e-mail get last-status] = "resolving-dns" or [/tool e-mail get last-status] = "in-progress")) do={\r
+    :if ($waitCounterPre >= $waitTimeoutPre) do={\r
+      :log error "$SMP Email send aborted: previous send did not complete after $waitTimeoutPre seconds";\r
+      :error $exitErrorMessage;\r
+    }\r
+    :log info "$SMP Waiting for previous email to finish (status: $[/tool e-mail get last-status])...";\r
+    :delay 1; \r
+    :set waitCounterPre ($waitCounterPre + 1);\r
+  }\r
+  # Send the email\r
+  :do {\r
+    /tool e-mail send to=$EmailGeneralTo subject=$emailSubject body=$emailBody file=$emailAttachments;\r
+  } on-error={\r
+    :log error "$SMP Email send command failed to execute. Check logs and verify email settings.";\r
+    :error $exitErrorMessage;\r
+  }\r
+  # Wait for send status to change from "in-progress" / "resolving-dns"\r
+  :local waitTimeout 60;\r
+  :local waitCounter 0;\r
+  :local emailStatus "";\r
+  :log info "$SMP Waiting for email to be sent, timeout in `$waitTimeout` seconds...";\r
+  :while ($waitCounter < $waitTimeout) do={\r
+    :set emailStatus [/tool e-mail get last-status];\r
+    :if ($emailStatus != "in-progress" and $emailStatus != "resolving-dns") do={\r
+      :log info "$SMP Email send status received: $emailStatus";\r
+      # exit loop\r
+      :set waitCounter $waitTimeout;\r
+    } else={:delay 1; :set waitCounter ($waitCounter + 1)}\r
+  }\r
+  # Final decision based on last status\r
+  :if ($emailStatus = "succeeded") do={\r
+    :log info  "$SMP Email successfully sent to `$EmailGeneralTo`";\r
+  } else={\r
+    :log error "$SMP Email failed to send. Status: `$emailStatus`. Check logs for more details and verify email settings.";\r
+    :error $exitErrorMessage;\r
+  }\r
+}\r
+# Global variable to track current update step\r
+# They need to be initialized here first to be available in the script\r
+:global buGlobalVarTargetOsVersion;\r
+:global buGlobalVarScriptStep;\r
+:local scriptStep $buGlobalVarScriptStep;\r
+:do {\r
+  /system/script/environment remove buGlobalVarScriptStep;\r
+} on-error={}\r
+:if ([:len $scriptStep] = 0) do={\r
+  :set scriptStep 1;\r
+}\r
+## ^^ FUNCTIONS ^^ ##\r
+#\r
+# Initial validation\r
+## Check email settings\r
+:if ([:len $EmailGeneralTo] < 3) do={\r
+  :log error ("$SMP Parameter `\$EmailGeneralTo` is not set, or contains invalid value. Script stopped.");\r
+  :error $exitErrorMessage;\r
+}\r
+# Values will be defined later in the script\r
+:local emailServer "";\r
+:local emailFromAddress [/tool e-mail get from];\r
+:log info "$SMP Validating email settings...";\r
+:do {\r
+  :set emailServer [/tool e-mail get server];\r
+} on-error={\r
+  # This is a workaround for the RouterOS v7.12 and older versions\r
+  :set emailServer [/tool e-mail get address];\r
+}\r
+:if ($emailServer = "0.0.0.0") do={\r
+  :log error ("$SMP Email server address is not correct: `$emailServer`, check `Tools -> Email`. Script stopped.");\r
+  :error $exitErrorMessage;\r
+}\r
+:if ([:len $emailFromAddress] < 3) do={\r
+  :log error ("$SMP Email configuration FROM address is not correct: `$emailFromAddress`, check `Tools -> Email`. Script stopped.");\r
+  :error $exitErrorMessage;\r
+}\r
+# Script mode validation\r
+:if ($BackupAndUpdateMode != "backup" and $BackupAndUpdateMode != "osupdate" and $BackupAndUpdateMode != "updateOnce" and $BackupAndUpdateMode != "osnotify") do={\r
+  :log error ("$SMP Script parameter `\$BackupAndUpdateMode` is not set, or contains invalid value: `$BackupAndUpdateMode`. Script stopped.");\r
+  :error $exitErrorMessage;\r
+}\r
+# Update channel validation\r
+:if ($updateChannel != "stable" and $updateChannel != "long-term" and $updateChannel != "testing" and $updateChannel != "development") do={\r
+  :log error ("$SMP Script parameter `\$updateChannel` is not set, or contains invalid value: `$updateChannel`. Script stopped.");\r
+  :error $exitErrorMessage;\r
+}\r
+# Verify if script is set to install patch updates and if the update channel is valid\r
+:if (($BackupAndUpdateMode = "osupdate" or $BackupAndUpdateMode = "updateOnce") and $installOnlyPatchUpdates = true) do={\r
+  :if ($updateChannel != "stable" and $updateChannel != "long-term") do={\r
+    :log error ("$SMP Patch-only updates enabled, but update channel `$updateChannel` is invalid. Only `stable` and `long-term` are supported. Script stopped");\r
+    :error $exitErrorMessage;\r
+  }\r
+  \r
+  :if ($runningChannel != "stable" and $runningChannel != "long-term") do={\r
+    :log error ("$SMP Script is set to install only patch updates, but the installed RouterOS version is not from `stable` or `long-term` channel: `$runningChannel`. Script stopped");\r
+    :error $exitErrorMessage;\r
+  }\r
+}\r
+#\r
+\r
+:local rawTime [/system clock get time];\r
+:local rawDate [/system clock get date];\r
+\r
+:local deviceOsVerAndChannelRunning [/system/resource/get version];\r
+\r
+:local backupNameTemplate     ("backup_v" . $runningVersion . "_" . $runningChannel . "_" . $MDTSFix);\r
+:local backupNameBeforeUpdate ($backupNameTemplate . "_before_update");\r
+:local backupNameAfterUpdate  ($backupNameTemplate . "_after_update");\r
+\r
+## Email body template\r
+:local mailSubjectPrefix  "$SMP Device - `$IdentityShort`";\r
+:local mailBodyCopyright  "Mikrotik RouterOS automatic backup & update (ver. $scriptVersion) \nhttps://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update";\r
+:local changelogUrl     "Check RouterOS changelog: https://mikrotik.com/download/changelogs/";\r
+:local mailBodyDeviceInfo  "";\r
+:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "Device information:");\r
+:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\n---------------------");\r
+:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nName: $Identity");\r
+:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nModel: $devModel");\r
+:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nBoard: $devBoardName");\r
+:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nSerial number: $devRbSerialNumber");\r
+:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nRouterOS version: v$deviceOsVerAndChannelRunning");\r
+:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nBuild time: $[/system/resource/get build-time]");\r
+:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nRouterboard FW: $ROsVerAvail");\r
+:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nDevice date-time: $rawDate $rawTime ($[/system/clock/get time-zone-name ])");\r
+:set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nUptime: $[/system/resource/get uptime]");\r
+\r
+:local mailAttachments  [:toarray ""];\r
+:if ($scriptStep = 1 or $scriptStep = 3) do={\r
+  :if ($scriptStep = 3) do={\r
+    :log info ("$SMP Waiting for one minute before continuing to the final step.");\r
+    :delay 1m;\r
+  }\r
+## IP address detection\r
+  :global DevInfoIncludeIP;\r
+  :if ([$DevInfoIncludeIP] = true) do={\r
+    :global GetPubIp;\r
+    :local PublicIpAddress [{$GetPubIp}];\r
+    :set mailBodyDeviceInfo ($mailBodyDeviceInfo . "\nPublic IP address: " . $PublicIpAddress . ".");\r
+    :log info "$SMP Public IP address detected: " . $PublicIpAddress;\r
+  }  \r
+}\r
+\r
+## STEP 1: Create backups, check for new RouterOS, and send email\r
+## Steps 2â3 run only if auto-update is enabled and a new version is available\r
+:if ($scriptStep = 1) do={\r
+  :global BackupPassword;\r
+  :global BackupSens;\r
+  :local isNewOsUpdateAvailable false;\r
+  :local isLatestOsAlreadyInstalled true;\r
+  :local isOsNeedsToBeUpdated false;\r
+  :local isUpdateCheckSucceeded false;\r
+  :local isEmailNeedsToBeSent false;\r
+  :local mailSubjectPartAction "";\r
+  :local mailPtBodyAction "";\r
+  :local mailPtSubjectBackup "";\r
+  :local mailPtBodyBackup "";\r
+  # Checking for new version\r
+  :if ($BackupAndUpdateMode = "osupdate" or $BackupAndUpdateMode = "osnotify" or $BackupAndUpdateMode = "updateOnce") do={\r
+    :log info ("$SMP Setting update channel to `$updateChannel`");\r
+     /system/package/update/set channel=$updateChannel;\r
+    :log info ("$SMP Checking for new RouterOS version. Current installed version is: `$runningVersion`");\r
+      $LogPrint debug $ScriptName ("Checking for updates...");\r
+      \r
+#     /system/package/update/check-for-updates without-paging as-value;\r
+#     /system/package/update check-for-updates;\r
+    :delay 5s;\r
+    :set PkgUpdStatus [ /system/package/update/get status ];\r
+    :if ($PkgUpdStatus = "New version is available") do={\r
+    :set ROsVerAvail [/system/package/update/get latest-version];\r
+    }\r
+\r
+    :if ($PkgUpdStatus = "New version is available") do={\r
+      :log info ("$SMP New RouterOS version is available: `$ROsVerAvail`");\r
+      :set isNewOsUpdateAvailable true;\r
+      :set isLatestOsAlreadyInstalled false;\r
+      :set isUpdateCheckSucceeded true;\r
+      :set isEmailNeedsToBeSent true;\r
+      :set mailSubjectPartAction "New RouterOS available";\r
+      :set mailPtBodyAction  "New RouterOS version is available, current version: v$runningVersion, new version: v$ROsVerAvail. \n$changelogUrl";\r
+    } else={\r
+      :if ($PkgUpdStatus = "System is already up to date") do={\r
+        :log info ("$SMP No new RouterOS version is available, the latest version is already installed: `v$runningVersion`");\r
+        :set isUpdateCheckSucceeded true;\r
+        :set mailSubjectPartAction "No os update available";\r
+        :set mailPtBodyAction  "No new RouterOS version is available, the latest version is already installed: `v$runningVersion`";\r
+      } else={\r
+        :log error ("$SMP Failed to check for new RouterOS version. Package check status: `$PkgUpdStatus`");\r
+        :set isEmailNeedsToBeSent true;\r
+        :set mailSubjectPartAction "Error unable to check new os version";\r
+        :set mailPtBodyAction  "An error occurred while checking for a new RouterOS version.\nStatus returned: `$PkgUpdStatus`\n\nPlease review the logs on the device for more details and verify internet connectivity.";\r
+      }\r
+    }\r
+  }\r
+  # Checking if the script needs to install new os version\r
+  :if (($BackupAndUpdateMode = "osupdate" or $BackupAndUpdateMode = "updateOnce") and $isNewOsUpdateAvailable = true) do={\r
+    :if ($installOnlyPatchUpdates = true) do={\r
+      :if ([$FuncIsPatchUpdateOnly $runningVersion $ROsVerAvail] = true) do={\r
+        :log info "$SMP New RouterOS version is available, and it is a patch update. Current version: v$runningVersion, new version: v$ROsVerAvail";\r
+        :set isOsNeedsToBeUpdated true;\r
+      } else={\r
+        :log info "$SMP The script will not install this update, because it is not a patch update. Current version: v$runningVersion, new version: v$ROsVerAvail";\r
+        :set mailPtBodyAction ($mailPtBodyAction . "\nThis update will not be installed, because the script is set to install only patch updates.");\r
+      }\r
+    } else={\r
+      :set isOsNeedsToBeUpdated true;\r
+      }\r
+  }\r
+  # Checking If the script needs to create a backup\r
+  :if ($forceBackup = true or $BackupAndUpdateMode = "backup" or $isOsNeedsToBeUpdated = true) do={\r
+    :log info ("$SMP Starting backup process.");\r
+    :set isEmailNeedsToBeSent true;\r
+    :local backupName $backupNameTemplate;\r
+    # This means it's the first step where we create a backup before the update process\r
+    :if ($isOsNeedsToBeUpdated = true) do={\r
+      :set backupName $backupNameBeforeUpdate;\r
+      #Email body if the purpose of the script is to update the device\r
+      :set mailSubjectPartAction "Update preparation";\r
+      :set mailPtBodyAction ($mailPtBodyAction . "\nThe update process for device '$Identity' is scheduled to upgrade RouterOS from version v.$runningVersion to version v.$ROsVerAvail (Update channel: $updateChannel)");\r
+      :set mailPtBodyAction ($mailPtBodyAction . "\nPlease note: The update will proceed only after a successful backup.");\r
+      :set mailPtBodyAction ($mailPtBodyAction . "\nA final report with detailed information will be sent once the update process is completed.");\r
+      :set mailPtBodyAction ($mailPtBodyAction . "\nIf you do not receive a second email within the next 10 minutes, there may be an issue. Please check your device logs for further information.");\r
+    }\r
+    :do {\r
+      :set mailAttachments [$FuncCreateBackups $backupName $BackupPassword $BackupSens];\r
+      :set mailPtSubjectBackup "Backup created";\r
+      :set mailPtBodyBackup "System backups have been successfully created and attached to this email.";\r
+    } on-error={\r
+      :set isOsNeedsToBeUpdated false;\r
+      :set mailPtSubjectBackup "Backup failed";\r
+      :set mailPtBodyBackup "The script failed to create backups. Please check device logs for more details.";\r
+      :log warning "$SMP Backup creation failed. Update process will be canceled if automatic update is enabled";\r
+    }\r
+  }\r
+  :if ($isEmailNeedsToBeSent = true) do={\r
+    :log info "$SMP Preparing to send email...";\r
+    :local mailStep1Subject $mailSubjectPrefix;\r
+    :local mailStep1Body  "";\r
+    # subject\r
+    :if ($mailSubjectPartAction != "")  do={:set mailStep1Subject ($mailStep1Subject . " - " . $mailSubjectPartAction)}\r
+    :if ($mailPtSubjectBackup != "")  do={:set mailStep1Subject ($mailStep1Subject . " - " . $mailPtSubjectBackup)}\r
+    # body\r
+    :if ($mailPtBodyAction != "") do={:set mailStep1Body ($mailStep1Body . $mailPtBodyAction . "\n\n")}\r
+    :if ($mailPtBodyBackup != "") do={:set mailStep1Body ($mailStep1Body . $mailPtBodyBackup . "\n\n")}\r
+    :set mailStep1Body ($mailStep1Body . $mailBodyDeviceInfo . "\n\n" . $mailBodyCopyright);\r
+    # Send email with backups\r
+    :do {$FuncSendEmailSafe $EmailGeneralTo $mailStep1Subject $mailStep1Body $mailAttachments} on-error={\r
+      :set isOsNeedsToBeUpdated false;\r
+      :log error "$SMP The script will not proceed with the update process, because the email was not sent.";\r
+    }\r
+  }\r
+  :if ([:len $mailAttachments] > 0) do={\r
+    :log info "$SMP Cleaning up backup files from the file system...";\r
+    /file remove $mailAttachments;\r
+    :delay 2s;\r
+  }\r
+  :if ($isOsNeedsToBeUpdated = true) do={\r
+    :global OldMode;\r
+    :log info "$SMP everything is ready to install new RouterOS, going to start the update process and reboot the device.";\r
+    :do {\r
+      :local nextStep 2;\r
+      :if ($isCloudHostedRouter = true) do={\r
+        :log info "$SMP The device is a cloud hosted router, the second step updating the Routerboard firmware will be skipped.";\r
+        :set nextStep 3;\r
+      }\r
+      :local scheduledCommand (":delay 5s; /system/scheduler remove BKPUPD-NEXT-BOOT-TASK; \\r
+       :global buGlobalVarScriptStep $nextStep; :global buGlobalVarTargetOsVersion \"$ROsVerAvail\"; \\r
+      :delay 10s; /system/script { run $ScriptName; }");\r
+      /system/scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=$scheduledCommand start-time=startup interval=0;\r
+      /system/package/update install;\r
+     } on-error={\r
+      # Failed to install new os version, remove the task and variables\r
+      :do {\r
+        /system/scheduler remove BKPUPD-NEXT-BOOT-TASK;\r
+        :set BackupAndUpdateMode $OldMode;\r
+        :delay 2;\r
+        :do {\r
+#          /system/script/environment remove [find name="buGlobalVarTargetOsVersion"];\r
+          :global buGlobalVarTargetOsVersion (a);\r
+        } on-error={\r
+          :set $mailSubjectPrefix ($mailSubjectPrefix . "ERROR Remove global variable \$buGlobalVarTargetOsVersion failed!");\r
+        }\r
+#        :if ([ :len $OldMode ] > 0) do={\r
+#          :do {\r
+#            #/system/script/environment remove [find name="OldMode"];\r
+#           :global OldMode (a);\r
+#          } on-error={\r
+#            :set $mailSubjectPrefix ($mailSubjectPrefix . " ERROR Remove global variable \$OldMode failed!");\r
+#          }\r
+#        }\r
+      } on-error={\r
+        :log error "$SMP Failed to install new RouterOS version. Please check device logs for more details. \\r
+        \nAnd Failed to remove task and variables! Check it!";\r
+        :set $mailSubjectPrefix ($mailSubjectPrefix . " ERROR Remove task and global variables!");\r
+      }\r
+      :log error "$SMP Failed to install new RouterOS version. Please check device logs for more details.";\r
+      :local mailUpdateErrorSubject ($mailSubjectPrefix . " - Update failed");\r
+      :local mailUpdateErrorBody "The script was unable to install new RouterOS version. Please check device logs for more details.";\r
+      # Send email with error\r
+      $FuncSendEmailSafe $EmailGeneralTo $mailUpdateErrorSubject $mailUpdateErrorBody "";\r
+      :error $exitErrorMessage;\r
+    }\r
+  }\r
+}\r
+## STEP 2: (Post-reboot) Upgrade RouterBOARD firmware\r
+## Runs only if auto-update is enabled and a new RouterOS version was found\r
+:if ($scriptStep = 2) do={\r
+  :log info "$SMP The script is in the second step, updating Routerboard firmware.";\r
+  :log info "$SMP Upgrading routerboard firmware from v.$deviceRbCurrentFw to v.$deviceRbUpgradeFw";\r
+  /system routerboard upgrade;\r
+  :delay 2;\r
+  :log info "$SMP routerboard upgrade process was completed, going to reboot in a moment!";\r
+  ## Set task to send final report on the next boot\r
+  /system scheduler add name=BKPUPD-NEXT-BOOT-TASK on-event=":delay 2; :global buGlobalVarScriptStep 3; \\r
+   :global buGlobalVarTargetOsVersion \"$buGlobalVarTargetOsVersion\"; :delay 10s; /system/script { run $ScriptName; }; \ \r
+   :delay 5s; /system/scheduler remove BKPUPD-NEXT-BOOT-TASK;" start-time=startup interval=0;\r
+   :delay 2;\r
+  /system reboot;\r
+}\r
+\r
+## STEP 3: Final report (after second reboot, with delay).\r
+## Runs only if auto-update is enabled and a new RouterOS version was found.\r
+:if ($scriptStep = 3) do={\r
+  :log info ("$SMP The script is in the third step, sending final report.");\r
+  :local targetOsVersion $buGlobalVarTargetOsVersion;\r
+  :do {\r
+#    /system/script/environment remove [find name="buGlobalVarTargetOsVersion"];\r
+    :global buGlobalVarTargetOsVersion (a);\r
+  } on-error={\r
+    :set $mailSubjectPrefix ($mailSubjectPrefix . " ERROR Remove global variable \$buGlobalVarTargetOsVersion failed!");\r
+  }\r
+\r
+  :if ([ :len $OldMode ] != 0) do={\r
+    :do {\r
+#      /system/script/environment remove [find name="OldMode"];\r
+      :global OldMode (a);\r
+      } on-error={\r
+        :set $mailSubjectPrefix ($mailSubjectPrefix . " ERROR! Remove global variable \$OldMode failed!");\r
+       }\r
+  }\r
+  :if ([:len $targetOsVersion] = 0) do={\r
+    :log warning "$SMP Something is wrong, the script was unable to get the target updated OS version from the global variable.";\r
+  }\r
+  :local mailStep3Subject $mailSubjectPrefix;\r
+  :local mailStep3Body  "";\r
+  :if ($targetOsVersion = $runningVersion) do={\r
+    :log info "$SMP Successfully verified new RouterOS version: target: `$targetOsVersion`, current: `$runningVersion`";\r
+    :set mailStep3Subject ($mailStep3Subject . " - Update completed - Backup created");\r
+    :set mailStep3Body ($mailStep3Body . "RouterOS and routerboard upgrade process was completed");\r
+    :set mailStep3Body ($mailStep3Body . "\nNew RouterOS version: v.$targetOsVersion, routerboard firmware: v.$deviceRbCurrentFw");\r
+    :set mailStep3Body ($mailStep3Body . "\n$changelogUrl\nBackups of the upgraded system are in the attachment of this email.\n\n$mailBodyDeviceInfo\n\n$mailBodyCopyright");\r
+    :set mailAttachments [$FuncCreateBackups $backupNameAfterUpdate $BackupPassword $BackupSens];\r
+  } else={\r
+    :log error "$SMP Failed to verify new RouterOS version: target: `$targetOsVersion`, current: `$runningVersion`";\r
+    :set mailStep3Subject ($mailStep3Subject . " - Update failed");\r
+    :set mailStep3Body ($mailStep3Body . "The script was unable to verify that the new RouterOS version was installed, target version: `$targetOsVersion`, current version: `$runningVersion`\nCheck device logs for more details.\n\n$mailBodyDeviceInfo\n\n$mailBodyCopyright");\r
+  }\r
+  $FuncSendEmailSafe $EmailGeneralTo $mailStep3Subject $mailStep3Body $mailAttachments;\r
+  :if ([:len $mailAttachments] > 0) do={\r
+    :log info "$SMP Cleaning up backup files from the file system...";\r
+    /file remove $mailAttachments;\r
+    :delay 2;\r
+  }\r
+  :log info "$SMP Final report email sent successfully, and the script has finished.";\r
+}\r
+:log info "$SMP the script has finished, script step: `$scriptStep` \n\n";\r
+} do={\r
+  :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;\r
+}\r
diff --git a/html/AM-CapsManRoll-Upgrade.rsc b/html/AM-CapsManRoll-Upgrade.rsc
new file mode 100644 (file)
index 0000000..8273697
--- /dev/null
@@ -0,0 +1,40 @@
+#!rsc by Vados
+# RouterOS script: AM-CapsManRoll-Upgrade
+# Script comment: Upgrade CAPs one after another
+#
+#
+# provides: capsman-rolling-upgrade.capsman
+# requires RouterOS, version=7.19
+#
+# !! Do not edit this file, it is generated from template!
+:local ExitOK false;
+:onerror Err {
+  :global GlobalConfReady; :global GlobalFuncReady;
+  :retry { :if ($GlobalConfReady != true || $GlobalFuncReady != true) \
+      do={ :error ("Global config and/or functions not ready."); }; } delay=500ms max=50;
+  :local ScriptName [ :jobname ];
+
+  :global LogPrint;
+  :global ScriptLock;
+  :if ([ $ScriptLock $ScriptName ] = false) do={
+    :set ExitOK true;
+    :error false;
+  }
+  :local InstalledVersion [ /system/package/update/get installed-version ];
+  :local RemoteCapCount [ :len [ /caps-man/remote-cap/find ] ];
+  :if ($RemoteCapCount > 0) do={
+    :local Delay (600 / $RemoteCapCount);
+    :if ($Delay > 120) do={:set Delay 120}
+    :foreach RemoteCap in=[ /caps-man/remote-cap/find where version!=$InstalledVersion ] do={
+      :local RemoteCapVal [ /caps-man/remote-cap/get $RemoteCap ];
+      :if ([ :len $RemoteCapVal ] > 1) do={
+        $LogPrint info $ScriptName ("Starting upgrade for " . $RemoteCapVal->"name" . \
+          " (" . $RemoteCapVal->"identity" . ")...");
+        /caps-man/remote-cap/upgrade $RemoteCap;
+      } else={$LogPrint warning $ScriptName ("Remote CAP vanished, skipping upgrade.")}
+      :delay ($Delay . "s");
+    }
+  }
+} do={
+  :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+}
diff --git a/html/AM-CapsUpgrade.rsc b/html/AM-CapsUpgrade.rsc
new file mode 100644 (file)
index 0000000..b697630
--- /dev/null
@@ -0,0 +1,42 @@
+#!rsc by Vados
+# RouterOS script: AM-CapsUpgrade
+# Script comment: 
+#
+#
+# requires RouterOS, version=7.19
+# requires device-mode, fetch, scheduler
+:local ExitOK false;
+:onerror Err {
+  :global GlobalConfReady; :global GlobalFuncReady;
+  :retry { :if ($GlobalConfReady != true || $GlobalFuncReady != true) \
+      do={ :error ("Global config and/or functions not ready."); }; } delay=500ms max=50;
+  :local ScriptName [ :jobname ];
+
+  # Helper function to log and put messages
+  :global LogPrint;
+  :local logput do={:log info $1; :put $1}
+  :local installed [/system/package get routeros version];
+  # Initiate Upgrade on outdated cAPs (old CAPs Manager)
+  :if ([/system/package/find where name="wireless" disabled=no]) do={
+    :put message="Old wireless driver detected";
+      [:parse "/caps-man/remote-cap
+        :local outdatedcaps [find where version!=$installed];
+        :foreach i in=\$outdatedcaps do={
+          \$logput (\"[INFO] Initiate Upgrade on \" . [get value-name=identity \$i]);
+          upgrade numbers=\$i;
+          :delay 120s;
+          }
+       "]
+    }
+  # Initiate Upgrade on outdated cAPs (new CAPs Manager)
+  /interface/wifi/capsman/remote-cap/;
+  :local outdatedWifiCaps [find where version!=$installed];
+  :foreach i in=$outdatedWifiCaps do={
+    $LogPrint info $ScriptName ("[INFO] Initiate Upgrade on " . [get value-name=identity $i]);
+    upgrade numbers=$i;
+    :delay 120s
+    }
+ }
+} do={
+  :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+}
\ No newline at end of file
diff --git a/html/AM-CheckUpdates.rsc b/html/AM-CheckUpdates.rsc
new file mode 100644 (file)
index 0000000..d137fe8
--- /dev/null
@@ -0,0 +1,226 @@
+#!rsc by Vados
+# RouterOS script: AM-CheckUpdates
+# Script comment: Check for RouterOS update, send notification and/or install
+# Copyright (c) 2005-2026 Vados <vados@vados.ru>
+# 
+#
+# requires RouterOS, version=7.19
+# requires device-mode, fetch, scheduler
+#
+:local ExitOK false;
+:onerror Err {
+  :global GlobalConfReady; :global GlobalFuncReady;
+  :retry { :if ($GlobalConfReady != true || $GlobalFuncReady != true) \
+      do={ :error ("Global configs or functions not ready."); }; } delay=500ms max=50;
+  :local ScriptName [ :jobname ];
+
+  :global Identity;
+  :global SafeUpdateAll;
+  :global SafeUpdateNeighbor;
+  :global SafeUpdateNeighborIdentity;
+  :global SafeUpdatePatch;
+  :global SafeUpdateUrl;
+  :global SentRouterosUpdateNotification;
+
+  :global DeviceInfo;
+  :global EscapeForRegEx;
+  :global FetchUserAgentStr;
+  :global LogPrint;
+  :global RebootForUpdate;
+  :global ScriptFromTerminal;
+  :global ScriptLock;
+  :global SendNotification2;
+  :global SymbolForNotification;
+  :global VersionToNum;
+
+  # Add scheduler
+:if ([ :len [ /system/scheduler/find where name=$ScriptName ] ] = 0) do={
+  $LogPrint warning $ScriptName ("SystemScheduler NOT SET!");
+  /system/scheduler/add name=$ScriptName on-event="/system/script { run $ScriptName; }" comment="Scheduler for $ScriptName" interval="7d 00:00:00" policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-time="03:41:27"; 
+  :set ExitOK true;
+  :error false;
+}
+  
+  :local DoUpdate do={
+    :local ScriptName [ :tostr $1 ];
+
+    :if ([ :len [ /system/script/find where name="packages-update" ] ] > 0) do={
+      /system/script/run packages-update;
+    } else={
+      /system/package/update/install without-paging;
+    }
+  }
+
+  :if ([ $ScriptLock $ScriptName ] = false) do={
+    :set ExitOK true;
+    :error false;
+  }
+
+  :if ([ :len [ /system/scheduler/find where name="running-from-backup-partition" ] ] > 0) do={
+    $LogPrint warning $ScriptName ("Running from backup partition, refusing to act.");
+    :set ExitOK true;
+    :error false;
+  }
+
+  :if ([ :len [ /system/scheduler/find where name="_RebootForUpdate" ] ] > 0) do={
+    :if ([ :typeof $RebootForUpdate ] = "nothing") do={
+      $LogPrint info $ScriptName ("Found a stale scheduler for reboot, removing.");
+      /system/scheduler/remove "_RebootForUpdate";
+    } else={
+      $LogPrint info $ScriptName ("A reboot for update is already scheduled.");
+      :set ExitOK true;
+      :error false;
+    }
+  }
+
+  $LogPrint debug $ScriptName ("Checking for updates...");
+  /system/package/update/check-for-updates without-paging as-value;
+  :local Update [ /system/package/update/get ];
+
+  :if (($Update->"installed-version") = ($Update->"latest-version")) do={
+    :if ([ $ScriptFromTerminal $ScriptName ] = true) do={
+      $LogPrint info $ScriptName ("System is already up to date.");
+    }
+    :set ExitOK true;
+    :error true;
+  }
+
+  :if ([ :len ($Update->"latest-version") ] = 0) do={
+    $LogPrint info $ScriptName ("Received an empty version string from server.");
+    :set ExitOK true;
+    :error false;
+  }
+
+  :local NumInstalled [ $VersionToNum ($Update->"installed-version") ];
+  :local NumLatest [ $VersionToNum ($Update->"latest-version") ];
+  :local BitMask [ $VersionToNum "255.255zero0" ];
+  :local NumInstalledFeature ($NumInstalled & $BitMask);
+  :local NumLatestFeature ($NumLatest & $BitMask);
+  :local Link ("https://mikrotik.com/download/changelogs/" . $Update->"channel" . "-release-tree");
+
+  :if ($NumLatest < [ $VersionToNum "7.0" ]) do={
+    $LogPrint warning $ScriptName ("The version '" . ($Update->"latest-version") . "' is not a valid version.");
+    :set ExitOK true;
+    :error false;
+  }
+
+  :if ($NumInstalled < $NumLatest) do={
+    :if ($SafeUpdateAll ~ "^YES,? ?PLEASE!?\$") do={
+      $LogPrint info $ScriptName ("Installing ALL versions automatically, including " . \
+        $Update->"latest-version" . "...");
+      $SendNotification2 ({ origin=$ScriptName; \
+        subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \
+        message=("Installing ALL versions automatically, including " . $Update->"latest-version" . \
+          "... Updating on " . $Identity . "..."); link=$Link; silent=true });
+      $DoUpdate $ScriptName;
+      :set ExitOK true;
+      :error true;
+    }
+
+    :if ($SafeUpdatePatch = true && $NumInstalledFeature = $NumLatestFeature) do={
+      $LogPrint info $ScriptName ("Version " . $Update->"latest-version" . " is a patch release, updating...");
+      $SendNotification2 ({ origin=$ScriptName; \
+        subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \
+        message=("Version " . $Update->"latest-version" . " is a patch update for " . $Update->"channel" . \
+          ", updating on " . $Identity . "..."); link=$Link; silent=true });
+      $DoUpdate $ScriptName;
+      :set ExitOK true;
+      :error true;
+    }
+
+    :if ($SafeUpdateNeighbor = true) do={
+      :local Neighbors [ /ip/neighbor/find where platform="MikroTik" identity~$SafeUpdateNeighborIdentity \
+         version~("^" . [ $EscapeForRegEx ($Update->"latest-version") ] . "\\b") ];
+      :if ([ :len $Neighbors ] > 0) do={
+        :local Neighbor [ /ip/neighbor/get ($Neighbors->0) identity ];
+        $LogPrint info $ScriptName ("Seen a neighbor (" . $Neighbor . ") running version " . \
+          $Update->"latest-version" . " from " . $Update->"channel" . ", updating...");
+        $SendNotification2 ({ origin=$ScriptName; \
+          subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \
+          message=("Seen a neighbor (" . $Neighbor . ") running version " . $Update->"latest-version" . \
+            " from " . $Update->"channel" . ", updating on " . $Identity . "..."); link=$Link; silent=true });
+        $DoUpdate $ScriptName;
+        :set ExitOK true;
+        :error true;
+      }
+    }
+
+    :if ([ :len $SafeUpdateUrl ] > 0) do={
+      :local Result;
+      :onerror Err {
+        :set Result [ /tool/fetch check-certificate=yes-without-crl \
+            ($SafeUpdateUrl . $Update->"channel" . "?installed=" . $Update->"installed-version" . \
+            "&latest=" . $Update->"latest-version") http-header-field=({ [ $FetchUserAgentStr $ScriptName ] }) \
+            output=user as-value ];
+      } do={
+        $LogPrint warning $ScriptName ("Failed receiving safe version for " . $Update->"channel" . ": " . $Err);
+      }
+      :if ($Result->"status" = "finished" && $Result->"data" = $Update->"latest-version") do={
+        $LogPrint info $ScriptName ("Version " . $Update->"latest-version" . " is considered safe, updating...");
+        $SendNotification2 ({ origin=$ScriptName; \
+          subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \
+          message=("Version " . $Update->"latest-version" . " is considered safe for " . $Update->"channel" . \
+            ", updating on " . $Identity . "..."); link=$Link; silent=true });
+        $DoUpdate $ScriptName;
+        :set ExitOK true;
+        :error true;
+      }
+    }
+
+    :if ([ $ScriptFromTerminal $ScriptName ] = true) do={
+      :if (($Update->"channel") = "testing" && $NumInstalledFeature < $NumLatestFeature) do={
+        :put ("This is a feature update in testing channel. Switch to channel 'stable'? [y/N]");
+        :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={
+          /system/package/update/set channel=stable;
+          $LogPrint info $ScriptName ("Switched to channel 'stable', please re-run!");
+          :set ExitOK true;
+          :error true;
+        }
+      }
+
+      :put ("Do you want to install RouterOS version " . $Update->"latest-version" . "? [y/N]");
+      :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={
+        $DoUpdate $ScriptName;
+        :set ExitOK true;
+        :error true;
+      } else={
+        :put "Canceled...";
+      }
+    }
+
+    :if ($SentRouterosUpdateNotification = $Update->"latest-version") do={
+      $LogPrint info $ScriptName ("Already sent the RouterOS update notification for version " . \
+          $Update->"latest-version" . ".");
+      :set ExitOK true;
+      :error true;
+    }
+
+    $SendNotification2 ({ origin=$ScriptName; \
+      subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \
+      message=("A new RouterOS version " . ($Update->"latest-version") . \
+        " is available for " . $Identity . ".\n\n" . \
+        [ $DeviceInfo ]); link=$Link; silent=true });
+    :set SentRouterosUpdateNotification ($Update->"latest-version");
+  }
+
+  :if ($NumInstalled > $NumLatest) do={
+    :if ($SentRouterosUpdateNotification = $Update->"latest-version") do={
+      $LogPrint info $ScriptName ("Already sent the RouterOS downgrade notification for version " . \
+          $Update->"latest-version" . ".");
+      :set ExitOK true;
+      :error true;
+    }
+
+    $SendNotification2 ({ origin=$ScriptName; \
+      subject=([ $SymbolForNotification "warning-sign" ] . "RouterOS version: " . $Update->"latest-version"); \
+      message=("A different RouterOS version " . ($Update->"latest-version") . \
+        " is available for " . $Identity . ", but it is a downgrade.\n\n" . \
+        [ $DeviceInfo ]); link=$Link; silent=true });
+    $LogPrint info $ScriptName ("A different RouterOS version " . ($Update->"latest-version") . \
+      " is available for downgrade.");
+    :set SentRouterosUpdateNotification ($Update->"latest-version");
+  }
+} do={
+  :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+}
diff --git a/html/AM-DnsNetwatch.rsc b/html/AM-DnsNetwatch.rsc
new file mode 100644 (file)
index 0000000..4ba1bd7
--- /dev/null
@@ -0,0 +1,165 @@
+#!rsc by Vados
+# RouterOS script: AM-DnsNetwatch
+# Script comment: Monitor and manage dns/doh with netwatch
+# Copyright (c) 2005-2026 Vados <vados@vados.ru>
+#
+#
+# requires RouterOS, version=7.19
+# requires device-mode, fetch
+# Scheduler:
+# /system/scheduler/add interval=1m name=DnsWetwatch comment="Scheduler for Amster-DnsNetwatch" on-event="/system/script/run Amster-DnsNetwatch;" start-time=startup;
+:local ExitOK false;
+:onerror Err {
+  :global GlobalConfReady; :global GlobalFuncReady;
+  :retry { :if ($GlobalConfReady != true || $GlobalFuncReady != true) \
+      do={ :error ("Global configs or functions not ready."); }; } delay=500ms max=50;
+  :local ScriptName [ :jobname ];
+
+  :global CertificateAvailable;
+  :global CharacterReplace;
+  :global EitherOr;
+  :global IsDNSResolving;
+  :global LogPrint;
+  :global ParseKeyValueStore;
+  :global ScriptLock;
+
+# Add scheduler
+  :if ([ :len [ /system/scheduler/find where name=$ScriptName ] ] = 0) do={
+    $LogPrint warning $ScriptName ("SystemScheduler NOT SET!");
+    /system/scheduler/add name=$ScriptName on-event="/system/script { run $ScriptName; }" comment="Scheduler for $ScriptName" interval=2m policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-time=startup; 
+  }
+
+#  /tool/netwatch/add comment="doh-fallback, doh-url=https://security.cloudflare-dns.com/dns-query, doh-cert=DigiCert Global Root CA, notify, name=CloudFlare-doh IPv4 1" host=1.1.1.1;
+#  /tool/netwatch/add comment="doh, doh-url=https://security.cloudflare-dns.com/dns-query, doh-cert=DigiCert Global Root CA, notify, name=CloudFlare-doh IPv4 2" host=1.0.0.1;
+#  /tool/netwatch/add comment="dns-fallback, notify, name=Google-DNS IPv4" host=8.8.4.4;
+#  /tool/netwatch/add comment="doh-fallback, doh-url=https://dns.google/dns-query, doh-cert=DigiCert Global Root G2, notify, name=Google-DOH IPv4" host=8.8.8.8;
+#  /tool/netwatch/add comment="dns-fallback, notify, name=Cuad9-DNS IPv4" host=149.112.112.112;
+#  /tool/netwatch/add comment="doh-fallback, doh-url=https://dns.quad9.net/dns-query, doh-cert=DigiCert Global Root G3, notify, name=Cuad9-DOH IPv4" host=9.9.9.9;
+#
+#  /tool/netwatch/add comment="dns, notify, name=ns1.unet.ws" host=91.192.189.2;
+#  /tool/netwatch/add comment="dns-fallback, notify, name=Google-dns IPv4 1" host=8.8.8.8;
+#  /tool/netwatch/add comment="dns-fallback, notify, name=Google-dns IPv4 2" host=8.8.4.4;
+#  /tool/netwatch/add comment="dns-fallback, notify, name=Cuad9-dns IPv4 1" host=9.9.9.9;
+#  /tool/netwatch/add comment="dns-fallback, notify, name=Cuad9-dns IPv4 2" host=149.112.112.112;
+#
+#  /tool/netwatch/add comment="doh, doh-cert=DigiCert Global Root CA" host=1.1.1.1;
+#  /tool/netwatch/add comment="doh, doh-cert=DigiCert Global Root CA" host=1.0.0.1;
+#  /tool/netwatch/add comment="doh, doh-cert=DigiCert Global Root G2" host=8.8.8.8;
+#  /tool/netwatch/add comment="doh, doh-cert=DigiCert Global Root G2" host=8.8.4.4;
+#  /tool/netwatch/add comment="doh, doh-cert=DigiCert Global Root G3" host=9.9.9.9;
+#  /tool/netwatch/add comment="doh, doh-cert=DigiCert Global Root G3" host=149.112.112.112;
+
+  :if ([ $ScriptLock $ScriptName ] = false) do={
+    $LogPrint warning $ScriptName ("ScriptLock is False!");
+    :set ExitOK true;
+    :error false;
+  }
+
+  :local SettleTime (5m30s - [ /system/resource/get uptime ]);
+  :if ($SettleTime > 0s) do={
+    $LogPrint info $ScriptName ("System just booted, giving netwatch " . $SettleTime . " to settle.");
+    :set ExitOK true;
+    :error true;
+  }
+
+  :local DnsServers ({});
+  :local DnsFallback ({});
+  :local DnsCurrent [ /ip/dns/get servers ];
+
+  :foreach Host in=[ /tool/netwatch/find where comment~"\\bdns\\b" status="up" ] do={
+    :local HostVal [ /tool/netwatch/get $Host ];
+    :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ];
+
+    :if ($HostInfo->"disabled" != true) do={
+      :if ($HostInfo->"dns" = true) do={
+        :set DnsServers ($DnsServers, $HostVal->"host");
+      }
+      :if ($HostInfo->"dns-fallback" = true) do={
+        :set DnsFallback ($DnsFallback, $HostVal->"host");
+      }
+    }
+  }
+
+  :if ([ :len $DnsServers ] > 0) do={
+    :if ($DnsServers != $DnsCurrent) do={
+      $LogPrint info $ScriptName ("Updating DNS servers: " . [ :tostr $DnsServers ]);
+      /ip/dns/set servers=$DnsServers;
+      /ip/dns/cache/flush;
+    }
+  } else={
+    :if ([ :len $DnsFallback ] > 0) do={
+      :if ($DnsFallback != $DnsCurrent) do={
+        $LogPrint info $ScriptName ("Updating DNS servers to fallback: " . [ :tostr $DnsFallback ]);
+        /ip/dns/set servers=$DnsFallback;
+        /ip/dns/cache/flush;
+      }
+    }
+  }
+
+  :local DohCurrent [ /ip/dns/get use-doh-server ];
+  :local DohServers ({});
+  :foreach Host in=[ /tool/netwatch/find where comment~"\\bdoh\\b" status="up" ] do={
+    :local HostVal [ /tool/netwatch/get $Host ];
+    :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ];
+    :local HostName [ /ip/dns/static/find where name address=($HostVal->"host") \
+        (type="A" or type="AAAA") !disabled !dynamic ];
+    :if ([ :len $HostName ] > 0) do={
+      :set HostName [ /ip/dns/static/get ($HostName->0) name ];
+    }
+
+    :if ($HostInfo->"doh" = true && $HostInfo->"disabled" != true) do={
+      :if ([ :len ($HostInfo->"doh-url") ] = 0) do={
+        :set ($HostInfo->"doh-url") ("https://" . [ $EitherOr $HostName ($HostVal->"host") ] . "/dns-query");
+      }
+      :if ($DohCurrent = $HostInfo->"doh-url" && [ $IsDNSResolving ] = true) do={
+        $LogPrint debug $ScriptName ("Current DoH server is still up and resolving: " . $DohCurrent);
+        :set ExitOK true;
+        :error true;
+      }
+      :set ($DohServers->[ :len $DohServers ]) $HostInfo;
+    }
+  }
+  :if ([ :len $DohCurrent ] > 0) do={
+    $LogPrint info $ScriptName ("Current DoH server is down or not resolving, disabling: " . $DohCurrent);
+    /ip/dns/set use-doh-server="";
+    /ip/dns/cache/flush;
+  }
+  :foreach DohServer in=$DohServers do={
+    :foreach DohCert in=[ :toarray [ $CharacterReplace ($DohServer->"doh-cert") ":" "," ] ] do={
+      :if ([ :len $DohCert ] > 0) do={
+        :if ([ $CertificateAvailable $DohCert "fetch" ] = false || \
+             [ $CertificateAvailable $DohCert "dns" ] = false) do={
+          $LogPrint warning $ScriptName ("Downloading certificate '" . $DohCert . "' failed, trying without.");
+        }
+      }
+    }
+    :local Data false;
+    :onerror Err {
+      :retry {
+        :set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \
+          http-header-field=({ "accept: application/dns-message" }) \
+          url=(($DohServer->"doh-url") . "?dns=" . [ :convert to=base64 ([ :rndstr length=2 ] . \
+          "\01\00" . "\00\01" . "\00\00" . "\00\00" . "\00\00" . "\09doh-check\05eworm\02de\00" . \
+          "\00\10" . "\00\01") ]) as-value ]->"data");
+      } delay=1s max=3;
+    } do={$LogPrint warning $ScriptName ("Request to DoH server " . ($DohServer->"doh-url") . " failed: " . $Err)}
+
+    :if ($Data != false) do={
+      :if ([ :typeof [ :find $Data "doh-check-OK" ] ] = "num") do={
+        /ip/dns/set use-doh-server=($DohServer->"doh-url") verify-doh-cert=yes;
+        :if ([ /certificate/settings/get crl-use ] = true) do={
+          $LogPrint warning $ScriptName ("Configured to use CRL, that can cause severe issue!");
+        }
+        /ip/dns/cache/flush;
+        $LogPrint info $ScriptName ("Setting DoH server: " . ($DohServer->"doh-url"));
+        :set ExitOK true;
+        :error true;
+      } else={
+        $LogPrint warning $ScriptName ("Received unexpected response from DoH server: " . \
+          ($DohServer->"doh-url"));
+      }
+    }
+  }
+} do={
+  :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+}
diff --git a/html/AM-DownloadPackages.capsman.rsc b/html/AM-DownloadPackages.capsman.rsc
new file mode 100644 (file)
index 0000000..ac67c45
--- /dev/null
@@ -0,0 +1,77 @@
+#!rsc by Vados
+# RouterOS script: AM-DownloadPackages.capsman
+# Script comment: Download and cleanup packages for CAP installation from CAPsMAN
+#
+#
+#
+# requires RouterOS, version=7.19
+# !! Do not edit this file, it is generated from template!
+:local ExitOK false;
+:onerror Err {
+  :global GlobalConfigReady; :global GlobalFunctionsReady;
+  :retry { :if ($GlobalConfigReady != true || $GlobalFunctionsReady != true) \
+      do={ :error ("Global config and/or functions not ready."); }; } delay=500ms max=50;
+  :local ScriptName [ :jobname ];
+
+  :global CleanFilePath;
+  :global DownloadPackage;
+  :global FileGet;
+  :global LogPrint;
+  :global MkDir;
+  :global RmFile;
+  :global ScriptLock;
+
+  :if ([ $ScriptLock $ScriptName ] = false) do={
+    :set ExitOK true;
+    :error false;
+  }
+
+  :local PackagePath [ $CleanFilePath [ /caps-man/manager/get package-path ] ];
+  :local InstalledVersion [ /system/package/update/get installed-version ];
+  :local Updated false;
+  :if ([ :len $PackagePath ] = 0) do={
+    $LogPrint warning $ScriptName ("The CAPsMAN package path is not defined, can not download packages.");
+    :set ExitOK true;
+    :error false;
+  }
+  :if ([ $FileGet $PackagePath ] = false) do={
+    :if ([ $MkDir $PackagePath ] = false) do={
+      $LogPrint warning $ScriptName ("Creating directory at CAPsMAN package path (" . \
+        $PackagePath . ") failed!");
+      :set ExitOK true;
+      :error false;
+    }
+    $LogPrint info $ScriptName ("Created directory at CAPsMAN package path (" . $PackagePath . \
+      "). Please place your packages!")}
+  :foreach Package in=[ /file/find where type="package" \
+        package-version!=$InstalledVersion name~("^" . $PackagePath) ] do={
+    :local File [ /file/get $Package ];
+    :if ($File->"package-architecture" = "mips") do={
+      :set ($File->"package-architecture") "mipsbe";
+    }
+    :if ([ $DownloadPackage ($File->"package-name") $InstalledVersion \
+         ($File->"package-architecture") $PackagePath ] = true) do={
+      :set Updated true;
+      $RmFile ($File->"name");
+    }
+  }
+
+  :if ([ :len [ /file/find where type="package" name~("^" . $PackagePath) ] ] = 0) do={
+    $LogPrint info $ScriptName ("No packages available, downloading default set.");
+    :foreach Arch in={ "arm"; "mipsbe" } do={
+      :foreach Package in={ "routeros"; "wireless" } do={
+        :if ([ $DownloadPackage $Package $InstalledVersion $Arch $PackagePath ] = true) do={
+          :set Updated true;
+        }
+      }
+    }
+  }
+  :if ($Updated = true) do={
+    :local Scripts [ /system/script/find where source~"\n# provides: capsman-rolling-upgrade.capsman\r?\n" ];
+    :if ([ :len $Scripts ] > 0) do={
+      :foreach Script in=$Scripts do={/system/script/run $Script}
+    } else={/caps-man/remote-cap/upgrade [ find where version!=$InstalledVersion ]}
+  }
+} do={
+  :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+}
diff --git a/html/AM-GlobalConfig.rsc b/html/AM-GlobalConfig.rsc
new file mode 100644 (file)
index 0000000..049364e
--- /dev/null
@@ -0,0 +1,152 @@
+#!rsc by Vados\r
+# RouterOS script: AM-GlobalConfig\r
+# Script comment: Global Config Script\r
+# Copyright (c) 2007-2026 Vados <vados@vados.ru>\r
+#\r
+#\r
+# Warning: Dont touch this line!\r
+:global GlobalConfReady false;\r
+#   \/    start edit\r
+#\r
+# Global Url For Amster scripts to fetch\r
+# most be defined in Amster-Init-Script\r
+:global GlobalScriptsUrl "https://ros.vados.ru/";\r
+:global ScriptsUrlSuffix "";\r
+:global CheckSumsVerify true;\r
+\r
+:global ScriptUpdatesCRLF true;\r
+:global CommentsInScripts true;\r
+\r
+# Debug output:\r
+:global PrintDebug true; #false;\r
+\r
+# Debug output for specific script:\r
+:set ($PrintDebugOverride->"AM-GlobalFunc") true;\r
+\r
+# Debug logs\r
+# Other actions (disk, email, remote or support) can be used as well.\r
+# I do not recommend using echo - use debug output instead (https://rsc.eworm.de/main/DEBUG.html#debug-output).\r
+#/system/logging/add topics=script,debug action=memory;\r
+\r
+:global MikrotikUpgradeUrl "https://upgrade.mikrotik.com/routeros";\r
+:global Domain "WORKGROUP";\r
+\r
+:global EmailGeneralTo "vados@vados.ru"; #:global EmailGeneralCc "";\r
+\r
+# $ScriptInstallUpdate mod/notification-telegram\r
+:global TelegramTokenId "8390868324:AAG3_d1hfVR2M6pGAgDK6ZvhOe4J97nyopw";\r
+:global TelegramChatId "-1003631309063"; #"454403445";\r
+# Use this to send notifications to a specific topic in group.\r
+#:global TelegramThreadId "30";\r
+# Using telegram-chat you have to define trusted chat ids (not group ids!)\r
+# or user names. Groups allow to chat with devices simultaneously.\r
+:global TelegramChatIdsTrusted {\r
+  "454403445"\r
+  "-1003631309063"\r
+  "-5124482807"\r
+  "1087968824";\r
+};\r
+#  "-5294757340";\r
+\r
+:global TelegramChatGroups "(all)";\r
+#:global TelegramChatGroups "(all|home|office)";\r
+\r
+:global NotificationFunctions {\r
+  "email"\r
+  "telegram";\r
+};\r
+# Toggle this to disable symbols in notifications.\r
+:global NotificationsWithSymbols true;\r
+:global TerminalColorOutput true;\r
+\r
+:global DetectIpAddrDefault "https://ipv4.mikrotik.ovh/";\r
+:global DetectIpAddrFallback "https://api.ipify.org/";\r
+\r
+## Update channel. Possible values: stable, long-term, testing, development\r
+:global updateChannel "stable";\r
+\r
+# Script BackupAndUpdate mode, possible values: backup,\r
+# updateOnce - Update once as "osnotify" and back "osnotify" mode (in terminal run :set OSUpdateMode updateOnce; $OSUpdateMode;).  \r
+# osupdate - Update if available and creates backups before/after update (ignores `forceBackup`)\r
+#               Set `forceBackup` to true to always create backups, even without updates\r
+# osnotify - Set `forceBackup` to always create backups on every run\r
+:global DevInfoIncludeIP true;\r
+:global BackupAndUpdateMode "osnotify"\r
+\r
+:global forceBackup true;\r
+:global BackupPassword "Peskar55";\r
+\r
+# SFTP Backup Variables\r
+:global BackupRandomDelay 0;\r
+:global BackupUploadUrl "sftp://amster.vados.ru/upload/";\r
+:global BackupUploadUser $Identity;\r
+:global BackupUploadPass "root.mnuc.backuper";\r
+\r
+:global BackupSendBinary true;\r
+:global BackupSendExport true;\r
+:global BackupSendGlobalConfig true;\r
+:global BackupSendScripts true;\r
+\r
+# Remove local file after uploading if no errors\r
+:global BackupRmLocal true;\r
+\r
+# Local Backups Root Directory \r
+:global BackupLocalRoot "tmpfs";\r
+\r
+# Encrypt Backup\r
+:global BackupEncrypt true;\r
+\r
+# Sensitive information in Backups\r
+:global BackupSens true;\r
+\r
+# User Export\r
+:global BackupUser true;\r
+\r
+# License Export (not for CHR, will silently skip)\r
+:global BackupLicense false;\r
+\r
+# SSH Keys\r
+:global BackupSshKeys true;\r
+\r
+# Certificate Export\r
+:global BackupCerts true;\r
+\r
+# Certificate Password\r
+:global BackupCertsPasswd "";\r
+\r
+# User-Manager Export\r
+:global BackupUserMan false;\r
+\r
+# The Dude Export\r
+:global BackupDude false;\r
+\r
+# User Files to export, comma-separated string or array of strings\r
+# User Files are not removed on backup\r
+# Any directory paths will be removed (/ -> _) on remote file\r
+# Nonpresent files are silently skipped\r
+:global BackupUserList "autosupout.rif,autosupout.old.rif";\r
+\r
+# load functions and any custom scripts\r
+#                    [ /system/script/find where name="global-config-overlay" ], \\r
+#                    [ /system/script/find where name~"^global-config-overlay\\.d/." ]\r
+#\r
+#:foreach Script in=([ /system/script/find where name="Amster-TGglobalVars" ], [ /system/script/find where name="Amster-TGglobalFunc" ]) do={\r
+#  :onerror Err {\r
+#     /system/script/run $Script;\r
+#     } do={\r
+#       :log error ("Loading configuration from configs and functions" . [ /system/script/get $Script name ] . " failed: " . $Err);\r
+#  }\r
+#}\r
+#:log info "\ndevMode = $devMode";\r
+#:log info "\ndevRunning = $devRunning";\r
+:foreach Script in=[ /system/script/find where source~"^#!rsc by Vados GlobalRUN\r?\n" ] do={\r
+  :onerror Err {\r
+    /system/script/run $Script;\r
+       } do={\r
+       :log error ("Run script " . [ /system/script/get $Script name ] . " width marker \" GlobalRUN\" failed: " . $Err);\r
+  }\r
+}\r
+\r
+# signal we are ready\r
+:set GlobalConfReady true;\r
+:while ($GlobalConfReady != true) do={:delay 500ms};\r
diff --git a/html/AM-GlobalFunc.rsc b/html/AM-GlobalFunc.rsc
new file mode 100644 (file)
index 0000000..9db0d68
--- /dev/null
@@ -0,0 +1,1561 @@
+#!rsc by Vados
+# RouterOS script: AM-GglobalFunc
+# Script comment: Global functions
+#
+#
+# requires RouterOS, version=7.21
+# requires device-mode, fetch, scheduler
+:local ExitOK false;
+:local ScriptName [ :jobname ];
+#:global checkRunJob $SriptName;
+#:global fixCRLF;
+#:if ($fixCRLF !=  false) do={
+#:foreach fixScript in=([ /system/script/find where name~"*" ]) do={/system/script/set source=[ :tocrlf [ get $fixScript source ] ] $fixScript}
+#:if ($fixCRLF !=  "LF") do={ /system/script/set source=[ :tocrlf [ get $fixScript source ] ] $fixScript};};
+#}
+:global GlobalFuncReady false;
+
+# The variables are received right here.
+:global GlobalConfVersion 138;
+
+:global Identity [/system/identity/get name];
+:global IdentityShort [:pick $Identity 0 18];
+
+:global devBoardName [/system/resource/get board-name];
+:global devMode [/system/device-mode/get mode];
+:global devCurArch [/system/resource/get architecture-name];
+:global devCurVersion [/system/resource/get version];
+
+# Variables defined in AM-GlobalConfig.rsc
+:global TerminalColorOutput;
+:global CommentsInScripts;
+:global DetectIpAddrDefault;
+:global DetectIpAddrFallback;
+:global updateChannel;
+:global Domain;
+:global EmailGeneralTo;
+:global BackupAndUpdateMode;
+
+# Single functions, without nested functions.
+:global GlobalEnvRemove;
+:global MiniDateTimeStamp;
+
+# Include globals $PrintDebug and $PrintDebugOverride
+:global LogPrint;
+
+# DeviceInfo and Updates
+:global IsCheckUpdates;
+:global WaitCheckUpdates;
+:global GetPubIp;
+
+:global GetArrDeviceInfo;
+:global WaitArrDeviceInfo;
+
+:global devModel;
+:global devRunningMode;
+
+:global devRbRevision;
+:global devRbSerialNumber;
+:global devRbVerAvail;
+
+:global runningChannelVersion;
+:global runningChannel;
+:global runningVersion;
+
+:global devBuildTime;
+#:global ROsVerAvail;
+:global PkgUpdStatus;
+:global currentUpdateChannel;
+
+:global ScriptInstallUpdate;
+
+:global DeviceInfo;
+
+:global OldMode;
+
+:global ParseKeyValueStore;
+:global RandomDelay;
+:global GetRandomNumber;
+:global EscapeForRegEx;
+:global ExitError;
+:global CleanFilePath;
+:global CleanName;
+:global CharacterReplace;
+:global CharacterMultiply;
+:global EitherOr;
+:global FormatLine;
+:global GetRandom20CharAlNum;
+:global Grep;
+:global IfThenElse;
+:global MAX;
+:global MIN;
+:global HumanReadableNum;
+
+:global IsFullyConnected;
+:global IsDNSResolving;
+:global IsTimeSync;
+:global IsTimeSyncCached;
+:global IsTimeSyncResetNtp;
+
+:global WaitDNSResolving;
+:global WaitFullyConnected;
+:global WaitTimeSync;
+:global MkDir;
+:global RmDir;
+:global RmFile;
+:global FileGet;
+:global Unix2Dos;
+:global UrlEncode;
+:global WaitForFile;
+:global ScriptLock;
+:global SendTelegram;
+:global NotificationFunctions;
+:global SendNotification;
+:global SendNotification2;
+:global SymbolForNotification;
+:global SymbolByUnicodeName;
+:global ValidateSyntax;
+:global CheckSumsVerify;
+:global GlobalConfChanges;
+:global GlobalConfMigration;
+:global VersionToNum;
+:global RequiredRouterOS;
+:global FetchLargeFiles;
+:global FetchUserAgentStr;
+:global CertificateAvailable;
+:global CertificateDownload;
+:global CertificateNameByCN;
+:global CharacterMultiply;
+
+:global InspectVar;
+:global InspectVarRet;
+:global ParseCustom;
+
+:set IsCheckUpdates do={
+  :local isInfo [:tobool $1];
+  :global LogPrint;
+  :global updateChannel;
+  :global PkgUpdStatus;
+  $LogPrint debug $0 ("\$isInfo = " . $isInfo); 
+  :local CurrentChannel [ /system/package/update/get channel ];
+  :if ($CurrentChannel != $updateChannel) do={
+    :retry command={/system/package/update set channel=$updateChannel}  delay=300ms max=5 on-error={};
+    $LogPrint debug $0 ("Set updates channel to: $updateChannel");
+  }
+   
+  :local Update [ /system/package/update/get ];
+  :local Channel ($Update->"channel");
+  $LogPrint debug $0 ("Update Channel: " . $Channel . ".");
+   
+  :local StatusPkg "undefined";
+  $LogPrint debug $0 ("Checking for updates...");
+  :retry command={:set $StatusPkg ([/system/package/update/check-for-updates without-paging as-value]->"status")} \
+   delay=300ms max=5 on-error={};
+  $LogPrint debug $0 ("\$StatusPkg = " . $StatusPkg);
+  :if ($StatusPkg = "New version is available") do={ :set PkgUpdStatus true; } else={ :set PkgUpdStatus false; }
+  $LogPrint debug $0 ("\$PkgUpdStatus = " . $PkgUpdStatus);
+  :if ($isInfo != false || $PkgUpdStatus = true) do={
+    $LogPrint debug $0 ("\$PkgUpdStatus = " . $PkgUpdStatus);
+    :if ([:len $arrDeviceInfo] = 0) do={
+    :global arrDeviceInfo ({});
+    :local arrDeviceInfo ({"section":{"key":""; "value":"";};});
+    }
+    # else={:local arrDeviceInfo ({})}
+    :retry command={:set ($arrDeviceInfo->"devUpdates") [[ :parse "/system/package/update/get" ]]} delay=500ms max=5 on-error={:log error ("Error")};
+    #:set ($arrDeviceInfo->"devUpdates") {"devCurrentChannel"; $CurrentChannel;};
+    #:set ($arrDeviceInfo->"devUpdates") {"devCurrentVersion"; ($Update->"installed-version");};
+    #:set ($arrDeviceInfo->"devUpdates") {"devCurrentChannelVersion"; [/system/resource/get version];};
+    #:set ($arrDeviceInfo->"devUpdates") {"devUpdateChannel"; [$updateChannel];};
+    #:set ($arrDeviceInfo->"devUpdates") {"devUpdateVersion"; ($Update->"latest-version");};
+  } else={
+  :return $PkgUpdStatus;
+  }
+}
+
+:set WaitArrDeviceInfo do={
+   :global GetArrDeviceInfo;
+   :while ([ $GetArrDeviceInfo ] = false) do={
+    :delay 1s;
+   }
+}
+
+:set GetArrDeviceInfo do={
+   :global LogPrint;
+
+   :global devBoardName;
+   :global devModel;
+   
+   :global arrDeviceInfo [:toarray {"section"={"key"=""; "value"="";} }];
+   #:do {:set ($arrDeviceInfo->"Updates") [[ :parse "/system/package/update/get" ]]} on-error={}
+   :global devRbRevision;
+   :global devRbSerialNumber;
+   :global devRbCurrentFw;
+   :global devRbUpgradeFw;
+   :global devRunning;
+   :global devCurArch;
+   :global ROsVerCurrent;
+   :global ROsChannelCurrent;
+   :global ROsVerAvail;
+
+   :global ParseCustom;
+   :global runningChannelVersion;
+   :global runningChannel;
+   :global runningVersion;
+   :global devBuildTime;
+   :global GetPubIp;
+   
+   :local License [/system/license/get];
+   :local Resource [/system/resource/get];
+   :set devBuildTime ($Resource->"build-time");
+   :set runningChannelVersion ($Resource->"version");
+   :set runningChannel [$ParseCustom $runningChannelVersion "(" ")" 1];
+   :set runningVersion [$ParseCustom $runningChannelVersion " "];
+   
+   :do {
+   :if ([:pick $devBoardName 0 3] != "CHR" or [:pick $devBoardName 0 3] != "x86") do={
+      :local RouterBoard;
+      :do {:set RouterBoard [[ :parse "/system/routerboard/get" ]]} on-error={}
+      :set devModel ($RouterBoard->"model");
+      :set devRbRevision ($RouterBoard->"revision");
+      :set devRbSerialNumber ($RouterBoard->"serial-number");
+      #:set devRbCurrentFw ($RouterBoard->"current-firmware");
+      #:set devRbUpgradeFw ($RouterBoard->"upgrade-firmware");
+      :if ($devCurArch = "mipsbe" || $devCurArch = "arm") do={
+         :global devRunning [ /system/device-mode/get mode ];
+         } else={
+          :global devRunning [/system/device-mode/get running];}
+    } else={
+    :set devModel $devBoardName;
+    
+    :set devRbRevision false;
+    :set devRbSerialNumber false;
+#    :set devRbCurrentFw false;
+#    :set devRbUpgradeFw false;
+    #:set devCurrentFw $runningVersion;#:set devUpgradeFw $ROsVerAvail;
+    :set devRunning [/system/device-mode/get running];
+    }
+   } on-error={
+    $LogPrint error $0 ("Error in Function getDevice!");
+    :return false;
+   }
+:return true;
+}
+
+:set DeviceInfo do={
+  :global WaitArrDeviceInfo;
+  # $WaitDeviceInfo;
+  :global devBoardName;
+  :global devCurArch;
+  :global devModel;
+  :global devRunning;
+  :global devRbRevision;
+  :global devRbSerialNumber;
+  :global PiblicIpAddress;
+
+  :global GlobalConfVersion;
+  :global Identity;
+  :global IfThenElse;
+  :global FormatLine;
+  :local Return;
+  :local License [/system/license/get];
+  :local Resource [/system/resource/get];
+  :local Snmp [ /snmp/get ];
+  :local Update [ /system/package/update/get ];
+  
+  :set Return ([ $FormatLine "Hostname:" $Identity ] . "\n" . \
+    [ $IfThenElse ([ :len ($Snmp->"location") ] > 0) \
+      ([ $FormatLine "Location:" ($Snmp->"location") ] . "\n") ] . \
+    [ $IfThenElse ([ :len ($Snmp->"contact") ] > 0) \
+      ([ $FormatLine "Contact:" ($Snmp->"contact") ] . "\n") ] . \
+      "Hardware:\n" . \
+    [ $FormatLine "    Board:" . $devBoardName ] . "\n" . \
+    [ $FormatLine "    Arch:" . $devCurArch ] . "\n" . \
+    [ $FormatLine "    Model:" $devModel ] . \
+      [ $IfThenElse ([ :len $devRbRevision ] > 0) \
+      (" " . $devRbRevision) ] . "\n" . \
+      [ $IfThenElse ([ :len $devRbSerialNumber ] > 0) \
+       ([ $FormatLine "    Serial:" . $devRbSerialNumber ] . "\n") ] .  \
+    [ $IfThenElse ([ :len ($License->"nlevel") ] > 0) \
+      ([ $FormatLine "    License:" ("level " . ($License->"nlevel")) ] . "\n") ] . "RouterOS:\n" . \
+    [ $IfThenElse ([ :len ($License->"level") ] > 0) \
+      ([ $FormatLine "    License:" ("level " . ($License->"level")) ] . "\n") ] . \
+    [ $FormatLine "Running Channel" $runningChannel ] . "\n" . \
+    [ $FormatLine "    Installed:" ($Update->"installed-version") ] . "\n" . \
+    [ $IfThenElse ([ :typeof ($Update->"latest-version") ] != "nothing" && \
+        $Update->"installed-version" != $Update->"latest-version") \
+      ([ $FormatLine "    Available" ($Update->"latest-version") ] . "\n") ] . \
+    [ $IfThenElse ($RouterBoard->"routerboard" = true && \
+        $RouterBoard->"current-firmware" != $RouterBoard->"upgrade-firmware") \
+      ([ $FormatLine "    Firmware" ($RouterBoard->"current-firmware") ] . "\n") ] . \ 
+      "RouterOS-Scripts:\n" . \
+      [ $FormatLine "    Commit" [ $CommitBrief ] ] . "\n" . \
+      [ $FormatLine "    Version" $GlobalConfVersion ]);
+}
+
+:if ([ :typeof $NotificationFunctions ] != "array") do={:set NotificationFunctions ({})}
+:set GetRandom20CharAlNum do={
+  :global EitherOr;
+  :return [ :rndstr length=[ $EitherOr [ :tonum $1 ] 20 ] from="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ];
+}
+
+:global checkRunJob;
+:set checkRunJob do={
+  :local Script [ :tostr $1 ];
+  :global ScriptLock; 
+  :if ([ $ScriptLock $Script ] = false) do={:return false}
+  :foreach Job in=[ /system/script/job/find where script=$Script ] do={:set Job [ /system/script/job/get $Job ];
+    :while ([ :typeof ($Job->"parent") ] = "id") do={:set Job [ /system/script/job/get [ find where .id=($Job->"parent") ] ]}
+     :if (($Job->"type") = "login") do={:return true};}; :return false; 
+}
+
+:set InspectVar do={
+  :global InspectVarRet;
+  :put [ :tocrlf [ $InspectVarRet $1 ] ];
+}
+
+:set InspectVarRet do={
+  :local Input $1;
+  :local Level (0 + [ :tonum $2 ]);
+  :global CharacterReplace;
+  :global IfThenElse;
+  :global InspectVarRet;
+  :local IndentReturn do={
+    :local Prefix [ :tostr $1 ];
+    :local Value  [ :tostr $2 ];
+    :local Level  [ :tonum $3 ];
+    :global CharacterMultiply;
+    :return ([ $CharacterMultiply " " ($Level * 2) ] . "-" . $Prefix . "-> " . $Value);
+  }
+  :local TypeOf [ :typeof $Input ];
+  :local Len    [ :len $Input ];
+  :local Return [ $IndentReturn "type" $TypeOf $Level ];
+  :if ($TypeOf = "array") do={
+    :foreach Key,Value in=$Input do={
+      :set $Return ($Return . "\n" . \
+        [ $IndentReturn "key" $Key ($Level + 1) ] . "\n" . \
+        [ $InspectVarRet $Value ($Level + 2) ]);
+    }
+  } else={
+    :if ($TypeOf = "str") do={
+      :set $Return ($Return . "\n" . \
+         [ $IndentReturn "len" $Len $Level ]);
+      :if ([ :typeof [ :find $Input ("\r") ] ] = "num") do={
+        :set Input [ $CharacterReplace $Input ("\r") "" ];
+      }
+      :if ([ :typeof [ :find $Input ("\n") ] ] = "num") do={
+        :set Input [ $CharacterReplace $Input ("\n") " " ];
+      }
+    }
+    :if ($TypeOf != "nothing") do={
+      :set $Return ($Return . "\n" . \
+       [ $IndentReturn "value" [ $IfThenElse ([ :len $Input ] > 80) \
+        ([ :pick $Input 0 77 ] . "...") $Input ] $Level ]);
+    }
+  }
+  :return $Return;
+}
+
+# !!! The function is not ready !!!
+:global FetchHttpInfo do={
+  :local Mac [ :tostr $1 ];
+  :global CertificateAvailable;
+  :global IsMacLocallyAdministered;
+  :global LogPrint;
+  :local Url "https://api.macvendors.com/";
+  :if ([ $IsMacLocallyAdministered $Mac ] = true) do={:return "locally administered";}
+  :do {
+    :if ([ $CertificateAvailable "GTS Root R4" "fetch" ] = false) do={
+      $LogPrint warning $0 ("Downloading required certificate failed.");
+      :error false;
+    }
+    :local Vendor ([ /tool/fetch 
+     ($Url . [ :pick $Mac 0 8 ]) output=user as-value ]->"data");
+    :return $Vendor;
+  } on-error={
+    :onerror Err {
+      /tool/fetch check-certificate=yes-without-crl ($Url) output=none as-value;
+      $LogPrint debug $0 ("The mac vendor is not known in database.");
+    } do={ $LogPrint warning $0 ("Failed getting mac vendor: " . $Err); }
+    :return "unknown vendor";
+  }
+}
+
+:set GetPubIp do={
+    :global DetectIpAddrDefault;
+    :global DetectIpAddrFallback;
+    :global LogPrint;
+    
+    :local PubIpAddr false;
+    :local Url $DetectIpAddrDefault;
+
+#   :retry command={abc} delay=1 max=2 on-error={:put "got error"} 
+    :do {
+      $LogPrint debug $0 ("Trying to detect public IP using default service: " . $Url . "...");
+       :retry command={:set PubIpAddr ([ /tool/fetch http-method="get" check-certificate=yes-without-crl $Url output=user as-value ]->"data")} \
+        delay=50ms max=5 on-error={$LogPrint error [ :jobname ] ("Failed to detect public IP using default service: " . $Url . ".")}
+       #:error "Failed to detect public IP using default service: $Url."};
+     } on-error={
+      $LogPrint error $0 ("Failed to detect public IP using default service: " . $Url . ".");
+      :set Url $DetectIpAddrFallBack;
+      $LogPrint debug $0 ("Trying to detect public IP using fallback service: " . $Url . "...");
+       :retry command={:set PubIpAddr ([ /tool/fetch http-method="get" check-certificate=yes-without-crl $Url output=user as-value ]->"data")} \
+        delay=200ms max=5 on-error={$LogPrint error [ :jobname ] ("Failed to detect public IP using fallback service: " . $Url . ".")}
+    };
+    :if ($PubIpAddr = false) do={
+      :set PubIpAddr "not-detected";
+    } else={
+      :set PubIpAddr [ :pick $PubIpAddr 0 15];
+    }
+  :return $PubIpAddr;
+}
+
+:set FormatLine do={
+  :local Key    [ :tostr $1 ];
+  :local Value  [ :tostr $2 ];
+  :local Indent [ :tonum $3 ];
+  :local Spaces;
+  :local Return "";
+  :global CharacterMultiply;
+  :global EitherOr;
+  :set Indent [ $EitherOr $Indent 16 ];
+  :local Spaces [ $CharacterMultiply " " $Indent ];
+  :if ([ :len $Key ] > 0) do={ :set Return ($Key . ":")}
+  :if ([ :len $Key ] > ($Indent - 2)) do={:set Return ($Return . "\n" . [ :pick $Spaces 0 $Indent ] . $Value);
+  } else={:set Return ($Return . [ :pick $Spaces 0 ($Indent - [ :len $Return ]) ] . $Value)}
+  :return $Return;
+}
+
+:set ParseCustom do={
+  :local Str [ :tostr $1 ];
+  :local Start [:find [ $Str ] [:tostr $2]];
+  :local End [ :tostr $3 ];
+  :local Num [ :tonum $4 ];
+  :local Return;
+  :if ([:len $Num] = 0) do={:set Num "0"}
+  :local rest [:pick $Str ($Start+$Num) [:len $Str]]; 
+  :if ([:len $End ] = 0) do={:set Return [:pick $Str $Num $Start]} else={
+  :set End [:find $rest $End];
+  :set Return [:pick $rest 0 $End];
+  }
+:return $Return;
+}
+
+:set LogPrint do={
+  :local Severity [ :tostr $1 ];
+  :local Name     [ :tostr $2 ];
+  :local Message  [ :tostr $3 ];
+  :global PrintDebug;
+  :global PrintDebugOverride;
+  :global EitherOr;
+  :local Debug [ $EitherOr ($PrintDebugOverride->$Name) $PrintDebug ];
+  :local PrintSeverity do={
+  :global TerminalColorOutput;
+   :if ($TerminalColorOutput != true) do={:return $1}
+   :local Color { debug=96; info=97; warning=93; error=91 };
+   :return ("\1B[" . $Color->$1 . "m" . $1 . "\1B[0m");
+}
+  :local Log ([ $EitherOr $Name "<unknown>" ] . ": " . $Message);
+  :if ($Severity ~ ("^(custom|debug|error|info)\$")) do={
+    :if ($Severity = "debug") do={:log debug $Log}
+    :if ($Severity = "error") do={:log error $Log}
+    :if ($Severity = "info" ) do={:log info  $Log}
+  } else={:log warning $Log; :set Severity "warning"}
+  :if ($Severity != "debug" || $Debug = true) do={:put ([ $PrintSeverity $Severity ] . ": " . $Message)}
+}
+
+:set CertificateAvailable do={
+  :local CommonName [ :tostr $1 ];
+  :local UseFor     [ :tostr $2 ];
+  :global CertificateDownload;
+  :global EitherOr;
+  :global LogPrint;
+  :global ParseKeyValueStore;
+  :set UseFor [ $EitherOr $UseFor "undefined" ];
+  :if ([ /system/resource/get free-hdd-space ] < 8388608 && \
+       [ /certificate/settings/get crl-download ] = true && \
+       [ /certificate/settings/get crl-store ] = "system") do={
+    $LogPrint warning $0 ("This system has low free flash space but " . \
+      "is configured to download certificate CRLs to system!");
+  }
+  :if ([ :len $CommonName ] = 0) do={
+    $LogPrint warning $0 ("No CommonName given!");
+    :return false;
+  }
+  :local CertSettings [ /certificate/settings/get ];
+  :if ((($CertSettings->"builtin-trust-anchors") = "trusted" || \
+        ($CertSettings->"builtin-trust-store") ~ $UseFor || \
+        ($CertSettings->"builtin-trust-store") = "all") && \
+       [ :len [ /certificate/builtin/find where common-name=$CommonName ] ] > 0) do={
+    :return true;
+  }
+  :if ([ :len [ /certificate/find where common-name=$CommonName ] ] = 0) do={
+    $LogPrint info $0 ("Certificate with CommonName '" . $CommonName . "' not available.");
+    :if ([ $CertificateDownload $CommonName ] = false) do={:return false}
+  }
+  :if ([ :len [ /certificate/find where common-name=$CommonName ] ] > 1) do={
+    $LogPrint info $0 ("There are " . $CertCount . " Certificates with CommonName '" . $CommonName . "'. Should be ok.");
+    :return true;
+  }
+  :local CertVal [ /certificate/get [ find where common-name=$CommonName ] ];
+  :while (($CertVal->"akid") != "" && ($CertVal->"akid") != ($CertVal->"skid")) do={
+    :if ([ :len [ /certificate/find where skid=($CertVal->"akid") ] ] = 0) do={
+      :local IssuerCN ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN");
+      $LogPrint info $0 ("Certificate chain for '" . $CommonName . "' is incomplete, missing '" . $IssuerCN . "'.");
+      :if ([ $CertificateDownload $IssuerCN ] = false) do={:return false}
+    }
+    :set CertVal [ /certificate/get [ find where skid=($CertVal->"akid") ] ];
+  }
+  :return true;
+}
+
+:set CertificateDownload do={
+  :local CommonName [ :tostr $1 ];
+  :global GlobalScriptsUrl;
+  :global GlobalScriptsUrlSuffix;
+  :global CertificateNameByCN;
+  :global CleanName;
+  :global FetchUserAgentStr;
+  :global LogPrint;
+  :global RmFile;
+  :global WaitForFile;
+  
+  $LogPrint info $0 ("Downloading and importing certificate with CommonName '" . $CommonName . "'.");
+  
+  :local FileName ([ $CleanName $CommonName ] . ".pem");
+  :do {
+    /tool/fetch check-certificate=yes-without-crl http-header-field=({ [ $FetchUserAgentStr $0 ] }) \
+      ($GlobalScriptsUrl . "certs/" . $FileName . $GlobalScriptsUrlSuffix) dst-path=$FileName as-value;
+    $WaitForFile $FileName;
+  } on-error={
+    $LogPrint warning $0 ("Failed downloading certificate with CommonName '" . $CommonName . \
+      "' from repository! Trying fallback to mkcert.org...");
+    :do {
+      :if ([ :len [ /certificate/find where common-name="ISRG Root X1" ] ] = 0) do={
+        $LogPrint error $0 ("Required certificate is not available.");
+        :return false;
+      }
+      /tool/fetch check-certificate=yes-without-crl http-header-field=({ [ $FetchUserAgentStr $0 ] }) \
+        "https://mkcert.org/generate/" http-data=[ :serialize to=json ({ $CommonName }) ] \
+        dst-path=$FileName as-value;
+      $WaitForFile $FileName;
+      :if ([ /file/get $FileName size ] = 0) do={
+        $RmFile $FileName;
+        :error false;
+      }
+    } on-error={
+      $LogPrint warning $0 ("Failed downloading certificate with CommonName '" . $CommonName . "'!");
+      :return false;
+    }
+  }
+
+  /certificate/import file-name=$FileName passphrase="" as-value;
+  :delay 1s;
+  $RmFile $FileName;
+
+  :if ([ :len [ /certificate/find where common-name=$CommonName ] ] = 0) do={
+    /certificate/remove [ find where name~("^" . $FileName . "_[0-9]+\$") ];
+    $LogPrint warning $0 ("Certificate with CommonName '" . $CommonName . "' still unavailable!");
+    :return false;
+  }
+  :foreach Cert in=[ /certificate/find where name~("^" . $FileName . "_[0-9]+\$") ] do={
+    $CertificateNameByCN [ /certificate/get $Cert common-name ];
+  }
+  :return true;
+}
+
+:set CertificateNameByCN do={
+  :local Match [ :tostr $1 ];
+  :global CleanName;
+  :global LogPrint;
+  :local Cert ([ /certificate/find where (common-name=$Match or fingerprint=$Match or name=$Match) ]->0);
+  :if ([ :len $Cert ] = 0) do={
+    $LogPrint warning $0 ("No matching certificate found.");
+    :return false;
+  }
+  :local CommonName [ /certificate/get $Cert common-name ];
+  /certificate/set $Cert name=[ $CleanName $CommonName ];
+  :return true;
+}
+
+:set CharacterMultiply do={
+  :local Str [ :tostr $1 ];
+  :local Num [ :tonum $2 ];
+  :if ($Num = 0) do={:return ""}
+  :local Return "";
+  :for I from=1 to=$Num do={:set Return ($Return . $Str)}
+  :return $Return;
+}
+
+:set ScriptInstallUpdate do={ :onerror Err {
+  :local Scripts    [ :toarray $1 ];
+  :local NewComment [ :tostr   $2 ];
+  :global InitSetup;
+
+  :global ScriptUpdatesCRLF;
+  :global GlobalConfVersion;
+  :global Identity;
+  :global CertificateAvailable;
+  :global CheckSumsVerify;
+  :global GlobalScriptsUrl;
+  :global GlobalScriptsUrlSuffix;
+  :global EitherOr;
+  :global FetchUserAgentStr;
+  :global Grep;
+  :global IfThenElse;
+  :global LogPrint;
+  :global ParseKeyValueStore;
+  :global RequiredRouterOS;
+  :global SendNotification2;
+  :global SymbolForNotification;
+  :global ValidateSyntax;
+  :global CommentsInScripts;
+
+  :if ([ $CertificateAvailable "Root YE" "fetch" ] = false) do={
+    $LogPrint warning $0 ("Downloading certificate failed, trying without.");
+  }
+
+  :foreach Script in=$Scripts do={
+    :if ([ :len [ /system/script/find where name=$Script ] ] = 0) do={
+      $LogPrint info $0 ("Adding new script: " . $Script);
+      /system/script/add name=$Script owner=$Script source="#!rsc by Vados\n" comment=$NewComment;
+    }
+  }
+    
+  :local GlobalConfVersionBefore $GlobalConfVersion;
+  :local ReloadGlobal false;
+  :local DeviceMode [ /system/device-mode/get ];
+  
+  :if ($CheckSumsVerify = true) do={
+    :local CheckSums ({});
+     :do {
+     :local Url ($GlobalScriptsUrl . "checksums.json" . $GlobalScriptsUrlSuffix);
+      $LogPrint debug $0 ("Fetching checksums from url: " . $Url);
+      :set CheckSums [ :deserialize from=json ([ /tool/fetch check-certificate=yes-without-crl \
+        http-header-field=({ [ $FetchUserAgentStr $0 ] }) $Url output=user as-value ]->"data") ];
+    } on-error={ $LogPrint warning $0 ("Err = " . $Err); }
+    }
+
+  :foreach Script in=[ /system/script/find where source~"^#!rsc by Vados" ] do={
+    :local ScriptVal [ /system/script/get $Script ];
+    :local ScriptInfo [ $ParseKeyValueStore ($ScriptVal->"comment") ];
+    :local SourceNew;
+
+    :foreach Scheduler in=[ /system/scheduler/find where on-event~("\\b" . $ScriptVal->"name" . "\\b") ] do={
+      :local SchedulerVal [ /system/scheduler/get $Scheduler ];
+      :if ($ScriptVal->"policy" != $SchedulerVal->"policy") do={
+        $LogPrint warning $0 ("Policies differ for script '" . $ScriptVal->"name" . "' and its scheduler '" . $SchedulerVal->"name" . "'!");
+        }
+    }
+    :do {
+      :if ($ScriptInfo->"ignore" = true) do={
+        $LogPrint debug $0 ("Ignoring script '" . $ScriptVal->"name" . "', as requested.");
+        :error true;
+      }
+      :if ($CheckSumsVerify = true) do={
+        :local CheckSum ($CheckSums->($ScriptVal->"name"));
+        :if ([ :len ($ScriptInfo->"base-url") ] = 0 && [ :len ($ScriptInfo->"url-suffix") ] = 0 && \
+             [ :convert transform=md5 to=hex [ :tolf ($ScriptVal->"source") ] ] = $CheckSum) do={
+          $LogPrint debug $0 ("Checksum for script '" . $ScriptVal->"name" . "' matches, ignoring.");
+          :error true;
+        }
+      }
+      :if ([ :len ($ScriptInfo->"certificate") ] > 0) do={
+        :if ([ $CertificateAvailable ($ScriptInfo->"certificate") "fetch" ] = false) do={
+          $LogPrint warning $0 ("Downloading certificate failed, trying without.");
+        }
+      }
+      :onerror Err {
+        :local BaseUrl [ $EitherOr ($ScriptInfo->"base-url") $GlobalScriptsUrl ];
+        :local UrlSuffix [ $EitherOr ($ScriptInfo->"url-suffix") $GlobalScriptsUrlSuffix ];
+        :local Url ($BaseUrl . $ScriptVal->"name" . ".rsc" . $UrlSuffix);
+        $LogPrint warning $0 ("Fetching script '" . ($ScriptVal->"name") . "' from url: " . $Url);
+        :local Result [ /tool/fetch check-certificate=yes-without-crl \
+          http-header-field=({ [ $FetchUserAgentStr $0 ] }) $Url output=user as-value ];
+        :if ($Result->"status" = "finished") do={
+          :set SourceNew [ :tolf ($Result->"data") ];
+        } 
+      } do={
+        $LogPrint warning $0 ("Failed fetching script '" . $ScriptVal->"name" . "': " . $Err);
+        :if ($Err != "Fetch failed with status 404") do={
+          :error false;
+        }
+        :if ($ScriptVal->"source" = "#!rsc by Vados") do={
+          $LogPrint warning $0 ("Removing dummy. Typo on installation?");
+          /system/script/remove $Script;
+          :error false;
+        }
+        :if ($CheckSumsVerify = true) do={
+          :if ([ :len ($ScriptInfo->"base-url") ] = 0 && [ :len ($ScriptInfo->"url-suffix") ] = 0 && \
+               [ :len $CheckSum ] = 0) do={
+            $LogPrint warning $0 ("Added the script manually? Skip updates with 'ignore=true' in comment.");
+          }
+        }
+        :error false;
+      }
+
+      :if ([ :len $SourceNew ] = 0) do={
+        $LogPrint debug $0 ("No update for script '" . ($ScriptVal->"name") . "'.");
+        :error false;
+      }
+
+      :local SourceCRLF [ :tocrlf $SourceNew ];
+      :if ($InitSetup != true && (($ScriptVal->"name") != "AM-GlobalConfig" || ($ScriptVal->"name") != "AM-GlobalFunc")) do={
+        :if ($SourceNew = ($ScriptVal->"source") || $SourceCRLF = ($ScriptVal->"source")) do={
+          $LogPrint debug $0 ("Script '" .  ($ScriptVal->"name") . "' source did not change. End of lines set to CRLF.");
+          if ($ScriptUpdatesCRLF = false) do={
+            :put [ :tolf $SourceNew ];
+            $LogPrint debug $0 ("Script '" .  ($ScriptVal->"name") . "' source update end of lines to LF.");
+          }
+        :error false;
+        }
+      }
+
+      :if ([ :pick $SourceNew 0 14 ] != "#!rsc by Vados") do={
+        $LogPrint warning $0 ("Looks like new script '" . $ScriptVal->"name" . "' is not valid (missing shebang = '$shebang') Ignoring!");
+        :error false;
+      }
+
+      :if ($CommentsInScripts = true) do={
+         :local CommentLine [ $Grep $SourceNew ("\23 Script comment: ") ];
+         :if ([ :len $CommentLine ] = 0) do={
+           $LogPrint debug $0 ("The search in new script '" . ($ScriptVal->"name") . "' for comment in script body, is not found result Ignoring!");
+           :error false;
+         } else={
+           :if ([ :len ($ScriptVal->"comment")] = 0 || [ :len ($ScriptVal->"comment")] != [ :pick $CommentLine ([ :find $CommentLine ":"] + 2) [ :len $CommentLine] ]) do={
+             $LogPrint debug $0 ("Exsiting comment for script '" . ($ScriptVal->"name") . "' is '" . ($ScriptVal->"comment") . "'.");
+             :set ($ScriptVal->"comment") [ :pick $CommentLine ([ :find $CommentLine ":"] + 2) [ :len $CommentLine] ];
+             $LogPrint debug $0 ("New comment (" . ($ScriptVal->"comment") . ") for script '" . ($ScriptVal->"name") . "' has been added!");
+           } else={
+             $LogPrint debug $0 ("Comment for script '" . ($ScriptVal->"name") . "' did not change.");
+             :error false;
+           }
+         }   
+      }
+
+      :local RequiredROS ([ $ParseKeyValueStore [ $Grep $SourceNew ("\23 requires RouterOS, ") ] ]->version);
+      :if ([ $RequiredRouterOS $0 [ $EitherOr $RequiredROS "0.0" ] false ] = false) do={
+        $LogPrint warning $0 ("The script '" . ($ScriptVal->"name") . "' requires RouterOS " . \
+            $RequiredROS . ", which is not met by your installation. Ignoring!");
+        :error false;
+      }
+      :local RequiredDM [ $ParseKeyValueStore [ $Grep $SourceNew ("\23 requires device-mode, ") ] ];
+      :local MissingDM ({});
+      :foreach Feature,Value in=$RequiredDM do={
+        :if ([ :typeof ($DeviceMode->$Feature) ] = "bool" && ($DeviceMode->$Feature) = false) do={
+          :set MissingDM ($MissingDM, $Feature);
+        }
+      }
+      :if ([ :len $MissingDM ] > 0) do={
+        $LogPrint warning $0 ("The script '" . ($ScriptVal->"name") . "' requires disabled " . \
+            "device-mode features (" . [ :tostr $MissingDM ] . "). Ignoring!");
+        :error false;
+      }
+
+      :if ([ $ValidateSyntax $SourceNew ] = false) do={
+        $LogPrint warning $0 ("Syntax validation for script '" . ($ScriptVal->"name") . "' failed! Ignoring!");
+        :error false;
+      }
+      
+      :log info ("Updating script: " . ($ScriptVal->"name") . " Script comment: " . ($ScriptVal->"comment"));
+
+      /system/script/set owner=($ScriptVal->"name") comment=($ScriptVal->"comment") \
+          source=[ $IfThenElse ($ScriptUpdatesCRLF = true) $SourceCRLF $SourceNew ] $Script;
+      :if ($ScriptVal->"name" = "AM-GlobalConfig" || \
+           $ScriptVal->"name" = "AM-GlobalFunc" || \
+           $ScriptVal->"name" ~ ("^(AM-GlobalFunc\\.d|mod)/.")) do={
+        :set ReloadGlobal true;
+      }
+    } on-error={ }
+  }
+  :if ($ReloadGlobal = true) do={
+    $LogPrint info $0 ("Reloading global configuration and functions.");
+    :onerror Err {
+      :global InitSetup (a);
+      /system/script {run AM-GlobalConfig; run AM-GlobalFunc;};
+    } do={$LogPrint error $0 ("Reloading global configuration and functions failed! " . $Err)}
+  }
+  :if ($GlobalConfVersionBefore > $GlobalConfVersion) do={
+    $LogPrint warning $0 ("The configuration version decreased from " . \
+      $GlobalConfVersionBefore . " to " . $GlobalConfVersion . \
+      ". Installed an older version?");
+  }
+    :if ($GlobalConfVersionBefore < $GlobalConfVersion) do={
+    :global GlobalConfChanges;
+    :global GlobalConfMigration;
+    :local ChangeLogCode;
+    :if ([ :len $ChangeLogCode ] > 0) do={
+      :if ([ $ValidateSyntax $ChangeLogCode ] = true) do={
+        :onerror Err {
+          [ :parse $ChangeLogCode ];
+        } do={$LogPrint warning $0 ("The changelog failed to run: " . $Err)}
+      } else={$LogPrint warning $0 ("The changelog failed syntax validation!")}
+    }
+    :if ([ :len $GlobalConfMigration ] > 0) do={
+      :for I from=($GlobalConfVersionBefore + 1) to=$GlobalConfVersion do={
+        :local Migration ($GlobalConfMigration->[ :tostr $I ]);
+        :do {
+          :if ([ :typeof $Migration ] != "str") do={
+            $LogPrint debug $0 ("Migration code for change " . $I . " is not available.");
+            :error false;
+          }
+          :if ([ $ValidateSyntax $Migration ] = false) do={
+            $LogPrint warning $0 ("Migration code for change " . $I . " failed syntax validation!");
+            :error false}
+          $LogPrint info $0 ("Applying migration for change " . $I . ": " . $Migration);
+          :onerror Err {
+            [ :parse $Migration ];
+          } do={$LogPrint warning $0 ("Migration code for change " . $I . " failed to run: " . $Err)}
+        } on-error={ }
+      }
+    }
+    :local NotificationMessage ("The configuration version on " . $Identity . " increased " . \
+       "to " . $GlobalConfVersion . ", current configuration may need modification. " . \
+       "Please review and update global-config-overlay, then re-run global-config.");
+    $LogPrint info $0 ($NotificationMessage);
+    :if ([ :len $GlobalConfChanges ] > 0) do={
+      :set NotificationMessage ($NotificationMessage . "\n\nChanges:");
+      :for I from=($GlobalConfVersionBefore + 1) to=$GlobalConfVersion do={
+        :local Change ($GlobalConfChanges->[ :tostr $I ]);
+        :set NotificationMessage ($NotificationMessage . "\n " . \
+            [ $SymbolForNotification "pushpin" "*" ] . $Change);
+        $LogPrint info $0 ("Change " . $I . ": " . $Change);
+      }
+    } else={:set NotificationMessage ($NotificationMessage . "\n\nNews and changes are not available.")}
+
+    :set GlobalConfChanges;
+    :set GlobalConfMigration;
+  }
+} do={:global ExitOnError; $ExitOnError $0 $Err}
+}
+
+:set FetchUserAgentStr do={
+  :local Caller [ :tostr $1 ];
+  :global IfThenElse;
+  :local Resource [ /system/resource/get ];
+  :return ("User-Agent: Mikrotik/" . $Resource->"version" . " " . $Resource->"architecture-name" . \
+    " " . $Caller . "/Fetch (https://amster.vados.ru/; main/3419/3ee19780)");
+}
+
+:set Grep do={
+  :local Input  ([ :tostr $1 ] . "\n");
+  :local Pattern [ :tostr $2 ];
+  :if ([ :typeof [ :find $Input $Pattern ] ] = "nil") do={:return []}
+  :do {
+    :local Line [ :pick $Input 0 [ :find $Input "\n" ] ];
+    :if ([ :typeof [ :find $Line $Pattern ] ] = "num") do={:return $Line}
+    :set Input [ :pick $Input ([ :find $Input "\n" ] + 1) [ :len $Input ] ];
+  } while=([ :len $Input ] > 0);
+  :return [];
+}
+
+:set HumanReadableNum do={
+  :local Input [ :tonum $1 ];
+  :local Base  [ :tonum $2 ];
+  :global EitherOr;
+  :global IfThenElse;
+  :local Prefix "kMGTPE";
+  :local Pow 1;
+  :set Base [ $EitherOr $Base 1024 ];
+  :local Bin [ $IfThenElse ($Base = 1024) "i" "" ];
+  :if ($Input < $Base) do={:return $Input}
+  :for I from=0 to=[ :len $Prefix ] do={
+    :set Pow ($Pow * $Base);
+    :if ($Input / $Base < $Pow) do={
+      :set Prefix [ :pick $Prefix $I ];
+      :local Tmp1 ($Input * 100 / $Pow);
+      :local Tmp2 ($Tmp1 / 100);
+      :if ($Tmp2 >= 100) do={:return ($Tmp2 . $Prefix . $Bin)}
+      :return ($Tmp2 . "." . \
+          [ :pick $Tmp1 [ :len $Tmp2 ] ([ :len $Tmp1 ] - [ :len $Tmp2 ] + 1) ] . \
+          $Prefix . $Bin);
+    }
+  }
+}
+
+:set CleanName do={
+  :local Input [ :tostr $1 ];
+  :local Return "";
+  :for I from=0 to=([ :len $Input ] - 1) do={
+    :local Char [ :pick $Input $I ];
+    :if ([ :typeof [ find "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" $Char ] ] = "nil") do={
+      :do {:if ([ :len $Return ] = 0) do={:error true};
+        :if ([ :pick $Return ([ :len $Return ] - 1) ] = "-") do={:error true}; :set Char "-"} on-error={:set Char ""}; 
+    };
+    :set Return ($Return . $Char)};
+  :return $Return;
+}
+
+:set GetRandomNumber do={
+  :global EitherOr;
+  :return [ :rndnum from=0 to=[ $EitherOr [ :tonum $1 ] 4294967295 ] ]
+}
+
+:set RandomDelay do={
+  :local Time [ :tonum $1 ];
+  :local Unit [ :tostr $2 ];
+  :global EitherOr;
+  :global GetRandomNumber;
+  :global MAX;
+  :if ($Time = 0) do={:return false}
+  :delay ([ $MAX 10 [ $GetRandomNumber ([ :tonsec [ :totime ($Time . [ $EitherOr $Unit "s" ]) ] ] / 1000000) ] ] . "ms");
+}
+
+:set EscapeForRegEx do={
+  :local Input [ :tostr $1 ];
+  :if ([ :len $Input ] = 0) do={:return ""}
+  :local Return "";
+  :local Chars ("^.[]\$()|*+?{}\\");
+  :for I from=0 to=([ :len $Input ] - 1) do={
+    :local Char [ :pick $Input $I ];
+    :if ([ :find $Chars $Char ]) do={:set Char ("\\" . $Char)}
+    :set Return ($Return . $Char);
+  }
+  :return $Return;
+}
+:set ExitError do={
+  :local ExitOK [ :tostr $1 ];
+  :local Name   [ :tostr $2 ];
+  :local Error  [ :tostr $3 ];
+  :global IfThenElse;
+  :global LogPrint;
+  :if ($ExitOK = "false") do={
+    $LogPrint error $Name ([ $IfThenElse ([ :pick $Name 0 1 ] = "\$") \
+        "Function" "Script" ] . " '" . $Name . "' exited with error" . \
+        [ $IfThenElse (!($Error ~ "^(|true|false)\$")) (": " . $Error) "." ]);
+  }
+}
+
+:set MiniDateTimeStamp do={
+    /system/clock;
+    :local vdate [get date];
+    :local vtime [get time];
+    :local vdoff [:toarray "0,4,5,7,8,10"];
+    :local MM    [:pick $vdate ($vdoff->2) ($vdoff->3)];
+    :local M     [:tonum $MM];
+    :if ($vdate ~ ".../../....") do={
+        :set vdoff [:toarray "7,11,1,3,4,6"];
+        :set M     ([:find "xxanebarprayunulugepctovecANEBARPRAYUNULUGEPCTOVEC" [:pick $vdate ($vdoff->2) ($vdoff->3)] -1] / 2);
+        :if ($M>12) do={:set M ($M - 12)}
+        :set MM    [:pick (100 + $M) 1 3];
+    }
+    :local yyyy [:pick $vdate ($vdoff->0) ($vdoff->1)];
+    :local dd   [:pick $vdate ($vdoff->4) ($vdoff->5)];
+    :local HH   [:pick $vtime 0  2];
+    :local mm   [:pick $vtime 3  5];
+    :local ss   [:pick $vtime 6  8];
+    :return ([$yyyy] . [$MM] . [$dd] . [$HH] . [$mm] . [$ss]);
+}
+
+:set WaitForFile do={
+  :local FileName [ :tostr  $1 ];
+  :local WaitTime [ :totime $2 ];
+  :global CleanFilePath;
+  :global EitherOr;
+  :global MAX;
+  :set FileName [ $CleanFilePath $FileName ];
+  :local Delay ([ $MAX [ $EitherOr $WaitTime 2s ] 100ms ] / 9);
+  :do {
+    :retry {
+      /file/get $FileName;
+      :return true;
+    } delay=$Delay max=10;
+  } on-error={ }
+  :while ([ :len [ /file/find where name=$FileName ] ] > 0) do={
+    :do {
+      /file/get $FileName;
+      :return true;
+    } on-error={ }
+    :delay $Delay;
+    :set Delay ($Delay * 3 / 2);
+  }
+  :return false;
+}
+
+:set FileGet do={
+  :local FileName [ :tostr $1 ];
+  :global WaitForFile;
+  :if ([ $WaitForFile $FileName 0s ] = false) do={:return false}
+  :local FileVal false;
+  :do {:set FileVal [ /file/get $FileName ];
+   } on-error={ }
+  :return $FileVal;
+}
+
+:set RmDir do={
+  :local DirName [ :tostr $1 ];
+  :global FileGet;
+  :global LogPrint;
+  $LogPrint debug $0 ("Removing directory: ". $DirName);
+  :local DirVal [ $FileGet $DirName ];
+  :if ($DirVal = false) do={$LogPrint debug $0 ("... which does not exist."); :return true};
+  :if ($DirVal->"type" != "directory") do={$LogPrint error $0 ("Directory '" . $DirName . "' is not a directory."); :return false};
+  :onerror Err {/file/remove $DirName} do={$LogPrint error $0 ("Removing directory '" . $DirName . "' failed: " . $Err); :return false};
+  :return true;
+}
+
+:set RmFile do={
+  :local FileName [ :tostr $1 ];
+  :global FileGet;
+  :global LogPrint;
+  $LogPrint debug $0 ("Removing file: ". $FileName);
+  :local FileVal [ $FileGet $FileName ];
+  :if ($FileVal = false) do={$LogPrint debug $0 ("... which does not exist."); :return true}
+  :if ($FileVal->"type" = "directory" || $FileVal->"type" = "disk") do={
+    $LogPrint error $0 ("File '" . $FileName . "' is not a file."); :return false}
+  :onerror Err {
+    /file/remove $FileName;
+  } do={$LogPrint error $0 ("Removing file '" . $FileName . "' failed: " . $Err); :return false}
+  :return true;
+}
+
+:set MkDir do={
+  :local Path [ :tostr $1 ];
+  :global CleanFilePath;
+  :global FileGet;
+  :global RmDir;
+  :global WaitForFile;
+  :global LogPrint;
+  :local MkTmpfs do={
+   :global WaitForFile;
+   :local TmpFs [ /disk/find where slot=tmpfs type=tmpfs ];
+   :if ([ :len $TmpFs ] = 1) do={
+     :if ([ /disk/get $TmpFs disabled ] = true) do={
+       $LogPrint info $0 ("The tmpfs is disabled, enabling.");
+       /disk/enable $TmpFs;
+      }
+      :return true;
+    }
+    $LogPrint info $0 ("Creating disk of type tmpfs.");
+    $RmDir "tmpfs";
+    :onerror Err {
+      /disk/add slot=tmpfs type=tmpfs tmpfs-max-size=([ /system/resource/get total-memory ] / 3);
+      $WaitForFile "tmpfs";
+    } do={
+      $LogPrint warning $0 ("Creating disk of type tmpfs failed: " . $Err);
+      :return false;
+    }
+    :return true;
+  }
+  :set Path [ $CleanFilePath $Path ];
+  :if ($Path = "") do={
+    :return true;
+  }
+  $LogPrint debug $0 ("Making directory: " . $Path);
+  :local PathVal [ $FileGet $Path ];
+  :if ($PathVal->"type" = "directory") do={
+    $LogPrint debug $0 ("... which already exists.");
+    :return true;
+  }
+  :if ([ :pick $Path 0 5 ] = "tmpfs") do={
+    :if ([ $MkTmpfs ] = false) do={
+      :return false;
+    }
+  }
+  :onerror Err {
+    /file/add type="directory" name=$Path;
+    $WaitForFile $Path;
+  } do={
+    $LogPrint warning $0 ("Making directory '" . $Path . "' failed: " . $Err);
+    :return false;
+  }
+  :return true;
+}
+
+:set MAX do={:if ($1 > $2) do={:return $1}
+ :return $2;
+}
+:set MIN do={:if ($1 < $2) do={:return $1}
+ :return $2;
+}
+
+:set CharacterMultiply do={
+  :local Str [ :tostr $1 ];
+  :local Num [ :tonum $2 ];
+  :if ($Num = 0) do={:return ""}
+  :local Return "";
+  :for I from=1 to=$Num do={:set Return ($Return . $Str)}
+  :return $Return;
+}
+
+:set CharacterReplace do={
+  :local String [ :tostr $1 ];
+  :local ReplaceFrom [ :tostr $2 ];
+  :local ReplaceWith [ :tostr $3 ];
+  :local Return "";
+  :if ($ReplaceFrom = "") do={:return $String}
+  :while ([ :typeof [ :find $String $ReplaceFrom ] ] != "nil") do={
+    :local Pos [ :find $String $ReplaceFrom ];
+    :set Return ($Return . [ :pick $String 0 $Pos ] . $ReplaceWith);
+    :set String [ :pick $String ($Pos + [ :len $ReplaceFrom ]) [ :len $String ] ];
+  }
+  :return ($Return . $String);
+}
+
+:set CleanFilePath do={
+  :local Path [ :tostr $1 ];
+  :global CharacterReplace;
+  :while ($Path ~ "//") do={:set $Path [ $CharacterReplace $Path "//" "/" ]};
+  :if ([ :pick $Path 0 ] = "/") do={:set Path [ :pick $Path 1 [ :len $Path ] ]};
+  :if ([ :pick $Path ([ :len $Path ] - 1) ] = "/") do={:set Path [ :pick $Path 0 ([ :len $Path ] - 1) ]};
+  :return $Path;
+}
+
+:set IfThenElse do={
+  :if ([ :tostr $1 ] = "true" || [ :tobool $1 ] = true) do={:return $2};
+  :return $3;
+}
+
+:set EitherOr do={
+  :global IfThenElse;
+  :if ([ :typeof $1 ] = "num") do={:return [ $IfThenElse ($1 != 0) $1 $2 ]}
+  :if ([ :typeof $1 ] = "time") do={:return [ $IfThenElse ($1 > 0s) $1 $2 ]}
+# this works for boolean values, literal ones with parentheses
+  :return [ $IfThenElse ([ :len [ :tostr $1 ] ] > 0) $1 $2 ];
+}
+
+:set ParseKeyValueStore do={
+  :local Source $1;
+  :if ([ :pick $Source 0 1 ] = "{") do={
+    :do {
+      :return [ :deserialize from=json $Source ];
+    } on-error={ }
+  }   
+  :if ([ :typeof $Source ] != "array") do={:set Source [ :tostr $1 ]}
+  :local Result ({});
+  :foreach KeyValue in=[ :toarray $Source ] do={
+    :if ([ :find $KeyValue "=" ]) do={
+      :local Key [ :pick $KeyValue 0 [ :find $KeyValue "=" ] ];
+      :local Value [ :pick $KeyValue ([ :find $KeyValue "=" ] + 1) [ :len $KeyValue ] ];
+      :if ($Value="true") do={:set Value true}
+      :if ($Value="false") do={:set Value false}
+      :set ($Result->$Key) $Value;
+      } else={:set ($Result->$KeyValue) true}
+  }
+  :return $Result;
+}
+:set RequiredRouterOS do={
+  :local Caller   [ :tostr $1 ];
+  :local Required [ :tostr $2 ];
+  :local Warn     [ :tostr $3 ];
+  :global IfThenElse;
+  :global LogPrint;
+  :global VersionToNum;
+  :if (!($Required ~ "^\\d+\\.\\d+((alpha|beta|rc|\\.)\\d+|)\$")) do={
+    $LogPrint error $0 ("No valid RouterOS version: " . $Required);
+    :return false;
+  }
+  :if ([ $VersionToNum $Required ] > [ $VersionToNum [ /system/package/update/get installed-version ] ]) do={
+    :if ($Warn = "true") do={
+      $LogPrint warning $0 ("This " . [ $IfThenElse ([ :pick $Caller 0 ] = ("\$")) "function" "script" ] . \
+        " '" . $Caller . "' (at least specific functionality) requires RouterOS " . $Required . ". Please update!");
+    }
+    :return false;
+  }
+  :return true;
+}
+:set VersionToNum do={
+  :local Input [ :tostr $1 ];
+  :local Multi 0x1000000;
+  :local Return 0;
+  :global CharacterReplace;
+  :set Input [ $CharacterReplace $Input "." "," ];
+  :foreach I in={ "zero"; "alpha"; "beta"; "rc" } do={
+    :set Input [ $CharacterReplace $Input $I ("," . $I . ",") ];
+  }
+  :foreach Value in=([ :toarray $Input ], 0) do={
+    :local Num [ :tonum $Value ];
+    :if ($Multi = 0x100) do={
+      :if ([ :typeof $Num ] = "num") do={
+        :set Return ($Return + 0xff00);
+        :set Multi ($Multi / 0x100);
+      } else={
+        :if ($Value = "zero") do={ }
+        :if ($Value = "alpha") do={ :set Return ($Return + 0x3f00); }
+        :if ($Value = "beta") do={ :set Return ($Return + 0x5f00); }
+        :if ($Value = "rc") do={ :set Return ($Return + 0x7f00); }
+      }
+    }
+    :if ([ :typeof $Num ] = "num") do={ :set Return ($Return + ($Value * $Multi)); }
+    :set Multi ($Multi / 0x100);
+  }
+  :return $Return;
+}
+:set ScriptLock do={
+  :local Script  [ :tostr  $1 ];
+  :local WaitMax [ :totime $2 ];
+  :global GetRandom20CharAlNum;
+  :global IfThenElse;
+  :global LogPrint;
+  :global ScriptLockOrder;
+  :if ([ :typeof $ScriptLockOrder ] = "nothing") do={
+    :set ScriptLockOrder ({});
+  }
+  :if ([ :typeof ($ScriptLockOrder->$Script) ] = "nothing") do={
+    :set ($ScriptLockOrder->$Script) ({});
+  }
+  :local JobCount do={
+    :local Script [ :tostr $1 ];
+    :return [ :len [ /system/script/job/find where script=$Script ] ];
+  }
+  :local TicketCount do={
+    :local Script [ :tostr $1 ];
+    :global ScriptLockOrder;
+    :local Count 0;
+    :foreach Ticket in=($ScriptLockOrder->$Script) do={
+      :if ([ :typeof $Ticket ] != "nothing") do={:set Count ($Count + 1)}
+    }
+    :return $Count;
+  }
+  :local IsFirstTicket do={
+    :local Script [ :tostr $1 ];
+    :local Check  [ :tostr $2 ];
+    :global ScriptLockOrder;
+    :foreach Ticket in=($ScriptLockOrder->$Script) do={
+      :if ($Ticket = $Check) do={:return true}
+      :if ([ :typeof $Ticket ] != "nothing" && $Ticket != $Check) do={:return false}
+    }
+    :return false;
+  }
+  :local AddTicket do={
+    :local Script [ :tostr $1 ];
+    :local Add    [ :tostr $2 ];
+    :global ScriptLockOrder;
+    :while (true) do={
+      :local Pos [ :len ($ScriptLockOrder->$Script) ];
+      :set ($ScriptLockOrder->$Script->$Pos) $Add;
+      :delay 10ms;
+      :if (($ScriptLockOrder->$Script->$Pos) = $Add) do={:return true}
+    }
+  }
+  :local RemoveTicket do={
+    :local Script [ :tostr $1 ];
+    :local Remove [ :tostr $2 ];
+    :global ScriptLockOrder;
+    :foreach Id,Ticket in=($ScriptLockOrder->$Script) do={
+      :while (($ScriptLockOrder->$Script->$Id) = $Remove) do={
+        :set ($ScriptLockOrder->$Script->$Id);
+        :delay 10ms;
+      }
+    }
+  }
+  :local CleanupTickets do={
+    :local Script [ :tostr $1 ];
+    :global ScriptLockOrder;
+    :foreach Ticket in=($ScriptLockOrder->$Script) do={
+      :if ([ :typeof $Ticket ] != "nothing") do={:return false}
+    }
+    :set ($ScriptLockOrder->$Script) ({});
+  }
+  :if ([ :typeof $WaitMax ] = "nil" ) do={:set WaitMax 0s}
+  :if ([ :len [ /system/script/find where name=$Script ] ] = 0) do={
+    $LogPrint error $0 ("A script named '" . $Script . "' does not exist!");
+    :error false;
+  }
+  :if ([ $JobCount $Script ] = 0) do={
+    $LogPrint error $0 ("No script '" . $Script . "' is running!");
+    :error false;
+  }
+  :if ([ $TicketCount $Script ] >= [ $JobCount $Script ]) do={
+    $LogPrint error $0 ("More tickets than running scripts '" . $Script . "', resetting!");
+    :set ($ScriptLockOrder->$Script) ({});
+    /system/script/job/remove [ find where script=$Script ];
+  }
+  :local MyTicket [ $GetRandom20CharAlNum 6 ];
+  $AddTicket $Script $MyTicket;
+  :local WaitInterval ($WaitMax / 20);
+  :local WaitTime $WaitMax;
+  :while ($WaitTime > 0 && \
+      ([ $IsFirstTicket $Script $MyTicket ] = false || \
+      [ $TicketCount $Script ] < [ $JobCount $Script ])) do={
+    :set WaitTime ($WaitTime - $WaitInterval);
+    :delay $WaitInterval;
+  }
+  :if ([ $IsFirstTicket $Script $MyTicket ] = true && \
+      [ $TicketCount $Script ] = [ $JobCount $Script ]) do={
+    $RemoveTicket $Script $MyTicket;
+    $CleanupTickets $Script;
+    :return true;
+  }
+  $RemoveTicket $Script $MyTicket;
+  $LogPrint debug $0 ("Script '" . $Script . "' started more than once" . \
+    [ $IfThenElse ($WaitTime < $WaitMax) " and timed out waiting for lock" "" ] . "...");
+  :return false;
+}
+:set SendNotification do={ :onerror Err {
+  :global SendNotification2;
+  $SendNotification2 ({ origin=$0; subject=$1; message=$2; link=$3; silent=$4 });
+} do={:global ExitOnError; $ExitOnError $0 $Err}
+}
+:set SendNotification2 do={
+  :local Notification $1;
+  :global NotificationFunctions;
+  :foreach FunctionName,Discard in=$NotificationFunctions do={
+    ($NotificationFunctions->$FunctionName) ("\$NotificationFunctions->\"" . $FunctionName . "\"") $Notification;
+  }
+}
+:set SymbolByUnicodeName do={
+  :local Name [ :tostr $1 ];
+  :global EitherOr;
+  :global LogPrint;
+  :global SymbolsExtra;
+  :local Symbols ({
+    "abacus"="\F0\9F\A7\AE";
+    "alarm-clock"="\E2\8F\B0";
+    "arrow-down"="\E2\AC\87";
+    "arrow-up"="\E2\AC\86";
+    "calendar"="\F0\9F\93\85";
+    "card-file-box"="\F0\9F\97\83";
+    "chart-decreasing"="\F0\9F\93\89";
+    "chart-increasing"="\F0\9F\93\88";
+    "cloud"="\E2\98\81";
+    "cross-mark"="\E2\9D\8C";
+    "earth"="\F0\9F\8C\8D";
+    "fire"="\F0\9F\94\A5";
+    "floppy-disk"="\F0\9F\92\BE";
+    "gear"="\E2\9A\99";
+    "heart"="\E2\99\A5";
+    "high-voltage-sign"="\E2\9A\A1";
+    "incoming-envelope"="\F0\9F\93\A8";
+    "information"="\E2\84\B9";
+    "large-orange-circle"="\F0\9F\9F\A0";
+    "large-red-circle"="\F0\9F\94\B4";
+    "link"="\F0\9F\94\97";
+    "lock-with-ink-pen"="\F0\9F\94\8F";
+    "memo"="\F0\9F\93\9D";
+    "mobile-phone"="\F0\9F\93\B1";
+    "pushpin"="\F0\9F\93\8C";
+    "scissors"="\E2\9C\82";
+    "scroll"="\F0\9F\93\9C";
+    "smiley-partying-face"="\F0\9F\A5\B3";
+    "smiley-smiling-face"="\E2\98\BA";
+    "smiley-winking-face-with-tongue"="\F0\9F\98\9C";
+    "sparkles"="\E2\9C\A8";
+    "speech-balloon"="\F0\9F\92\AC";
+    "star"="\E2\AD\90";
+    "warning-sign"="\E2\9A\A0";
+    "white-heavy-check-mark"="\E2\9C\85"
+  }, $SymbolsExtra);
+  :local Magic [ :pick [ /system/clock/get date ] 4 10 ];
+  :local Special {
+    "information-04-01"="\F0\9F\9A\BB";
+    "large-orange-circle-04-01"="\F0\9F\8D\8A";
+    "large-orange-circle-10-31"="\F0\9F\8E\83";
+    "large-red-circle-04-01"="\F0\9F\8D\92" };
+  :if ([ :len ($Symbols->$Name) ] = 0) do={
+    $LogPrint warning $0 ("No symbol available for name '" . $Name . "'!");
+    :return "";
+  }
+  :return ([ $EitherOr ($Special->($Name . $Magic)) ($Symbols->$Name) ] . "\EF\B8\8F");
+}
+:set SymbolForNotification do={
+  :global NotificationsWithSymbols;
+  :global SymbolByUnicodeName;
+  :global IfThenElse;
+  :if ($NotificationsWithSymbols != true) do={:return [ $IfThenElse ([ :len $2 ] > 0) ([ :tostr $2 ] . " ") "" ]}
+  :local Return "";
+  :foreach Symbol in=[ :toarray $1 ] do={:set Return ($Return . [ $SymbolByUnicodeName $Symbol ])}
+  :return ($Return . " ");
+}
+:set Unix2Dos do={:return [ :tocrlf [ :tostr $1 ] ]}
+:set UrlEncode do={
+  :local Input [ :tostr $1 ];
+  :if ([ :len $Input ] = 0) do={:return ""}
+  :local Return "";
+  :local Chars ("\n\r !\"#\$%&'()*+,:;<=>?@[\\]^`{|}~");
+  :local Subs { "%0A"; "%0D"; "%20"; "%21"; "%22"; "%23"; "%24"; "%25"; "%26"; "%27";
+         "%28"; "%29"; "%2A"; "%2B"; "%2C"; "%3A"; "%3B"; "%3C"; "%3D"; "%3E"; "%3F";
+         "%40"; "%5B"; "%5C"; "%5D"; "%5E"; "%60"; "%7B"; "%7C"; "%7D"; "%7E" };
+  :for I from=0 to=([ :len $Input ] - 1) do={
+    :local Char [ :pick $Input $I ];
+    :local Replace [ :find $Chars $Char ];
+    :if ([ :typeof $Replace ] = "num") do={:set Char ($Subs->$Replace)}
+    :set Return ($Return . $Char);
+  }
+  :return $Return;
+}
+:set ValidateSyntax do={
+  :local Code [ :tostr $1 ];
+  :global LogPrint;
+  :onerror Err {
+    [ :parse (":local Validate do={\n" . $Code . "\n}") ];
+  } do={
+    $LogPrint debug $0 ("Valdation failed: " . $Err);
+    :return false;
+  }
+  :return true;
+}
+:set IsDNSResolving do={
+  :do {:resolve "api.telegram.org"} on-error={:return false}
+  :return true;
+}
+:set IsFullyConnected do={
+  :global IsDNSResolving;
+  :global IsTimeSync;
+  :if ([ $IsDNSResolving ] = false) do={:return false};
+  :if ([ $IsTimeSync ] = false) do={:return false};
+  :return true;
+}
+:set IsTimeSync do={
+  :global IsTimeSyncCached;
+  :global IsTimeSyncResetNtp;
+  :if ($IsTimeSyncCached = true) do={:return true}
+  :if ([ /system/ntp/client/get enabled ] = true) do={
+    :if ([ /system/ntp/client/get status ] = "synchronized") do={
+      :set IsTimeSyncCached true;
+      :return true;
+    }
+    :local Uptime [ /system/resource/get uptime ];
+    :if ([ :typeof $IsTimeSyncResetNtp ] = "nothing") do={
+      :set IsTimeSyncResetNtp $Uptime;
+    }
+    :if ($Uptime - $IsTimeSyncResetNtp < 3m) do={:return false}
+    :log warning "$0 The ntp client is configured, but did not sync.";
+    :set IsTimeSyncResetNtp $Uptime;
+    /system/ntp/client/set enabled=no;
+    :delay 20ms;
+    /system/ntp/client/set enabled=yes;
+    :return false;
+  }
+  :if ([ /system/license/get ]->"level" = "free" || \
+       [ /system/resource/get ]->"board-name" = "x86") do={
+    :log debug "$0 No ntp client configured, relying on RTC for CHR free license and x86.";
+    :return true;
+  }
+  :if ([ /ip/cloud/get update-time ] = true) do={
+    :if ([ :typeof [ /ip/cloud/get public-address ] ] = "ip") do={
+      :set IsTimeSyncCached true;
+      :return true;
+    }
+    :return false;
+  }
+  :log debug "$0 No time source configured! Returning gracefully...";
+  :return true;
+}
+
+# read parts of a large file
+:set FetchLargeFiles do={
+  :local ScriptName [:tostr $1];
+  :local fetchUrl [:tostr $2];
+  :local CheckCert [:tostr $3];
+  :local fetchMode [:tostr $4];
+  :local fetchMethod [:tostr $5];
+  :local FetchString "undefined";
+  :global CleanName;
+  :global FetchUserAgentStr;
+  :global GetRandom20CharAlNum;
+  :global IfThenElse;
+  :global LogPrint;
+  :global MkDir;
+  :global RmDir;
+  :global RmFile;
+  :global WaitForFile;
+
+  :set CheckCert [ $IfThenElse ($CheckCert = "false") "no" "yes-without-crl" ];
+  #:set fetchMode [ $IfThenElse ($fetchMode != "false") [:tostr ("mode=" . $fetchMode)] ];
+  #:set fetchMethod [$IfThenElse ($fetchMethod != "false") [:tostr ("http-method=\"" . $fetchMethod . "\"") ]];
+  
+  :local DirName ("tmpfs/" . [ $CleanName $ScriptName ]);
+  :if ([ $MkDir $DirName ] = false) do={
+    $LogPrint error $0 ("Failed creating directory!");
+    :return false;
+  }
+  :local FileName ($DirName . "/" . [ $CleanName $0 ] . "-" . [ $GetRandom20CharAlNum ]);
+  #/tool/fetch check-certificate=$CheckCert $Url dst-path=$FileName http-header-field=({ [ $FetchUserAgentStr $ScriptName ] }) as-value;
+  #:log warning ("\$fUrl =" . $2 . ({[ $fMode ]}));
+    :onerror Err {
+      :set FetchString ("/tool/fetch " . \
+      [$IfThenElse ([$fetchMethod] != "false") ("http-method=\"" . $fetchMethod . "\" ")] . \
+      "check-certificate=" . $CheckCert . " url=(\"" . $fetchUrl . "\") " . \
+      [$IfThenElse ([$fetchMode] != "false") ("mode=" . $fetchMode . " ")] . \
+      http-header-field=({ [ $FetchUserAgentStr $ScriptName ] }) as-value);
+      :log warning ("\$FetchSring = " . $FetchString);
+      :put [$FetchString];
+    #:put [/tool/fetch $fetchMode check-certificate=$CheckCert url=($fetchUrl) dst-path=($FileName) http-header-field=({ [ $FetchUserAgentStr $ScriptName ] }) as-value];
+  } do={
+    :if ([ $WaitForFile $FileName 500ms ] = true) do={
+      $RmFile $FileName;
+    }
+    $LogPrint debug $0 ("Failed downloading from " . $fetchUrl . " - " . $Err);
+    $RmDir $DirName;
+    :return false;
+  }
+  :if ([ $WaitForFile $FileName 5s ] = false) do={
+    $LogPrint debug $0 ("The file downloaded from " . $fetchUrl . " did not show up.");
+    :return false;
+  }
+  :local FileSize [ /file/get $FileName size ];
+  :local Return "";
+  :local VarSize 0;
+  :while ($VarSize != $FileSize) do={
+    :set Return ($Return . ([ /file/read offset=$VarSize chunk-size=32768 file=$FileName as-value ]->"data"));
+    :set FileSize [ /file/get $FileName size ];
+    :set VarSize [ :len $Return ];
+    :if ($VarSize > $FileSize) do={
+      :delay 100ms;
+    }
+  }
+  $RmDir $DirName;
+  :return $Return;
+}
+
+# Global Environment clear. Use: "$GlobalEnvRemove True;".
+:set GlobalEnvRemove do={
+  :local doRmEnv [ :tostr $1 ];
+  :global GlobalScriptsUrl;
+  :local ScriptsUrl;
+  :global LogPrint;
+  :if ($doRmEnv != "True") do={
+     $LogPrint error "GlobalEnvRemove" ("This script is needed \"True\" for confirm to remove global environment variables!");
+     :return [];
+     :error true;  
+  } else={
+     :set ScriptsUrl $GlobalScriptsUrl;
+     :delay 5;
+     :foreach var in=[/system/script/environment print as-value] do={/system/script/environment remove ($var->".id")}
+     :delay 5;
+     :global GlobalScriptsUrl $ScriptsUrl;
+     $LogPrint warning "GlobalEnvRemove" ("Done!");
+     :return [];
+     :error false;
+  }
+}
+
+# Add and fix global scheduler
+:put "Add or/and fix Global scheduler...";
+:local OnEvent "/system/script { run AM-GlobalConfig; run AM-GlobalFunc; }";
+:local Comment "Setup Global Environment for scripts";
+:if ([ :len [ /system/scheduler/find where name="AM-SetGlobalEnv" ] ] = 0) do={
+  /system/scheduler/add name="AM-SetGlobalEnv" comment=$Comment on-event=$OnEvent start-time=startup;
+}
+:if ([ /system/scheduler/get "AM-SetGlobalEnv" comment ] != $Comment) do={
+  /system/scheduler/set "AM-SetGlobalEnv" comment=$Comment;
+}
+:if ([ /system/scheduler/get "AM-SetGlobalEnv" on-event ] != $OnEvent) do={
+  /system/scheduler/set "AM-SetGlobalEnv" on-event=$OnEvent;
+}
+# Add Update Script scheduler
+:put "Add or/and fix Update Script scheduler";
+:set OnEvent ":global ScriptInstallUpdate; \$ScriptInstallUpdate;";
+:set Comment "Every Day Check For Update Scripts";
+:if ([ :len [ /system/scheduler/find where name="AM-ScriptInstallUpdate" ] ] = 0) do={
+/system/scheduler/add name="AM-ScriptInstallUpdate" comment=$Comment start-time=startup interval=1d on-event=$OnEvent;
+}
+
+#:local Resource [ /system/resource/get ];
+#:log info ($ScriptName . " Loaded on " . $Resource->"board-name" . " with RouterOS " . $Resource->"version" . " and Architecture " .".");
+
+:onerror Err {
+  :global GlobalConfReady;
+  :retry { :if ($GlobalConfReady != true) do={ :error ("Global configs not ready."); }; } delay=100ms max=5;
+
+# signal we are ready
+:set GlobalFuncReady true;
+:set ExitOK true;
+:log warning ("This script $ScriptName Loaded on " . $devBoardName . "\nRouterOS: " . $devCurVersion . "\nArchitecture: " . $devCurArch . "\nIdentity: " . $Identity);
+} do={:global ExitOnError; $ExitOnError $0 $Err;}
+
+#} do={:global ExitError; $ExitError $ExitOK [ :jobname ] $Err}
+# log the successful load of the script, or an error if it failed
+#:if ([$GlobalFuncReady] = true && [$GlobalConfReady] = true) do={
diff --git a/html/AM-Init-Script.rsc b/html/AM-Init-Script.rsc
new file mode 100644 (file)
index 0000000..64ff49f
--- /dev/null
@@ -0,0 +1,98 @@
+#!rsc by Vados\r
+# RouterOS script: AM-Init\r
+# Script comment: Initial Script for setup routeros scrypt system\r
+# Copyright (c) 2005-2026 Vados <vados@vados.ru>\r
+#\r
+#\r
+# requires RouterOS, version=7.19\r
+# requires device-mode, fetch\r
+#\r
+# Additional commands:\r
+# /system/script/set source=[ :tocrlf [ get $ScriptName source ] ] $ScriptName;\r
+#\r
+#:global GlobalEnvRemove true; /system/script {run Amster-GlobalEnvRemove; };\r
+#:delay 1s;\r
+#:global ReloadGlobal true; \r
+\r
+:global GlobalScriptsUrl "https://ros.vados.ru/";\r
+:global defOwner "vados";\r
+:global InitFirstRun;\r
+\r
+:set InitFirstRun do={\r
+    :global GlobalScriptsUrl;\r
+    :global defOwner;\r
+    #:global ParseKeyValueStore;\r
+    #:local AmUpdCRLF true;\r
+    #:local IfThenElse;\r
+    #:set IfThenElse do={:if ([ :tostr $1 ] = "true" || [ :tobool $1 ] = true) do={:return $2}; :return $3}\r
+    #/certificate/settings/set builtin-trust-store=fetch;\r
+    #/tool/fetch "$scriptsUrl/certs/Root-YE.pem" dst-path="root-ye.pem";\r
+    #/certificate/import file-name="root-ye.pem" passphrase="";\r
+    # For basic verification we rename the certificate and print it by fingerprint. Make sure exactly this one certificate ("Root-YE") is shown.\r
+    #/certificate/set name="Root-YE" [ find where common-name="Root YE" ];\r
+    #/certificate/print proplist=name,fingerprint where fingerprint="e14ffcad5b0025731006caa43a121a22d8e9700f4fb9cf852f02a708aa5d5666";\r
+    #:log info "\nGlobalScriptsUrl = $GlobalScriptsUrl";\r
+    :local Scripts {\r
+        "Amster-GlobalConfig"\r
+        "Amster-GlobalFunc";\r
+#        "Amster-SFTP-backup"\r
+#        "Amster-BkpEmail-and-upd";\r
+    };\r
+#    /system/script/set owner=($ScriptVal->"name") \\r
+#    source=[ $IfThenElse ($ScriptUpdatesCRLF = true) $SourceCRLF $SourceNew ] $Script;\r
+    :foreach Script in=$Scripts do={\r
+        :put "Installing $Script...";\r
+        /system/script/remove [ find where name=$Script ];\r
+        /system/script/add name=$Script owner=$defOwner source=([ /tool/fetch ("$GlobalScriptsUrl" . $Script . ".rsc") output=user as-value ]->"data");\r
+        /system/script/set source=[ :tocrlf [ get $Script source ] ] $Script;\r
+    };\r
+    /system/script { run Amster-GlobalConfig; run Amster-GlobalFunc; }\r
+}\r
+:global InitUpdRun;\r
+:set InitUpdRun do={\r
+    #:local UpdScript [ :tostr $1 ];\r
+    #:global ParseKeyValueStore;\r
+    :global GlobalScriptsUrl;\r
+    :global defOwner;\r
+    :local Scripts {\r
+#        "Amster-GlobalConfig"\r
+#        "Amster-GlobalFunc"\r
+#        "Amster-GlobalRemove"\r
+        "Amster-backup-SFTP"\r
+        "Amster-SFTP-backup"\r
+        "Amster-BkpEmail-and-upd"\r
+        "Amster-TG-Bot"\r
+        "Amster-TG-Notification";\r
+        };\r
+    :foreach Script in=$Scripts do={\r
+        :put "updating $Script...";\r
+        /system/script/remove [ find where name=$Script ];\r
+        /system/script/add name=$Script owner=$defOwner source=([ /tool/fetch ("$GlobalScriptsUrl" . $Script . ".rsc") output=user as-value ]->"data");\r
+        /system/script/set source=[ :tocrlf [ get $Script source ] ] $Script;\r
+    };\r
+#    /system/script/add name=$Script owner=$AmOwner source=([ /tool/fetch check-certificate=yes-without-crl ("$GlobalScriptsUrl" . $Script . ".rsc") output=user as-value ]->"data"); };\r
+}\r
+\r
+$InitFirstRun;\r
+#$InitUpdRun;\r
+\r
+#$setUpdExist;\r
+#:global setUpdExist;\r
+#:global ParseKeyValueStore;\r
+#:set setUpdExist do={\r
+#:do {\r
+#:global ParseKeyValueStore;\r
+#  :foreach Script in=[ /system/script/find where source~"^#!rsc by Vados\r?\n#comment=*" ] do={\r
+#    :local ScriptVal [ /system/script/get $Script ];\r
+#    :local ScriptInfo [ $ParseKeyValueStore ($ScriptVal->"#comment") ];\r
+#    :local scriptName ($ScriptVal->"name");\r
+#:log warning ("\n\$scriptName = ". $scriptName);\r
+#: log warning ("\n\$ScriptInfo = ". $ScriptInfo);\r
+#  }\r
+#} on-error={}\r
+#}\r
+#$name;$owner;$policy;$dont-require-permissions;$last-started;$run-count;$source;$invalid;$comment;$.id;$.nextid;$.dead;$.about;\r
+#;(evl / (evl /docommand=;(evl / (evl /foreachcounter=$Script;do=;(evl / (evl /localname=$ScriptVal;value=(evl (evl /system/script/getnumber=$Script))) (evl /localname=$ScriptInfo;value=(evl (<%% $ParseKeyValueStore (> $ParseKeyValueStore);(-> $ScriptVal comment)))) (evl /localname=$scriptName) (evl /setname=$scriptName;value=(-> #$ScriptVal name)) (evl /log/errormessage=(. \r
+# $ ScriptInfo =  $ScriptInfo)));in=(evl (evl /system/script/findwhere=$name;$owner;$policy;$dont-require-permissions;$last-started;$run-count;$source;$invalid;$comment;$.id;$.nextid;$.dead;$.about;(~ $source (. ^#!rsc by Vados \r
+# ? \r
+#));5))));on-error=;(evl /)))\r
diff --git a/html/AM-LogForward.rsc b/html/AM-LogForward.rsc
new file mode 100644 (file)
index 0000000..6fccfea
--- /dev/null
@@ -0,0 +1,105 @@
+#!rsc by Vados
+# RouterOS script: AM-LogForward
+# Script comment: Forward log messages via notification
+# Copyright (c) 2005-2026 Vados <vados@vados.ru>
+#
+#
+#
+# requires RouterOS, version=7.19
+#
+:local ExitOK false;
+:onerror Err {
+  :global GlobalConfReady; :global GlobalFuncReady;
+  :retry { :if ($GlobalConfReady != true || $GlobalFuncReady != true) \
+      do={ :error ("Global configs or functions not ready."); }; } delay=500ms max=50;
+  :local ScriptName [ :jobname ];
+  
+  :global Identity;
+  :global LogForwardFilter;
+  :global LogForwardFilterMessage;
+  :global LogForwardInclude;
+  :global LogForwardIncludeMessage;
+  :global LogForwardLast;
+  :global LogForwardRateLimit;
+  :global EitherOr;
+  :global HexToNum;
+  :global IfThenElse;
+  :global LogForwardFilterLogForwarding;
+  :global LogPrint;
+  :global MAX;
+  :global ScriptLock;
+  :global SendNotification2;
+  :global SymbolForNotification;
+
+  :if ([ $ScriptLock $ScriptName ] = false) do={
+    :set ExitOK true;
+    :error false;
+  }
+  :if ([ :typeof $LogForwardRateLimit ] = "nothing") do={
+    :set LogForwardRateLimit 0;
+  }
+  :if ($LogForwardRateLimit > 30) do={
+    :set LogForwardRateLimit ($LogForwardRateLimit - 1);
+    $LogPrint info $ScriptName ("Rate limit in action, not forwarding logs, if any!");
+    :set ExitOK true;
+    :error false;
+  }
+  :local Count 0;
+  :local Duplicates false;
+  :local Last [ $IfThenElse ([ :len $LogForwardLast ] > 0) [ $HexToNum $LogForwardLast ] -1 ];
+  :local Messages "";
+  :local Warning false;
+  :local MessageVal;
+  :local MessageDups ({});
+  :set LogForwardFilter [ $EitherOr $LogForwardFilter [] ];
+  :set LogForwardFilterMessage [ $EitherOr $LogForwardFilterMessage [] ];
+  :set LogForwardInclude [ $EitherOr $LogForwardInclude [] ];
+  :set LogForwardIncludeMessage [ $EitherOr $LogForwardIncludeMessage [] ];
+  :local LogAll [ /log/find ];
+  :local MaxId ($LogAll->([ :len $LogAll ] - 1));
+  :local MaxNum [ $HexToNum $MaxId ];
+  :local LogForwardFilterLogForwardingCached [ $EitherOr [ $LogForwardFilterLogForwarding ] ("\$^") ];
+  :foreach Message in=[ /log/find where (!(message="") and \
+      !(message~$LogForwardFilterLogForwardingCached) and \
+      !(topics~$LogForwardFilter) and !(message~$LogForwardFilterMessage)) or \
+      topics~$LogForwardInclude or message~$LogForwardIncludeMessage ] do={
+    :set MessageVal [ /log/get $Message ];
+    :local Bullet "information";
+    :local Current [ $HexToNum ($MessageVal->".id") ];
+    :if ($Last < $Current && $Current <= $MaxNum) do={
+      :local DupCount ($MessageDups->($MessageVal->"message"));
+      :if ($MessageVal->"topics" ~ "(warning)") do={
+        :set Warning true;
+        :set Bullet "large-orange-circle";
+      }
+      :if ($MessageVal->"topics" ~ "(emergency|alert|critical|error)") do={
+        :set Warning true;
+        :set Bullet "large-red-circle";
+      }
+      :if ($DupCount < 3) do={
+        :set Messages ($Messages . "\n" . [ $SymbolForNotification $Bullet ] . \
+          $MessageVal->"time" . " " . [ :tostr ($MessageVal->"topics") ] . " " . $MessageVal->"message");
+      } else={
+        :set Duplicates true;
+      }
+      :set ($MessageDups->($MessageVal->"message")) ($DupCount + 1);
+      :set Count ($Count + 1);
+    }
+  }
+  :if ($Count > 0) do={
+    :set LogForwardRateLimit ($LogForwardRateLimit + 10);
+    $SendNotification2 ({ origin=$ScriptName; \
+      subject=([ $SymbolForNotification ("memo" . [ $IfThenElse ($Warning = true) ",warning-sign" ]) ] . \
+        "Log Forwarding"); \
+      message=("The log on " . $Identity . " contains " . [ $IfThenElse ($Count = 1) "this message" \
+        ("these " . $Count . " messages") ] . " after " . [ /system/resource/get uptime ] . " uptime." . \
+        [ $IfThenElse ($Duplicates = true) (" Multi-repeated messages have been skipped.") ] . \
+        [ $IfThenElse ($LogForwardRateLimit > 30) ("\nRate limit in action, delaying forwarding.") ] . \
+        "\n" . $Messages) });
+  } else={
+    :set LogForwardRateLimit [ $MAX 0 ($LogForwardRateLimit - 1) ];
+  }
+  :set LogForwardLast $MaxId;
+} do={
+  :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+}
diff --git a/html/AM-SFTP-backup.rsc b/html/AM-SFTP-backup.rsc
new file mode 100644 (file)
index 0000000..ec8d6d2
--- /dev/null
@@ -0,0 +1,327 @@
+#!rsc by Vados\r
+# RouterOS script: AM-SFTP-backup\r
+# Script comment: BackUp via /tool fetch SFTP\r
+# Copyright (c) 2005-2026 Vados <vados@vados.ru>\r
+#\r
+#\r
+#\r
+# requires RouterOS, version=7.21\r
+# requires device-mode, fetch, scheduler\r
+#\r
+# Based on:\r
+# https://forum.mikrotik.com/viewtopic.php?t=159432\r
+# https://forum.mikrotik.com/viewtopic.php?p=858564#p858564\r
+#\r
+### Info Log Action\r
+# $stage (string) selects action text\r
+# $msg (string) additional message, usually the backup stage or filename\r
+# $error (bool) (optional) creates error log instead of info if 'true'\r
+:local ExitOK false;\r
+:onerror Err {\r
+  :global GlobalConfReady; :global GlobalFuncReady;\r
+  :retry { :if ($GlobalConfReady != true || $GlobalFuncReady != true) \\r
+      do={ :error ("Global configs or functions not ready."); }; } delay=500ms max=50;\r
+  :local ScriptName [ :jobname ];\r
+\r
+:global Identity;\r
+:global checkRunJob;\r
+:global LogPrint;\r
+:global RandomDelay;\r
+:global MkDir;\r
+:global RmDir;\r
+:global RmFile;\r
+:global FileGet;\r
+:global WaitForFile;\r
+:global MiniDateTimeStamp;\r
+:global OSVersion;\r
+:global devBoardName;\r
+:global BackupRandomDelay;\r
+:global BackupUploadUrl;\r
+:global BackupUploadUser;\r
+:global BackupUploadPass;\r
+:global BackupRmLocal;\r
+:global BackupSendBinary;\r
+:global BackupLocalRoot;\r
+:global BackupEncrypt;\r
+:global BackupPassword;\r
+:global BackupSens;\r
+:global BackupSendGlobalConfig;\r
+:global BackupUser;\r
+:global BackupLicense;\r
+:global BackupSshKeys;\r
+:global BackupCerts;\r
+:global BackupCertsPasswd;\r
+:global BackupUserMan;\r
+:global BackupDude;\r
+:global BackupUserList;\r
+:global SendTelegram;\r
+:global SendNotification;\r
+### End Configuration\r
+  \r
+  :if ([ :len [ /system/scheduler/find where name="BackupEveryDaySFTP" ] ] = 0) do={\r
+    $LogPrint warning $ScriptName ("SystemScheduler NOT SET!");\r
+    /system/scheduler/add name=$ScriptName on-event="/system/script { run $ScriptName; }" comment="Backup Every Day on SFTP" interval=1w policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon; start-time=startup; \r
+  }\r
+\r
+  :if ([ $checkRunJob $ScriptName ] = false && $BackupRandomDelay > 0) do={\r
+    $RandomDelay $BackupRandomDelay;\r
+  }\r
+\r
+:local dateTimeStampFix [$MiniDateTimeStamp];\r
+:local bkpDirName ($Identity . "-" . $dateTimeStampFix);\r
+:local BkpPath;\r
+:local FilePref ($Identity . "-");\r
+\r
+:if ($BackupLocalRoot = "") do={\r
+    $LogPrint warning $ScriptName ("\n\$BackupLocalRoot is not set! I set \$BackupRmLocal to true!" . $Err);\r
+    :set $BackupRmLocal true;\r
+:do {\r
+$MkDir ($bkpDirName);\r
+:set BkpPath $bkpDirName;\r
+   } on-error={$LogPrint error $ScriptName ("ERROR Create Directory $BkpPath" . $Err);}\r
+} else={\r
+:do {\r
+$MkDir ($BackupLocalRoot . "/" . $bkpDirName);\r
+:set BkpPath ($BackupLocalRoot . "/" . $bkpDirName);\r
+   } on-error={$LogPrint error $ScriptName ("ERROR Create Directories $BkpPath" . $Err);}\r
+}\r
+:local locFilePref ($BkpPath . "/" . $FilePref);\r
+\r
+$LogPrint warning $ScriptName ("\$locFilePref = $locFilePref" . $Err);\r
+\r
+:local remFilePref ($FilePref . $dateTimeStampFix . "-");\r
+#($bkpDirName . "/" . $BkpFilePrefix);\r
+#:if ($SFTPpath != "") do={\r
+#  :set remFilePref ($SFTPpath . "/" . $remFilePref);\r
+#} else={:set remFilePref ("/" . $remFilePref)}\r
+\r
+$LogPrint warning $ScriptName ("\$remFilePref = $remFilePref");\r
+\r
+### Process local filename to create remote filename\r
+### Return array for file array\r
+## Strips path separator '/' from local file name and replaces with '_'\r
+# 'lfile' (string) the local filename\r
+# 'lpref' (string) the local prefix to strip from start of lfile (if present)\r
+# 'rpref' (string) remote prefix to prepend to remote filename\r
+# 'clear' (bool) whether to delete local file after uploading\r
+:local dofnames do={\r
+  :local rfile "";\r
+  :local rfilef "";\r
+  # Strip Local Prefix if present\r
+  if ([:find $lfile $lpref -1] = 0) do={\r
+    :set rfile [:pick $lfile [:len $lpref] [:len $lfile]];\r
+  } else={\r
+    :set rfile $lfile;\r
+  }\r
+  $LogPrint warning $ScriptName ("\n\$rfile = $rfile");\r
+  # Convert / to _\r
+  :for i from=0 to=([:len $rfile] - 1) do={\r
+    :local char [:pick $rfile $i];\r
+    :if ($char = "/") do={\r
+      :set $char "_";\r
+    }\r
+    :set rfilef ($rfilef . $char);\r
+    $LogPrint warning $ScriptName ("\n\$rfilef = " . $rfilef);\r
+    $LogPrint warning $ScriptName ("\n\$char = " . $char);\r
+  }\r
+  # Prepend Remote Prefix\r
+  :set rfile ($rpref . $rfilef);\r
+  $LogPrint warning $ScriptName ("\n\$rfile = " . $rfile);\r
+  $LogPrint warning $ScriptName ("\n\$rpref = " . $rpref);\r
+  $LogPrint warning $ScriptName ("\n\$rfilef = " . $rfilef);\r
+  $LogPrint warning $ScriptName ("\n\$lfile = " . $lfile);\r
+  $LogPrint warning $ScriptName ("\n\$clear = " . $clear);\r
+  # Return array\r
+  :return {lfile=$lfile; rfile=$rfile; clear=$clear};\r
+}\r
+\r
+### Delete Local File(s)\r
+# $lfile (string) local file to be deleted\r
+:local dodelete do={\r
+  :global BackupRmLocal;\r
+  :global RmFile;\r
+  :global LogPrint;\r
+  if ($BackupRmLocal = true) do={\r
+    :do {\r
+      $RmFile $lfile;\r
+    } on-error={\r
+      $LogPrint error $ScriptName ("Error remove file " . $lfile . "." . $Err);\r
+    }\r
+#  if ([:len [/file find where name="$lfile"]] > 0) do={\r
+#    /file remove [find where name="$lfile"];\r
+  }\r
+}\r
+\r
+:local filesa [:toarray ""];\r
+:local cfilename "";\r
+:local lfilename "";\r
+:local rfilename "";\r
+\r
+### Binary Backup\r
+if ($BackupSendBinary = true) do={\r
+  :set cfilename ($locFilePref . "backup");\r
+  :set lfilename ($cfilename . ".backup");\r
+  if ($BackupEncrypt = false) do={\r
+    :do {\r
+      /system backup save dont-encrypt=yes name=$cfilename;\r
+    } on-error={$LogPrint error $ScriptName ("Binary backup save " .$cfilename . " dont-encrypted failed" . $Err);}\r
+  } else={\r
+    :do {\r
+      /system backup save encryption=aes-sha256 name=$cfilename password=$BackupPassword;\r
+    } on-error={$LogPrint error $ScriptName ("Binary backup save " .$cfilename . " encrypted failed" . $Err);}\r
+  }\r
+  :set ($filesa->([:len $filesa])) [$dofnames lfile=$lfilename lpref=$locFilePref rpref=$remFilePref clear=$BackupRmLocal];\r
+}\r
+\r
+### Generic Export\r
+if ($BackupSendGlobalConfig = true) do={\r
+  :set cfilename ($locFilePref . "export");\r
+  :set lfilename ($cfilename . ".rsc");\r
+  if (($OSVersion = "6" and $BackupSens = true) or ($OSVersion = "7" and $BackupSens = false)) do={\r
+    :do {\r
+      /export compact file=$cfilename;\r
+    } on-error={$LogPrint error $ScriptName ("Export Global compact " .$cfilename . " failed" . $Err);}\r
+  } else={\r
+    if ($OSVersion = "6") do={\r
+      $LogPrint info $ScriptName ("Start Global compact if OSVersion = 6 (hide-sensitive)");\r
+      :do {\r
+        /export compact hide-sensitive file=$cfilename;\r
+      } on-error={$LogPrint error $ScriptName ("Export Global compact if OSVersion = 6 (hide-sensitive) " .$cfilename . " failed" . $Err);}\r
+    } else={\r
+      $LogPrint info $ScriptName ("Start Global compact");\r
+      :do {\r
+        if ($BackupSens = true) do={\r
+        /export compact hide-sensitive file=$cfilename;\r
+        } else={\r
+          /export compact show-sensitive file=$cfilename;\r
+        }\r
+      } on-error={$LogPrint error $ScriptName ("Export Global compact (\$BackupSens = " . $BackupSens . ") " .$cfilename . " failed" . $Err);}\r
+    }\r
+  }\r
+  :set ($filesa->([:len $filesa])) [$dofnames lfile=$lfilename lpref=$locFilePref rpref=$remFilePref clear=$BackupRmLocal];\r
+}\r
+\r
+### User Export\r
+if ($BackupUser = true) do={\r
+  :set cfilename ($locFilePref . "user");\r
+  :set lfilename ($cfilename . ".rsc");\r
+  if (($OSVersion = "6" and $BackupSens = true) or ($OSVersion = "7" and $BackupSens = false)) do={\r
+    :do {\r
+      /user export compact file=$cfilename;\r
+      } on-error={$LogPrint error $ScriptName ("User Export " . $cfilename . " failed" . $Err);}\r
+  } else={\r
+    if ($OSVersion = "6") do={\r
+      $LogPrint info $ScriptName ("User Export (hide-sensitive)" . $lfilename);\r
+      :do {\r
+        /user export compact hide-sensitive file=$cfilename;\r
+      } on-error={$LogPrint error $ScriptName ("User Export (hide-sensitive) " .$cfilename . " failed" . $Err);}\r
+    } else={\r
+      $LogPrint info $ScriptName ("User Export (show-sensitive) " . $lfilename);\r
+      :do {\r
+        /user export compact show-sensitive file=$cfilename;\r
+      } on-error={$LogPrint error $ScriptName ("User Export (show-sensitive) " .$cfilename . " failed" . $Err);}\r
+    }\r
+  }\r
+  :set ($filesa->([:len $filesa])) [$dofnames lfile=$lfilename lpref=$locFilePref rpref=$remFilePref clear=$BackupRmLocal];\r
+}\r
+\r
+### License Export\r
+if ($BackupLicense = true and $devBoardName != "CHR") do={\r
+  :set lfilename ([/system license get software-id] . ".key");\r
+  :set rfilename ($remFilePref . "license.key");\r
+  :do {\r
+    /system license output;\r
+  } on-error={$LogPrint error $ScriptName ("Backup " . $lfilename . " failed" . $Err);}\r
+  :set ($filesa->([:len $filesa])) {lfile=$lfilename; rfile=$rfilename; clear=$BackupRmLocal};\r
+}\r
+\r
+### SSH Keys\r
+if ($BackupSshKeys = true) do={\r
+  :set cfilename ($locFilePref . "host-key");\r
+  :do {\r
+    /ip ssh export-host-key key-file-prefix=$cfilename;\r
+  } on-error={$LogPrint error $ScriptName ("Backup SSH Keys " . $cfilename . " failed" . $Err);}\r
+  :foreach lfile in=[/file find where name~"^$cfilename"] do={\r
+    :set ($filesa->([:len $filesa])) [$dofnames lfile=[/file get $lfile name] lpref=$locFilePref rpref=$remFilePref clear=$BackupRmLocal];\r
+  }\r
+}\r
+\r
+### Certificates\r
+if ($BackupCerts = true) do={\r
+  :foreach cert in=[/certificate find] do={\r
+    :local certname [/certificate get $cert name];\r
+    :local cfilename ($locFilePref . "cert-" . $certname);\r
+    :do {\r
+      /certificate export-certificate $cert file-name=$cfilename \\r
+                                      type=pkcs12 export-passphrase=$BackupCertsPasswd;\r
+    } on-error={$LogPrint error $ScriptName ("Backup Certificate " . $cfilename . " failed" . $Err);}\r
+    :set ($filesa->([:len $filesa])) [$dofnames lfile=($cfilename . ".p12") lpref=$locFilePref rpref=$remFilePref clear=$BackupRmLocal];\r
+  }\r
+}\r
+\r
+# User-Manager\r
+if ($BackupUserMan = true) do={\r
+  :set cfilename ($locFilePref . "user-manager");\r
+  :set lfilename ($cfilename . ".umb");\r
+  :do {\r
+    $dodelete lfile=$lfilename;\r
+  } on-error={$LogPrint error $ScriptName ("User-Manager " . $lfilename . " clear failed" . $Err);}\r
+  if ($OSVersion = "6") do={\r
+    :do {\r
+      /tool user-manager database save name=$cfilename;\r
+    } on-error={$LogPrint error $ScriptName ("Backup User-Manager " . $cfilename . " failed" . $Err);}\r
+  }\r
+  if ($OSVersion = "7") do={\r
+    :do {\r
+      /user-manager database save name=$cfilename;\r
+    } on-error={$LogPrint error $ScriptName ("Backup User-Manager " . $cfilename . " failed" . $Err);}\r
+  }\r
+  :set ($filesa->([:len $filesa])) [$dofnames lfile=$lfilename lpref=$locFilePref rpref=$remFilePref clear=$BackupRmLocal];\r
+}\r
+\r
+# The Dude\r
+if ($BackupDude = true) do={\r
+  :set lfilename ($locFilePref . "the-dude.db");\r
+  :do {\r
+    $dodelete lfile=$lfilename;\r
+  } on-error={$LogPrint error $ScriptName ("Dude " . $lfilename . " clear failed" . $Err);}\r
+  $dolog stage="create" msg=$logstage;\r
+  :do {\r
+    /dude export-db backup-file=$lfilename;\r
+  } on-error={$LogPrint error $ScriptName ("Backup Dude " . $lfilename . " failed" . $Err);}\r
+  :set ($filesa->([:len $filesa])) [$dofnames lfile=$lfilename lpref=$locFilePref rpref=$remFilePref clear=$BackupRmLocal];\r
+}\r
+\r
+# User File List\r
+if ([:len $BackupUserList] > 0) do={\r
+  :foreach lfile in=[:toarray $BackupUserList] do={\r
+    :set ($filesa->([:len $filesa])) [$dofnames lfile=$lfile lpref=$locFilePref rpref=$remFilePref clear=false];\r
+  }\r
+}\r
+\r
+# Process Files Array\r
+:local lfile "";\r
+:local rfile "";\r
+:local clear true;\r
+/delay 10s;\r
+:foreach a in=$filesa do={\r
+  :set lfile ($a->"lfile");\r
+  :set rfile ($a->"rfile");\r
+  :set clear ($a->"clear");\r
+  if ([:len [/file find where name="$lfile"]] > 0) do={\r
+    $LogPrint info $ScriptName ("Upload " . $lfile . " AS " . $rfile);\r
+    :do {\r
+      /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $rfile) \\r
+          user=$BackupUploadUser password=$BackupUploadPass src-path=$lfile;\r
+#      /tool fetch address=$SFTPsrv user=$SFTPusr password=$SFTPpasswd src-path=$lfile dst-path=$rfile upload=yes mode=sftp;\r
+    } on-error={$LogPrint error $ScriptName ($rfile . $Err);}\r
+    if ($clear = true) do={\r
+      $LogPrint info $ScriptName ("Delete " . $lfile)\r
+      :do {\r
+        :if ($BackupRmLocal = true) do={$RmFile $lfile}\r
+      } on-error={$LogPrint error $ScriptName ($lfile . $Err);}\r
+    }\r
+  }\r
+}\r
+:if (!$ExitError && $BackupRmLocal = true) do={$RmDir $BkpPath}\r
+} do={:global ExitError; $ExitError $ExitOK [ :jobname ] $Err}\r
diff --git a/html/AM-SshKeysImport.rsc b/html/AM-SshKeysImport.rsc
new file mode 100644 (file)
index 0000000..8c29f9c
--- /dev/null
@@ -0,0 +1,106 @@
+#!rsc by Vados
+# RouterOS script: AM-SshKeysImport
+# Script comment: Import ssh keys for public key authentication
+# Copyright (c) 2005-2026 Vados <vados@vados.ru>
+#
+# requires RouterOS, version=7.19
+#
+:local ExitOK false;
+:onerror Err {
+  :global GlobalConfReady; :global GlobalFuncReady;
+  :retry { :if ($GlobalConfReady != true || $GlobalFuncReady != true) \
+      do={ :error ("Global configs or functions not ready."); }; } delay=500ms max=50;
+  :local ScriptName [ :jobname ];
+
+:global SSHKeysImport;
+:global SSHKeysImportFile;
+
+# import single key passed as string
+:set SSHKeysImport do={ :onerror Err {
+  :local Key  [ :tostr $1 ];
+  :local User [ :tostr $2 ];
+  :global GetRandom20CharAlNum;
+  :global LogPrint;
+  :global MkDir;
+  :global RmDir;
+  :global WaitForFile;
+  :if ([ :len $Key ] = 0 || [ :len $User ] = 0) do={
+    $LogPrint warning $0 ("Missing argument(s), please pass key and user!");
+    :return false;
+  }
+  :if ([ :len [ /user/find where name=$User ] ] = 0) do={
+    $LogPrint warning $0 ("User '" . $User . "' does not exist.");
+    :return false;
+  }
+  :local KeyVal ([ :deserialize $Key delimiter=" " from=dsv options=dsv.plain ]->0);
+  :if (!($KeyVal->0 = "ssh-ed25519" || $KeyVal->0 = "ssh-rsa")) do={
+    $LogPrint warning $0 ("SSH key of type '" . $KeyVal->0 . "' is not supported.");
+    :return false;
+  }
+  :local FingerPrintMD5 [ :convert from=base64 transform=md5 to=hex ($KeyVal->1) ];
+  :local RegEx ("\\bmd5=" . $FingerPrintMD5 . "\\b");
+  :if ([ :len [ /user/ssh-keys/find where user=$User \
+       (key-owner~$RegEx or info~$RegEx) ] ] > 0) do={
+    $LogPrint warning $0 ("The ssh public key (MD5:" . $FingerPrintMD5 . \
+      ") is already available for user '" . $User . "'.");
+    :return false;
+  }
+  :if ([ $MkDir "tmpfs/ssh-keys-import" ] = false) do={
+    $LogPrint warning $0 ("Creating directory 'tmpfs/ssh-keys-import' failed!");
+    :return false;
+  }
+  :local FileName ("tmpfs/ssh-keys-import/key-" . [ $GetRandom20CharAlNum 6 ] . ".pub");
+  /file/add name=$FileName contents=($Key . ", md5=" . $FingerPrintMD5);
+  $WaitForFile $FileName;
+  :onerror Err {
+    /user/ssh-keys/import public-key-file=$FileName user=$User;
+    $LogPrint info $0 ("Imported ssh public key (" . $KeyVal->2 . ", " . $KeyVal->0 . ", " . \
+      "MD5:" . $FingerPrintMD5 . ") for user '" . $User . "'.");
+    $RmDir "tmpfs/ssh-keys-import";
+  } do={
+    $LogPrint warning $0 ("Failed importing key: " . $Err);
+    $RmDir "tmpfs/ssh-keys-import";
+    :return false;
+  }
+} do={
+  :global ExitOnError; $ExitOnError $0 $Err;
+} }
+
+# import keys from a file
+:set SSHKeysImportFile do={ :onerror Err {
+  :local FileName [ :tostr $1 ];
+  :local User     [ :tostr $2 ];
+  :global EitherOr;
+  :global FileExists;
+  :global LogPrint;
+  :global ParseKeyValueStore;
+  :global SSHKeysImport;
+  :if ([ :len $FileName ] = 0 || [ :len $User ] = 0) do={
+    $LogPrint warning $0 ("Missing argument(s), please pass file name and user!");
+    :return false;
+  }
+  :if ([ $FileExists $FileName ] = false) do={
+    $LogPrint warning $0 ("File '" . $FileName . "' does not exist.");
+    :return false;
+  }
+  :local Keys [ :tolf [ /file/get $FileName contents ] ];
+  :foreach KeyVal in=[ :deserialize $Keys delimiter=" " from=dsv options=dsv.plain ] do={
+    :local Continue false;
+    :if ($KeyVal->0 = "ssh-ed25519" || $KeyVal->0 = "ssh-rsa") do={
+      :if ([ $SSHKeysImport ($KeyVal->0 . " " . $KeyVal->1 . " " . $KeyVal->2) $User ] = false) do={
+        $LogPrint warning $0 ("Failed importing key for user '" . $User . "'.");
+      }
+      :set Continue true;
+    }
+    :if ($Continue = false && $KeyVal->0 = "#") do={
+      :set User [ $EitherOr ([ $ParseKeyValueStore ($KeyVal->1) ]->"user") $User ];
+      :set Continue true;
+    }
+    :if ($Continue = false && [ :len ($KeyVal->0) ] > 0) do={
+      $LogPrint warning $0 ("SSH key of type '" . $KeyVal->0 . "' is not supported.");
+    }
+  }
+} do={
+  :global ExitOnError; $ExitOnError $0 $Err;
+} }
+} do={:global ExitError; $ExitError $ExitOK [ :jobname ] $Err}
diff --git a/html/AM-backupSFTP.rsc b/html/AM-backupSFTP.rsc
new file mode 100644 (file)
index 0000000..d83eed8
--- /dev/null
@@ -0,0 +1,222 @@
+#!rsc by Vados\r
+# RouterOS script: AM-backupSFTP\r
+# Script comment: Create and upload backup and config file\r
+# Copyright (c) 2005-2026 Vados <vados@vados.ru>\r
+#\r
+#\r
+# provides: backup-script, order=50\r
+# requires RouterOS, version=7.17\r
+# requires device-mode, fetch\r
+:local ExitOK false;\r
+:onerror Err {\r
+  :global GlobalConfReady; :global GlobalFuncReady;\r
+  :retry { :if ($GlobalConfReady != true || $GlobalFuncReady != true) \\r
+      do={ :error ("Global configs or functions not ready."); }; } delay=500ms max=50;\r
+  :local ScriptName [ :jobname ];\r
+\r
+  :global Domain;\r
+  :global DeviceInfo;\r
+  :global IfThenElse;\r
+  :global Identity;\r
+  :global checkRunJob;\r
+  :global CleanName;\r
+  :global FormatLine;\r
+  :global HumanReadableNum;\r
+  :global LogPrint;\r
+  :global RandomDelay;\r
+  :global MkDir;\r
+  :global RmDir;\r
+  :global RmFile;\r
+  :global MiniDateTimeStamp;\r
+  :global OSVersion;\r
+  :global devBoardName;\r
+  :global WaitForFile;\r
+\r
+  :global BackupUploadUrl;\r
+  :global BackupUploadPass;\r
+  :global BackupUploadUser;\r
+  :global BackupSendBinary;\r
+  :global BackupSendExport;\r
+  :global BackupSendGlobalConfig;\r
+  :global BackupSendScripts;\r
+  :global PackagesUpdateBackupFailure;\r
+  :global BackupRmLocal;\r
+  :global BackupLocalRoot;\r
+  :global BackupEncrypt;\r
+  :global BackupPassword;\r
+  :global BackupRandomDelay;\r
+  \r
+  :global BackupSens;\r
+  :global BackupGeneral;\r
+  :global BackupUser;\r
+  :global BackupLicense;\r
+  :global BackupSshKeys;\r
+  :global BackupCerts;\r
+  :global BackupCertsPasswd;\r
+  :global BackupUserMan;\r
+  :global BackupDude;\r
+  :global BackupUserList;\r
+  :global SendTelegram;\r
+  :global SendNotification;\r
+\r
+  :if ($BackupSendBinary != true && $BackupSendExport != true) do={\r
+    $LogPrint error $ScriptName ("Configured to send neither backup nor config export.");\r
+    :set ExitOK true; :error false;\r
+  }\r
+\r
+  :if ([ $checkRunJob $ScriptName ] = false && $BackupRandomDelay > 0) do={\r
+    $RandomDelay $BackupRandomDelay;\r
+  }\r
+\r
+  # filename based on identity\r
+  :local dateTimeStampFix [$MiniDateTimeStamp];\r
+  :local DirName ($Identity . "-" . $dateTimeStampFix);\r
+  #:local DirName ($BackupLocalRoot . "/" $ScriptName);\r
+  :local FilePref [ $CleanName ($Identity . "-") ];\r
+  :if ($BackupLocalRoot = "") do={\r
+    $LogPrint warning $ScriptName ("\n\$BackupLocalRoot is not set! I set \$BackupRmLocal to true!" . $Err);\r
+    :set $BackupRmLocal true;\r
+    $LogPrint debug $ScriptName ("\n\$DirName = " . $DirName);\r
+  } else={\r
+    :set $DirName ($BackupLocalRoot . "/" . $DirName);\r
+    $LogPrint debug $ScriptName ("\n\$DirName = " . $DirName);\r
+  }\r
+  :local FileName "undefined";\r
+  :local FilePath "undefined";\r
+  :local expScript "undefined";\r
+  :local BackupFile "none";\r
+  :local ExportFile "none";\r
+  :local ConfigFile "none";\r
+  :local ExportScript "none";\r
+  :local Failed 0;\r
+\r
+  :if ([ $MkDir $DirName ] = false) do={\r
+    $LogPrint error $ScriptName ("Failed creating directory!");\r
+    :set ExitOK true;\r
+    :error false;\r
+  }\r
+  # binary backup\r
+  :if ($BackupSendBinary = true) do={\r
+    :set FileName [ $CleanName ($FilePref . "BinaryBackup") ];\r
+    :set FilePath ($DirName . "/" . $FileName);\r
+    if ($BackupEncrypt = true) do={\r
+       /system/backup/save encryption=aes-sha256 name=$FilePath password=$BackupPassword;\r
+    } else={\r
+       /system/backup/save dont-encrypt=yes name=$FilePath;\r
+    }\r
+      $WaitForFile ($FilePath . ".backup");\r
+      :onerror Err {\r
+      /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".backup") \\r
+          user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".backup");\r
+      :set BackupFile [ /file/get ($FilePath . ".backup") ];\r
+      :set ($BackupFile->"name") ($FileName . ".backup");\r
+    } do={\r
+      $LogPrint error $ScriptName ("\nUploading backup file " . $FileName . ".backup failed: " . $Err);\r
+      :set BackupFile "failed";\r
+      :set Failed 1;\r
+    }\r
+    :if ($BackupRmLocal = true) do={$RmFile ($FilePath . ".backup")}\r
+    :set FileName "undefined";\r
+    :set FilePath "undefined";\r
+  }\r
+\r
+  # create configuration export\r
+  :if ($BackupSendExport = true) do={\r
+    :set FileName [ $CleanName ($FilePref . "Export") ];\r
+    :set FilePath ($DirName . "/" . $FileName);\r
+    if ($BackupSens = true) do={\r
+    /export terse hide-sensitive file=$FilePath;\r
+    } else={\r
+    /export terse show-sensitive file=$FilePath;\r
+    }\r
+    $WaitForFile ($FilePath . ".rsc");\r
+    :onerror Err {\r
+      /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".rsc") \\r
+          user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".rsc");\r
+      :set ExportFile [ /file/get ($FilePath . ".rsc") ];\r
+      :set ($ExportFile->"name") ($FileName . ".rsc");\r
+    } do={\r
+      $LogPrint error $ScriptName ("Uploading configuration export failed: " . $Err);\r
+      :set ExportFile "failed";\r
+      :set Failed 1;\r
+    }\r
+    :if ($BackupRmLocal = true) do={$RmFile ($FilePath . ".rsc")}\r
+    :set FileName "undefined";\r
+    :set FilePath "undefined";\r
+  }\r
+\r
+  # Amster-GlobalConfig\r
+  :if ($BackupSendGlobalConfig = true) do={\r
+    # Do *NOT* use '/file/add ...' here, as it is limited to 4095 bytes!\r
+    :set FileName [ $CleanName ($FilePref . "GlobalConfig") ];\r
+    :set FilePath ($DirName . "/" . $FileName);\r
+    :execute script={ :put [ /system/script/get Amster-GlobalConfig source ]; } \\r
+        file=($FilePath . ".conf\00");\r
+    $WaitForFile ($FilePath . ".conf");\r
+    :onerror Err {\r
+      /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".conf") \\r
+          user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".conf");\r
+      :set ConfigFile [ /file/get ($FilePath . ".conf") ];\r
+      :set ($ConfigFile->"name") ($FileName . ".conf");\r
+    } do={\r
+      $LogPrint error $ScriptName ("Uploading Amster-GlobalConfig failed: " . $Err);\r
+      :set ConfigFile "failed";\r
+      :set Failed 1;\r
+    }\r
+    :if ($BackupRmLocal = true) do={$RmFile ($FilePath . ".conf")}\r
+    :set FileName "undefined";\r
+    :set FilePath "undefined";\r
+  }\r
+\r
+  # Export Scripts  \r
+  :if ($BackupSendScripts = true) do={\r
+    :foreach expScript in=([ /system/script/find where name~"Amster-GlobalConf*" ]) do={\r
+    # Do *NOT* use '/file/add ...' here, as it is limited to 4095 bytes!\r
+    :set FileName [ $CleanName ($FilePref . [ /system/script/get $expScript name ]) ];\r
+    :set FilePath ($DirName . "/" . $FileName . ".rsc\00");\r
+    :execute script={ :put [ /system/script/get $FileName source ]; } \\r
+        file=($FilePath . ".rsc\00");\r
+    $WaitForFile ($FilePath . ".rsc");\r
+    :onerror Err {\r
+      /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".rsc") \\r
+          user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".rsc");\r
+      :set ExportScript [ /file/get ($FilePath . ".rsc") ];\r
+      :set ($ExportScript->"name") ($FileName . ".rsc");\r
+    } do={\r
+      $LogPrint error $ScriptName ("Uploading " . $ExportScript . " failed!" . $Err);\r
+      :set ExportScript "failed";\r
+      :set Failed 1;\r
+    }\r
+    :if ($BackupRmLocal = true) do={$RmFile ($FilePath . ".rsc")}\r
+    :set FileName "undefined";\r
+    :set FilePath "undefined";\r
+    :set ExportScript "undefined";\r
+    }\r
+  }\r
+\r
+  :local FileInfo do={\r
+    :local Name $1;\r
+    :local File $2;\r
+    :global FormatLine;\r
+    :global HumanReadableNum;\r
+    :global IfThenElse;\r
+    :return \\r
+      [ $IfThenElse ([ :typeof $File ] = "array") \\r
+        ($Name . ":\n" . [ $FormatLine "    name" ($File->"name") ] . "\n" . \\r
+          [ $FormatLine "    size" ([ $HumanReadableNum ($File->"size") 1024 ] . "B") ]) \\r
+        [ $FormatLine $Name $File ] ];\r
+  }\r
+  $SendNotification2 ({ origin=$ScriptName; \\r
+    subject=[ $IfThenElse ($Failed > 0) \\r
+      ([ $SymbolForNotification "floppy-disk,warning-sign" ] . "Backup & Config upload with failure") \\r
+      ([ $SymbolForNotification "floppy-disk,arrow-up" ] . "Backup & Config upload") ]; \\r
+    message=("Backup and config export upload for " . $Identity . ".\n\n" . \\r
+      [ $DeviceInfo ] . "\n\n" . \\r
+      [ $FileInfo "Backup file" $BackupFile ] . "\n" . \\r
+      [ $FileInfo "Export file" $ExportFile ] . "\n" . \\r
+      [ $FileInfo "Config file" $ConfigFile ]); silent=true });\r
+  :if ($Failed = 1) do={\r
+    :set PackagesUpdateBackupFailure true;\r
+  }\r
+  :if ($BackupRmLocal = true) do={$RmDir $DirName}\r
+} do={:global ExitError; $ExitError $ExitOK [ :jobname ] $Err}\r
diff --git a/html/AM-setup.rsc b/html/AM-setup.rsc
new file mode 100644 (file)
index 0000000..176df51
--- /dev/null
@@ -0,0 +1,122 @@
+#!rsc by Vados\r
+# RouterOS script: AM-setup\r
+# Script comment: Script for setup routeros scrypt system\r
+# Copyright (c) 2005-2026 Vados <vados@vados.ru>\r
+#\r
+#\r
+# requires RouterOS, version=7.19\r
+# requires device-mode, fetch\r
+#\r
+# :local ScriptName [ :jobname ];\r
+# Additional commands:\r
+#\r
+# For scripts:\r
+#:global NewScript "script_name"; \r
+#:global GlobalScriptsUrl "https://ros.vados.ru/";\r
+#/system/script/add name=$NewScript owner=$NewScript source=([/tool/fetch check-certificate=yes-without-crl ($GlobalScriptsUrl . $ScriptAdd . ".rsc") output=user as-value ]->"data");\r
+#:global NewScript (a);\r
+#:global ScriptInstallUpdate; $ScriptInstallUpdate;\r
+#\r
+# For run from terminal:\r
+#/system/script/add name=AM-setup owner=AM-Setup source=([/tool/fetch check-certificate=yes-without-crl "https://ros.vados.ru/AM-setup.rsc" output=user as-value ]->"data");\r
+#\r
+#:foreach Script in=[ /system/script/find where source~"^#!rsc by Vados" ] do={\r
+#  if ($ScriptUpdatesCRLF = true) do={\r
+#   /system/script/set source=[ :tocrlf [ get $Script source ] ] $Script;\r
+#  } else={\r
+#   /system/script/set source=[ :tolf [ get $Script source ] ] $Script;\r
+#  }\r
+#}\r
+# /import verbose=no $ScriptName\r
+:global SetupScript [ :jobname ];\r
+:global RemoveMe;\r
+:global InitSetup true;\r
+:global tmpfs true;\r
+\r
+{\r
+  :local RmMe [:tobool $1];\r
+\r
+  :global GlobalScriptsUrl;\r
+  :global SetupScript;\r
+  :global RemoveMe;\r
+  :global InitSetup;\r
+  :global tmpfs;\r
+    \r
+  :local CertCommonName "Root YE";\r
+  :local CertFileName "Root-YE.pem";\r
+  :local CertFingerprint "e14ffcad5b0025731006caa43a121a22d8e9700f4fb9cf852f02a708aa5d5666";\r
+\r
+  :if ($SetupScript = false) do={\r
+    :global SetupScript "AM-setup";\r
+  }\r
+  :if ($RemoveMe != false) do={\r
+    :set RemoveMe true;\r
+  }\r
+\r
+  :log warning ("\n\$GlobalScriptsUrl = " . $GlobalScriptsUrl);\r
+  \r
+  :local CertSettings [ /certificate/settings/get ];\r
+  :if (!((($CertSettings->"builtin-trust-anchors") = "trusted" || \\r
+          ($CertSettings->"builtin-trust-store") ~ "fetch" || \\r
+          ($CertSettings->"builtin-trust-store") = "all") && \\r
+         [ :len [ /certificate/builtin/find where common-name=$CertCommonName ] ] > 0)) do={\r
+    :put "Importing certificate...";\r
+    \r
+    /tool/fetch ($GlobalScriptsUrl . "certs/" . $CertFileName) dst-path=$CertFileName as-value;\r
+    :delay 1s;\r
+    /certificate/import file-name=$CertFileName passphrase="";\r
+    :if ([ :len [ /certificate/find where fingerprint=$CertFingerprint ] ] != 1) do={\r
+      :error "Something is wrong with your certificates!";\r
+    };\r
+    :delay 1s;\r
+  };\r
+  :put "Thr tmpfs variable is true. Checking for tmpfs/scripts directory...";\r
+  :if ($tmpfs = true && [/file find name="tmpfs/scripts"] != "") do={\r
+    :log debug ($0 . "Directory tmpfs/scripts exsist.");\r
+    :return $true;\r
+  } else={\r
+    :log error ($0 . "Directory tmpfs/scripts does not exsist. Remove global variable tmpfs!");\r
+    :global tmpfs (a);\r
+    return false;\r
+}\r
+  :put "Renaming AM-GlobalConfig, if exists...";\r
+  :local ConfOldName ("AM-GlobalConfig-" . [ /system/clock/get date ] . [ /system/clock/get time ] . ".rsc");\r
+  /system/script/set name=$ConfOldName [ find where name="AM-GlobalConfig" ];\r
+  :foreach Script in={ "AM-GlobalConfig"; "AM-GlobalFunc" } do={\r
+    :put "Installing $Script...";\r
+    /system/script/remove [ find where name=$Script ];\r
+    /system/script/add name=$Script owner=$Script source=([ /tool/fetch check-certificate=yes-without-crl url=($GlobalScriptsUrl . $Script . ".rsc")  output=user as-value ]->"data");\r
+    \r
+    /system/script run $Script;\r
+    :delay 2s;\r
+  };\r
+\r
+  :if ([ :len [ /certificate/find where fingerprint=$CertFingerprint ] ] > 0) do={\r
+    :put "Renaming certificate by its common-name...";\r
+    :global CertificateNameByCN;\r
+    $CertificateNameByCN $CertFingerprint;\r
+  };\r
+\r
+  :if ($RemoveMe = true) do={\r
+    :put "Add Scheduler for remove this script...";\r
+    :local OnEvent (":delay 30s;\n" . "/system/script/remove [ find where name=\"$SetupScript\" ];\n" . \\r
+    ":delay 2s;\n" . "/system/scheduler/remove [ find where name=\"_RemoveSetup\" ];\n" . \\r
+    ":delay 2s;\n" . ":global RemoveMe (a);\n");\r
+    :local Comment "_RemoveSetup tmp scheduler for remove setup script.";\r
+    /system/scheduler/add name="_RemoveSetup" comment=$Comment start-time=startup interval=30s on-event=$OnEvent;\r
+      :log warning ($0 . "\nI've done everything, and now I'm tired... I'm leaving.\nThere will be an error below, this is normal.");\r
+    }\r
+    :delay 1;\r
+    :put "Loading configuration and functions...";\r
+    :do {      \r
+      :global ScriptInstallUpdate; $ScriptInstallUpdate;\r
+      :put "Normal removing temporary global environment variables.";\r
+      :global SetupScript (a);\r
+      :global InitSetup (a);\r
+      :global RemoveMe (a);\r
+    } on-error={\r
+      :local ErrorMsg "Loading configuration and functions error!";\r
+      :log error ($0 . $ErrorMsg . :error);\r
+      :error $ErrorMsg;\r
+    }\r
+}\r
diff --git a/html/CERTIFICATES.d/01-dialog-A.avif b/html/CERTIFICATES.d/01-dialog-A.avif
new file mode 100644 (file)
index 0000000..2fc3c9b
Binary files /dev/null and b/html/CERTIFICATES.d/01-dialog-A.avif differ
diff --git a/html/CERTIFICATES.d/02-dialog-B.avif b/html/CERTIFICATES.d/02-dialog-B.avif
new file mode 100644 (file)
index 0000000..5e408ab
Binary files /dev/null and b/html/CERTIFICATES.d/02-dialog-B.avif differ
diff --git a/html/CERTIFICATES.d/03-window.avif b/html/CERTIFICATES.d/03-window.avif
new file mode 100644 (file)
index 0000000..96039a3
Binary files /dev/null and b/html/CERTIFICATES.d/03-window.avif differ
diff --git a/html/CERTIFICATES.d/04-certificate.avif b/html/CERTIFICATES.d/04-certificate.avif
new file mode 100644 (file)
index 0000000..e666314
Binary files /dev/null and b/html/CERTIFICATES.d/04-certificate.avif differ
diff --git a/html/COPYING.md b/html/COPYING.md
new file mode 100644 (file)
index 0000000..2fb2e74
--- /dev/null
@@ -0,0 +1,675 @@
+### GNU GENERAL PUBLIC LICENSE
+
+Version 3, 29 June 2007
+
+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.
+
+### Preamble
+
+The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+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>.
diff --git a/html/INITIAL-COMMANDS.md b/html/INITIAL-COMMANDS.md
new file mode 100644 (file)
index 0000000..3858e6f
--- /dev/null
@@ -0,0 +1,64 @@
+Initial commands
+================
+
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+
+[⬅️ Go back to main README](README.md)
+
+> ⚠️ **Warning**: These commands are intended for initial setup. If you are
+> not aware of the procedure please follow
+> [the long way in detail](README.md#the-long-way-in-detail).
+
+Run the complete base installation:
+
+    {
+      :local BaseUrl "https://rsc.eworm.de/main/";
+      :local CertCommonName "Root YE";
+      :local CertFileName "Root-YE.pem";
+      :local CertFingerprint "e14ffcad5b0025731006caa43a121a22d8e9700f4fb9cf852f02a708aa5d5666";
+
+      :local CertSettings [ /certificate/settings/get ];
+      :if (!((($CertSettings->"builtin-trust-anchors") = "trusted" || \
+              ($CertSettings->"builtin-trust-store") ~ "fetch" || \
+              ($CertSettings->"builtin-trust-store") = "all") && \
+             [ :len [ /certificate/builtin/find where common-name=$CertCommonName ] ] > 0)) do={
+        :put "Importing certificate...";
+        /tool/fetch ($BaseUrl . "certs/" . $CertFileName) dst-path=$CertFileName as-value;
+        :delay 1s;
+        /certificate/import file-name=$CertFileName passphrase="";
+        :if ([ :len [ /certificate/find where fingerprint=$CertFingerprint ] ] != 1) do={
+          :error "Something is wrong with your certificates!";
+        };
+        :delay 1s;
+      };
+      :put "Renaming global-config-overlay, if exists...";
+      /system/script/set name=("global-config-overlay-" . [ /system/clock/get date ] . "-" . [ /system/clock/get time ]) [ find where name="global-config-overlay" ];
+      :foreach Script in={ "global-config"; "global-config-overlay"; "global-functions" } do={
+        :put "Installing $Script...";
+        /system/script/remove [ find where name=$Script ];
+        /system/script/add name=$Script owner=$Script source=([ /tool/fetch check-certificate=yes-without-crl ($BaseUrl . $Script . ".rsc") output=user as-value ]->"data");
+      };
+      :put "Loading configuration and functions...";
+      /system/script { run global-config; run global-functions; };
+      :if ([ :len [ /certificate/find where fingerprint=$CertFingerprint ] ] > 0) do={
+        :put "Renaming certificate by its common-name...";
+        :global CertificateNameByCN;
+        $CertificateNameByCN $CertFingerprint;
+      };
+    };
+
+Then continue setup with
+[scheduled automatic updates](README.md#scheduled-automatic-updates) or
+[editing configuration](README.md#editing-configuration).
+
+## Fix existing installation
+
+The [initial commands](#initial-commands) above allow to fix an existing
+installation in case it ever breaks. If `global-config-overlay` did exist
+before it is renamed with a date and time suffix (like
+`global-config-overlay-2024-01-25-09:33:12`). Make sure to restore the
+configuration overlay if required.
+
+---
+[⬅️ Go back to main README](README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/Makefile b/html/Makefile
new file mode 100644 (file)
index 0000000..308ef45
--- /dev/null
@@ -0,0 +1,46 @@
+# Makefile to generate data:
+#  template scripts -> final scripts
+#  markdown files -> html files
+
+ALL_RSC                := $(wildcard *.rsc */*.rsc)
+GEN_RSC                := $(wildcard *.capsman.rsc *.local.rsc *.wifi.rsc)
+
+MARKDOWN       := $(wildcard *.md doc/*.md doc/mod/*.md)
+HTML           := $(MARKDOWN:.md=.html)
+
+DATE           ?= $(shell date --rfc-email)
+VERSION                ?= $(shell git symbolic-ref --short HEAD 2>/dev/null)/$(shell git rev-list --count HEAD 2>/dev/null)/$(shell git rev-parse --short=8 HEAD 2>/dev/null)
+export DATE VERSION
+
+.PHONY: all checksums commitinfo docs rsc clean
+
+all: checksums docs rsc
+
+checksums: checksums.json
+
+checksums.json: .bin/checksums.sh $(ALL_RSC)
+       .bin/checksums.sh > $@
+
+commitinfo: Amster-GlobalFunctions.rsc
+       .bin/commitinfo.sh $< > $<~
+       mv $<~ $<
+
+docs: $(HTML)
+
+%.html: %.md .include/css/style.css .bin/html.sh .tmpl/head.html .tmpl/foot.html
+       .bin/html.sh $< > $@
+
+rsc: $(GEN_RSC)
+
+caps/%.capsman.rsc: tmpl/caps/%.template.rsc .bin/template-capsman.sh
+       .bin/template-capsman.sh $< > $@
+
+local/%.local.rsc: tmpl/local/%.template.rsc .bin/template-local.sh
+       .bin/template-local.sh $< > $@
+
+wifi/%.wifi.rsc: tmpl/wifi/%.template.rsc .bin/template-wifi.sh
+       .bin/template-wifi.sh $< > $@
+
+clean:
+       rm -f $(HTML) checksums.json
+       make -C tmpl/ clean
diff --git a/html/README.d/00-builtin-trust-store.avif b/html/README.d/00-builtin-trust-store.avif
new file mode 100644 (file)
index 0000000..0693ee4
Binary files /dev/null and b/html/README.d/00-builtin-trust-store.avif differ
diff --git a/html/README.d/01-download-certs.avif b/html/README.d/01-download-certs.avif
new file mode 100644 (file)
index 0000000..e4d8755
Binary files /dev/null and b/html/README.d/01-download-certs.avif differ
diff --git a/html/README.d/02-import-certs.avif b/html/README.d/02-import-certs.avif
new file mode 100644 (file)
index 0000000..b31343c
Binary files /dev/null and b/html/README.d/02-import-certs.avif differ
diff --git a/html/README.d/03-check-certs.avif b/html/README.d/03-check-certs.avif
new file mode 100644 (file)
index 0000000..6610ac4
Binary files /dev/null and b/html/README.d/03-check-certs.avif differ
diff --git a/html/README.d/04-import-scripts.avif b/html/README.d/04-import-scripts.avif
new file mode 100644 (file)
index 0000000..c09949a
Binary files /dev/null and b/html/README.d/04-import-scripts.avif differ
diff --git a/html/README.d/05-run-scripts.avif b/html/README.d/05-run-scripts.avif
new file mode 100644 (file)
index 0000000..f8ccf5b
Binary files /dev/null and b/html/README.d/05-run-scripts.avif differ
diff --git a/html/README.d/06-schedule-update.avif b/html/README.d/06-schedule-update.avif
new file mode 100644 (file)
index 0000000..158e13f
Binary files /dev/null and b/html/README.d/06-schedule-update.avif differ
diff --git a/html/README.d/07-edit-global-config-overlay.avif b/html/README.d/07-edit-global-config-overlay.avif
new file mode 100644 (file)
index 0000000..9a5b903
Binary files /dev/null and b/html/README.d/07-edit-global-config-overlay.avif differ
diff --git a/html/README.d/08-apply-configuration.avif b/html/README.d/08-apply-configuration.avif
new file mode 100644 (file)
index 0000000..ab22cae
Binary files /dev/null and b/html/README.d/08-apply-configuration.avif differ
diff --git a/html/README.d/09-update-scripts.avif b/html/README.d/09-update-scripts.avif
new file mode 100644 (file)
index 0000000..39ff98e
Binary files /dev/null and b/html/README.d/09-update-scripts.avif differ
diff --git a/html/README.d/10-install-scripts.avif b/html/README.d/10-install-scripts.avif
new file mode 100644 (file)
index 0000000..cf26b16
Binary files /dev/null and b/html/README.d/10-install-scripts.avif differ
diff --git a/html/README.d/11-schedule-script.avif b/html/README.d/11-schedule-script.avif
new file mode 100644 (file)
index 0000000..558614f
Binary files /dev/null and b/html/README.d/11-schedule-script.avif differ
diff --git a/html/README.d/12-setup-lease-script.avif b/html/README.d/12-setup-lease-script.avif
new file mode 100644 (file)
index 0000000..2a8bcb2
Binary files /dev/null and b/html/README.d/12-setup-lease-script.avif differ
diff --git a/html/README.d/13-install-custom-script.avif b/html/README.d/13-install-custom-script.avif
new file mode 100644 (file)
index 0000000..221b84e
Binary files /dev/null and b/html/README.d/13-install-custom-script.avif differ
diff --git a/html/README.d/14-remove-script.avif b/html/README.d/14-remove-script.avif
new file mode 100644 (file)
index 0000000..3e4c105
Binary files /dev/null and b/html/README.d/14-remove-script.avif differ
diff --git a/html/README.d/hello-world.rsc b/html/README.d/hello-world.rsc
new file mode 100644 (file)
index 0000000..ced2275
--- /dev/null
@@ -0,0 +1,3 @@
+#!rsc by Vados
+
+:put ("Hello World from " . [ /system/identity/get name ] . "!");
diff --git a/html/README.d/notification-news-and-changes.avif b/html/README.d/notification-news-and-changes.avif
new file mode 100644 (file)
index 0000000..d2e8aa7
Binary files /dev/null and b/html/README.d/notification-news-and-changes.avif differ
diff --git a/html/README.d/telegram-group.avif b/html/README.d/telegram-group.avif
new file mode 100644 (file)
index 0000000..eb75d13
Binary files /dev/null and b/html/README.d/telegram-group.avif differ
diff --git a/html/README.md b/html/README.md
new file mode 100644 (file)
index 0000000..5e6c936
--- /dev/null
@@ -0,0 +1,375 @@
+[Amster] RouterOS Scripts
+=========================
+
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+
+**a collection of scripts for MikroTik RouterOS**
+
+[RouterOS ↗️](https://mikrotik.com/software) is the operating system developed
+by [MikroTik ↗️](https://mikrotik.com/aboutus) for networking tasks. This
+repository holds a number of [scripts ↗️](https://wiki.mikrotik.com/wiki/Manual:Scripting)
+to manage RouterOS devices or extend their functionality.
+
+*Use at your own risk*, pay attention to
+[license](#license).
+
+Requirements
+------------
+
+### Software (RouterOS)
+
+Latest version of the scripts require recent RouterOS to function properly.
+Make sure to install latest updates before you begin. This is supposed to
+work flawlessly with these channels:
+
+* `stable` - the latest version considered stable for daily use, including
+   new features
+* `long-term` - a version considered rock-solid, usually one minor version
+   behind `stable` (`7.(n-1)`)
+
+New functionality or breaking changes in RouterOS are adopted fairly quick.
+These changes are pushed for general availability once a version of
+RouterOS supporting this had been released to the `long-term` channel a
+reasonable time ago.
+
+At any time you should have at least two minor versions and their bugfix
+releases to choose from. Often way older versions of RouterOS work just
+fine.
+
+On the other hand in seldom cases and for good reasons *specific* scripts
+may require an even newer RouterOS version, so only `stable` is supported
+temporarily.
+
+> 💡️ **Hint**: If in doubt have a look at the badge at the top of each
+> page showing the minimum version required:
+> ![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)
+
+> ℹ️ **Info**: The `main` branch is now RouterOS v7 only. If you are still
+> running RouterOS v6 switch to `routeros-v6` branch!
+
+#### Prerequisite configuration
+
+The
+[device-mode ↗️](https://help.mikrotik.com/docs/spaces/ROS/pages/93749258/Device-mode)
+is a mechanism to lock down a device for security reasons, it gives
+fine-grained control over what features are available. You need to enable
+`scheduler` and `fetch` at least, specific scripts may require additional
+features.
+
+### Hardware
+
+RouterOS packages increase in size with each release. This becomes a
+problem for devices with 16MB storage and below, those with an ARM CPU
+are specifically affected.
+
+Huge configuration and lots of scripts give an extra risk. **Take care!**
+
+### Initial setup
+-----------------
+> ℹ️ **Info**: For a comfortable installation, I came up with a script: AM-setup.rsc. Below are the commands for the right system administrators, with straight hands. :) And if anyone doesn't like it, write, let's have a laugh together!
+
+* Step by step:
+
+    :global GlobalScriptsUrl "https://ros.vados.ru/";
+
+    :global RemoveMe false;
+    
+    /system/script/add name="AM-setup" owner="A
+M-setup" source=([/tool/fetch check-certificate=yes-without-crl url=($GlobalScriptsUrl . "AM-setup.rsc")  output=user as-value]->"data");
+
+    /system/script/run AM-setup;
+
+* One string:
+
+    :global GlobalScriptsUrl "https://ros.vados.ru/"; :global RemoveMe false; /system/script/add name="AM-setup" owner="A
+M-setup" source=([/tool/fetch check-certificate=yes-without-crl url=($GlobalScriptsUrl . "AM-setup.rsc")  output=user as-value]->"data");
+ /system/script/run AM-setup;
+
+
+> 💡️ **Hint**: To prevent the installation script from being deleted 30 seconds after its completion, you can use a global variable [`:global RemoveMe false;`]. Don't worry, the script will delete all its global variables. Shall we check? ;)
+
+### The long way in detail
+
+The update script does server certificate verification, so first step is
+to establish trust.
+
+#### Builtin trust store
+
+RouterOS comes with a builtin trust store with several CA certificates.
+If you intend **not** to trust this store jump to
+[download and import certificate](#download-and-import-certificate) now.
+
+Select the `fetch` command to trust these builtin certificates at
+least, but make sure not to drop other targets:
+
+    /certificate/settings/set builtin-trust-store=fetch;
+
+![screenshot: builtin trust store](README.d/00-builtin-trust-store.avif)
+
+> 💡️ **Hint**: With RouterOS 7.20.x and before the functionality was
+> different. Set the trust for the builtin trust anchors:  
+> `/certificate/settings/set builtin-trust-anchors=trusted;`  
+
+You can skip the steps regarding *download and import certificate* and
+jump to [installation of scripts](#installation-of-scripts) now.
+
+#### Download and import certificate
+
+If you intend to download the scripts from a
+different location (for example from github.com) install the corresponding
+certificate chain.
+
+    /tool/fetch "https://rsc.eworm.de/main/certs/Root-YE.pem" dst-path="root-ye.pem";
+
+![screenshot: download certs](README.d/01-download-certs.avif)
+
+> ℹ️ **Info**: Note that the command above does *not* verify server
+> certificate, so if you want to be safe download with your workstations's
+> browser from CA's website and transfer the file to your MikroTik device:
+> *Let's Encrypt* / *ISRG* [Root YE ↗️](https://letsencrypt.org/certs/gen-y/root-ye.pem)
+
+Then we import the certificate.
+
+    /certificate/import file-name="root-ye.pem" passphrase="";
+
+Do not worry that the command is not shown - that happens because it contains
+a sensitive property, the passphrase.
+
+![screenshot: import certs](README.d/02-import-certs.avif)
+
+For basic verification we rename the certificate and print it by
+fingerprint. Make sure exactly this one certificate ("*Root-YE*")
+is shown.
+
+    /certificate/set name="Root-YE" [ find where common-name="Root YE" ];
+    /certificate/print proplist=name,fingerprint where fingerprint="e14ffcad5b0025731006caa43a121a22d8e9700f4fb9cf852f02a708aa5d5666";
+
+![screenshot: check certs](README.d/03-check-certs.avif)
+
+Always make sure there are no certificates installed you do not know or want!
+
+#### Installation of scripts
+
+All following commands will verify the server certificate. For validity the
+certificate's lifetime is checked with local time, so make sure the device's
+date and time is set correctly!
+
+Now let's download the main scripts and add them in configuration on the fly.
+
+    :foreach Script in={ "global-config"; "global-config-overlay"; "global-functions" } do={ /system/script/add name=$Script owner=$Script source=([ /tool/fetch check-certificate=yes-without-crl ("https://rsc.eworm.de/main/" . $Script . ".rsc") output=user as-value ]->"data"); };
+
+![screenshot: import scripts](README.d/04-import-scripts.avif)
+
+And finally run configuration and functions. This will also add the
+scheduler for loading at system startup automatically.
+
+    /system/script { run global-config; run global-functions; };
+
+![screenshot: run scripts](README.d/05-run-scripts.avif)
+
+> 💡️ **Hint**: You see complaints regarding syntax errors? Most likely the
+> RouterOS on your device is too old. Check for updates!
+
+### Scheduled automatic updates
+
+The last step is optional: Add this scheduler **only** if you want the
+scripts to be updated automatically!
+
+    /system/scheduler/add name="ScriptInstallUpdate" start-time=startup interval=1d on-event=":global ScriptInstallUpdate; \$ScriptInstallUpdate;";
+
+![screenshot: schedule update](README.d/06-schedule-update.avif)
+
+### Editing configuration
+-------------------------
+
+The configuration needs to be tweaked for your needs. Edit
+`global-config-overlay`, copy relevant configuration from
+[`global-config`](global-config.rsc) (the one without `-overlay`).
+Save changes and exit with `Ctrl-o`.
+
+    /system/script/edit global-config-overlay source;
+
+![screenshot: edit global-config-overlay](README.d/07-edit-global-config-overlay.avif)
+
+Additionally creating configuration snippets is supported. The script name
+of these snippets has to start with `global-config-overlay.d/` to make them
+being loaded automatically. This allows to split off parts of the
+configuration.
+
+To apply your changes run `global-config`, which will automatically load
+the overlay as well:
+
+    /system/script/run global-config;
+
+![screenshot: apply configuration](README.d/08-apply-configuration.avif)
+
+This last step is required when ever you make changes to your configuration.
+
+> ℹ️ **Info**: It is recommended to edit the configuration using the command
+> line interface. If using Winbox on Windows OS, the line endings may be
+> missing. To fix this run:  
+> `/system/script/set source=[ :tocrlf [ get global-config-overlay source ] ] global-config-overlay;`
+
+### Updating scripts
+--------------------
+
+To update existing scripts just run function `$ScriptInstallUpdate`. If
+everything is up-to-date it will not produce any output.
+
+    $ScriptInstallUpdate;
+
+![screenshot: update scripts](README.d/09-update-scripts.avif)
+
+If the update includes news or requires configuration changes a notification
+is sent - in addition to terminal output and log messages.
+
+![news and changes notification](README.d/notification-news-and-changes.avif)
+
+### Adding a script
+-------------------
+
+To add a script from the repository run function `$ScriptInstallUpdate` with
+a comma separated list of script names.
+
+    $ScriptInstallUpdate check-certificates,check-routeros-update;
+
+![screenshot: install scripts](README.d/10-install-scripts.avif)
+
+### Scheduler and events
+------------------------
+
+Most scripts are designed to run regularly from
+[scheduler ↗️](https://wiki.mikrotik.com/wiki/Manual:System/Scheduler). We just
+added `check-routeros-update`, so let's run it daily to make sure not to
+miss an update.
+
+    /system/scheduler/add name="check-routeros-update" interval=1d start-time=startup on-event="/system/script/run check-routeros-update;";
+
+![screenshot: schedule script](README.d/11-schedule-script.avif)
+
+Some events can run a script. If you want your DHCP hostnames to be available
+in DNS use `dhcp-to-dns` with the events from dhcp server. For a regular
+cleanup add a scheduler entry.
+
+    $ScriptInstallUpdate dhcp-to-dns,lease-script;
+    /ip/dhcp-server/set lease-script=lease-script [ find ];
+    /system/scheduler/add name="dhcp-to-dns" interval=5m start-time=startup on-event="/system/script/run dhcp-to-dns;";
+
+![screenshot: setup lease script](README.d/12-setup-lease-script.avif)
+
+There's much more to explore... Have fun!
+
+### Available scripts
+---------------------
+
+* [Find and remove access list duplicates](doc/accesslist-duplicates.md) (`accesslist-duplicates`)
+* [Upload backup to Mikrotik cloud](doc/backup-cloud.md) (`backup-cloud`)
+* [Send backup via e-mail](doc/backup-email.md) (`backup-email`)
+* [Save configuration to fallback partition](doc/backup-partition.md) (`backup-partition`)
+* [Upload backup to server](doc/backup-upload.md) (`backup-upload`)
+* [Download packages for CAP upgrade from CAPsMAN](doc/capsman-download-packages.md) (`capsman-download-packages`)
+* [Run rolling CAP upgrades from CAPsMAN](doc/capsman-rolling-upgrade.md) (`capsman-rolling-upgrade`)
+* [Renew locally issued certificates](doc/certificate-renew-issued.md) (`certificate-renew-issued`)
+* [Renew certificates and notify on expiration](doc/check-certificates.md) (`check-certificates`)
+* [Notify about health state](doc/check-health.md) (`check-health`)
+* [Notify on LTE firmware upgrade](doc/check-lte-firmware-upgrade.md) (`check-lte-firmware-upgrade`)
+* [Check perpetual license on CHR](doc/check-perpetual-license.md) (`check-perpetual-license`)
+* [Notify on RouterOS update](doc/check-routeros-update.md) (`check-routeros-update`)
+* [Collect MAC addresses in wireless access list](doc/collect-wireless-mac.md) (`collect-wireless-mac`)
+* [Use wireless network with daily psk](doc/daily-psk.md) (`daily-psk`)
+* [Comment DHCP leases with info from access list](doc/dhcp-lease-comment.md) (`dhcp-lease-comment`)
+* [Create DNS records for DHCP leases](doc/dhcp-to-dns.md) (`dhcp-to-dns`)
+* [Automatically upgrade firmware and reboot](doc/firmware-upgrade-reboot.md) (`firmware-upgrade-reboot`)
+* [Download, import and update firewall address-lists](doc/fw-addr-lists.md) (`fw-addr-lists`)
+* [Wait for global functions und modules](doc/global-wait.md) (`global-wait`)
+* [Send GPS position to server](doc/gps-track.md) (`gps-track`)
+* [Use WPA network with hotspot credentials](doc/hotspot-to-wpa.md) (`hotspot-to-wpa` & `hotspot-to-wpa-cleanup`)
+* [Create DNS records for IPSec peers](doc/ipsec-to-dns.md) (`ipsec-to-dns`)
+* [Update configuration on IPv6 prefix change](doc/ipv6-update.md) (`ipv6-update`)
+* [Manage IP addresses with bridge status](doc/ip-addr-bridge.md) (`ip-addr-bridge`)
+* [Run other scripts on DHCP lease](doc/lease-script.md) (`lease-script`)
+* [Manage LEDs dark mode](doc/leds-mode.md) (`leds-day-mode`, `leds-night-mode` & `leds-toggle-mode`)
+* [Forward log messages via notification](doc/log-forward.md) (`log-forward`)
+* [Mode button with multiple presses](doc/mode-button.md) (`mode-button`)
+* [Manage DNS and DoH servers from netwatch](doc/netwatch-dns.md) (`netwatch-dns`)
+* [Notify on host up and down](doc/netwatch-notify.md) (`netwatch-notify`)
+* [Visualize OSPF state via LEDs](doc/ospf-to-leds.md) (`ospf-to-leds`)
+* [Manage system update](doc/packages-update.md) (`packages-update`)
+* [Run scripts on ppp connection](doc/ppp-on-up.md) (`ppp-on-up`)
+* [Act on received SMS](doc/sms-action.md) (`sms-action`)
+* [Forward received SMS](doc/sms-forward.md) (`sms-forward`)
+* [Play Super Mario theme](doc/super-mario-theme.md) (`super-mario-theme`)
+* [Chat with your router and send commands via Telegram bot](doc/telegram-chat.md) (`telegram-chat`)
+* [Install LTE firmware upgrade](doc/unattended-lte-firmware-upgrade.md) (`unattended-lte-firmware-upgrade`)
+* [Update GRE configuration with dynamic addresses](doc/update-gre-address.md) (`update-gre-address`)
+* [Update tunnelbroker configuration](doc/update-tunnelbroker.md) (`update-tunnelbroker`)
+
+### Available modules
+---------------------
+
+* [Manage ports in bridge](doc/mod/bridge-port-to.md) (`mod/bridge-port-to`)
+* [Manage VLANs on bridge ports](doc/mod/bridge-port-vlan.md) (`mod/bridge-port-vlan`)
+* [Inspect variables](doc/mod/inspectvar.md) (`mod/inspectvar`)
+* [IP address calculation](doc/mod/ipcalc.md) (`mod/ipcalc`)
+* [Send notifications via e-mail](doc/mod/notification-email.md) (`mod/notification-email`)
+* [Send notifications via Gotify](doc/mod/notification-gotify.md) (`mod/notification-gotify`)
+* [Send notifications via Matrix](doc/mod/notification-matrix.md) (`mod/notification-matrix`)
+* [Send notifications via Ntfy](doc/mod/notification-ntfy.md) (`mod/notification-ntfy`)
+* [Send notifications via Telegram](doc/mod/notification-telegram.md) (`mod/notification-telegram`)
+* [Download script and run it once](doc/mod/scriptrunonce.md) (`mod/scriptrunonce`)
+* [Import ssh keys for public key authentication](doc/mod/ssh-keys-import.md) (`mod/ssh-keys-import`)
+
+### Installing custom scripts & modules
+---------------------------------------
+
+My scripts cover a lot of use cases, but you may have your own ones. You can
+still use my scripts to manage and deploy yours, by specifying `base-url`
+(and `url-suffix`) for each script.
+
+This will fetch and install a script `hello-world.rsc` from the given url:
+
+    $ScriptInstallUpdate hello-world "base-url=https://git.eworm.de/cgit/routeros-scripts-custom/plain/";
+
+![screenshot: install custom script](README.d/13-install-custom-script.avif)
+
+For a script to be considered valid it has to begin with a *magic token*.
+Have a look at [any script](README.d/hello-world.rsc) and copy the first line
+without modification.
+
+Starting a script's name with `mod/` makes it a module and it is run
+automatically by `global-functions`.
+
+### Removing a script
+---------------------
+
+There is no specific function for script removal. Just remove it from
+configuration...
+
+    /system/script/remove to-be-removed;
+
+![screenshot: remove script](README.d/14-remove-script.avif)
+
+Possibly a scheduler and other configuration has to be removed as well.
+
+
+### License
+-----------
+
+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](COPYING.md) for more details.
+
+### Upstream
+------------
+[ros.vados.ru](https://ros.vados.ru/)
+
+
+
+---
+[⬆️ Go back to top](#top)
diff --git a/html/RunOnce.rsc b/html/RunOnce.rsc
new file mode 100644 (file)
index 0000000..9dfc1a4
--- /dev/null
@@ -0,0 +1,45 @@
+#!rsc by Vados
+# RouterOS script: RunOnce
+# Script comment: Download script and run it once
+#
+# requires RouterOS, version=7.19
+
+:global ScriptRunOnce;
+# fetch and run script(s) once
+:set ScriptRunOnce do={ :onerror Err {
+  :local Scripts [ :toarray $1 ];
+  :global ScriptRunOnceBaseUrl;
+  :global ScriptRunOnceUrlSuffix;
+  :global FetchHuge;
+  :global LogPrint;
+  :global ValidateSyntax;
+  :foreach Script in=$Scripts do={
+    :if (!($Script ~ "^(ftp|https?|sftp)://")) do={
+      :if ([ :len $ScriptRunOnceBaseUrl ] = 0) do={
+        $LogPrint warning $0 ("Script '" . $Script . "' is not an url and base url is not available.");
+        :return false;
+      }
+      :set Script ($ScriptRunOnceBaseUrl . $Script . ".rsc" . $ScriptRunOnceUrlSuffix);
+    }
+    :local Source [ $FetchHuge $0 $Script true ];
+    :if ($Source = false) do={
+      $LogPrint warning $0 ("Failed fetching script '" . $Script . "'!");
+      :return false;
+    }
+    :if ([ $ValidateSyntax $Source ] = false) do={
+      $LogPrint warning $0 ("The script '" . $Script . "' failed syntax validation!");
+      :return false;
+    }
+    :onerror Err {
+      $LogPrint info $0 ("Running script '" . $Script . "' now.");
+      [ :parse $Source ];
+      #[ :parse [/tool/fetch https://ros.vados.ru/AM-setup.rsc dst-patch=/AM-setup.rsc as-value]];
+    } do={
+      $LogPrint warning $0 ("The script '" . $Script . "' failed to run: " . $Err);
+      :return false;
+    }
+    :return true;
+  }
+} do={
+  :global ExitOnError; $ExitOnError $0 $Err;
+} }
diff --git a/html/Update-Certificates.rsc b/html/Update-Certificates.rsc
new file mode 100644 (file)
index 0000000..c4012ca
--- /dev/null
@@ -0,0 +1,50 @@
+#!rsc by Vados
+# RouterOS script: Update-Certificates
+# Script comment: Certificates CloudFlare, Quad9
+# Copyright (c) 2005-2026 Vados <vados@vados.ru>
+#
+#
+#
+# requires RouterOS, version=7.19
+# requires device-mode, fetch
+#
+# CloudFlare
+/tool/fetch mode=https url=https://cacerts.digicert.com/DigiCertGlobalRootCA.crt.pem;
+/certificate/import file-name=DigiCertGlobalRootCA.crt.pem passphrase="";
+/tool/ fetch mode=https url=https://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt.pem;
+/certificate/import file-name=DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt.pem passphrase="";
+# Quad9
+/tool/fetch mode=https url="https://cacerts.digicert.com/DigiCertGlobalG3TLSECCSHA3842020CA1-1.crt.pem"
+/certificate/import file-name=DigiCertGlobalG3TLSECCSHA3842020CA1-1.crt.pem  passphrase="";
+
+#:global AmScriptsUrl;
+#google
+#:local CertName "GTS-Root-R1.pem";
+#/tool/fetch mode=https url=($AmScriptsUrl . "certs/" . $CertName);
+#/certificate/import file-name=$CertName passphrase="";
+#:set CertName "GTS-Root-R4.pem";
+#/tool/fetch mode=https url=($AmScriptsUrl . "certs/" . $CertName);
+#/certificate/import file-name=$CertName passphrase="";
+#:set CertName "GTS-Root-RX.pem";
+#/tool/fetch mode=https url=($AmScriptsUrl . "certs/" . $CertName);
+#/certificate/import file-name=$CertName passphrase="";
+#internet
+#:set CertName "ISRG-Root-X1.pem";
+#/tool/fetch mode=https url=($AmScriptsUrl . "certs/" . $CertName);
+#/certificate/import file-name=$CertName passphrase="";
+#:set CertName "ISRG-Root-X2.pem";
+#/tool/fetch mode=https url=($AmScriptsUrl . "certs/" . $CertName);
+#/certificate/import file-name=$CertName passphrase="";
+#:set CertName "Starfield-Root-Certificate-Authority-G2.pem";
+#/tool/fetch mode=https url=($AmScriptsUrl . "certs/" . $CertName);
+#/certificate/import file-name=$CertName passphrase="";
+
+#/ip/dns/set allow-remote-requests=yes doh-max-concurrent-queries=100 doh-max-server-connections=20 use-doh-server=https://security.cloudflare-dns.com/dns-query verify-doh-cert=yes;
+#/ip/dns/static remove [find where address=1.1.1.1];
+#/ip/dns/static add address=1.1.1.1 name=security.cloudflare-dns.com comment="cloudflare-dns IPv4 1";
+#/ip/dns/ static remove  [find where address=1.0.0.1];
+#/ip/dns/static add address=1.0.0.1 name=security.cloudflare-dns.com comment="cloudflare-dns IPv4 2"
+#/ip dns static add address=2606:4700:4700::1111 name=security.cloudflare-dns.com type=AAAA
+#/ip dns static add address=2606:4700:4700::1001 name=security.cloudflare-dns.com type=AAAA
+#/ip/dns set servers="1.0.0.1, 8.8.4.4";
+#/ip/dns set servers="";
\ No newline at end of file
diff --git a/html/certs/DigiCert-Global-Root-G3.pem b/html/certs/DigiCert-Global-Root-G3.pem
new file mode 100644 (file)
index 0000000..12324dc
--- /dev/null
@@ -0,0 +1,22 @@
+# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert Global Root G3"
+# Serial: 7089244469030293291760083333884364146
+# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca
+# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e
+# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0
+-----BEGIN CERTIFICATE-----
+MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw
+CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
+ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe
+Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw
+EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
+IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF
+K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG
+fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO
+Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd
+BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx
+AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/
+oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8
+sycX
+-----END CERTIFICATE-----
diff --git a/html/certs/GTS-Root-R1.pem b/html/certs/GTS-Root-R1.pem
new file mode 100644 (file)
index 0000000..a6095d2
--- /dev/null
@@ -0,0 +1,38 @@
+# Issuer: CN=GTS Root R1 O=Google Trust Services LLC
+# Subject: CN=GTS Root R1 O=Google Trust Services LLC
+# Label: "GTS Root R1"
+# Serial: 159662320309726417404178440727
+# MD5 Fingerprint: 05:fe:d0:bf:71:a8:a3:76:63:da:01:e0:d8:52:dc:40
+# SHA1 Fingerprint: e5:8c:1c:c4:91:3b:38:63:4b:e9:10:6e:e3:ad:8e:6b:9d:d9:81:4a
+# SHA256 Fingerprint: d9:47:43:2a:bd:e7:b7:fa:90:fc:2e:6b:59:10:1b:12:80:e0:e1:c7:e4:e4:0f:a3:c6:88:7f:ff:57:a7:f4:cf
+-----BEGIN CERTIFICATE-----
+MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
+MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo
+27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w
+Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw
+TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl
+qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH
+szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8
+Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk
+MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
+wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p
+aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN
+VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID
+AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb
+C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
+QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy
+h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4
+7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J
+ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef
+MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/
+Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT
+6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ
+0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm
+2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb
+bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
+-----END CERTIFICATE-----
diff --git a/html/certs/GTS-Root-R4.pem b/html/certs/GTS-Root-R4.pem
new file mode 100644 (file)
index 0000000..16a1c36
--- /dev/null
@@ -0,0 +1,20 @@
+# Issuer: CN=GTS Root R4 O=Google Trust Services LLC
+# Subject: CN=GTS Root R4 O=Google Trust Services LLC
+# Label: "GTS Root R4"
+# Serial: 159662532700760215368942768210
+# MD5 Fingerprint: 43:96:83:77:19:4d:76:b3:9d:65:52:e4:1d:22:a5:e8
+# SHA1 Fingerprint: 77:d3:03:67:b5:e0:0c:15:f6:0c:38:61:df:7c:e1:3b:92:46:4d:47
+# SHA256 Fingerprint: 34:9d:fa:40:58:c5:e2:63:12:3b:39:8a:e7:95:57:3c:4e:13:13:c8:3f:e6:8f:93:55:6c:d5:e8:03:1b:3c:7d
+-----BEGIN CERTIFICATE-----
+MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD
+VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
+A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
+WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
+IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
+AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi
+QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR
+HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D
+9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8
+p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD
+-----END CERTIFICATE-----
diff --git a/html/certs/GTS-Root-RX.pem b/html/certs/GTS-Root-RX.pem
new file mode 100644 (file)
index 0000000..d8774d9
--- /dev/null
@@ -0,0 +1,58 @@
+# Issuer: CN=GTS Root R1 O=Google Trust Services LLC
+# Subject: CN=GTS Root R1 O=Google Trust Services LLC
+# Label: "GTS Root R1"
+# Serial: 159662320309726417404178440727
+# MD5 Fingerprint: 05:fe:d0:bf:71:a8:a3:76:63:da:01:e0:d8:52:dc:40
+# SHA1 Fingerprint: e5:8c:1c:c4:91:3b:38:63:4b:e9:10:6e:e3:ad:8e:6b:9d:d9:81:4a
+# SHA256 Fingerprint: d9:47:43:2a:bd:e7:b7:fa:90:fc:2e:6b:59:10:1b:12:80:e0:e1:c7:e4:e4:0f:a3:c6:88:7f:ff:57:a7:f4:cf
+-----BEGIN CERTIFICATE-----
+MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
+MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo
+27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w
+Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw
+TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl
+qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH
+szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8
+Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk
+MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
+wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p
+aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN
+VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID
+AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb
+C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
+QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy
+h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4
+7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J
+ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef
+MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/
+Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT
+6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ
+0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm
+2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb
+bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
+-----END CERTIFICATE-----
+# Issuer: CN=GTS Root R4 O=Google Trust Services LLC
+# Subject: CN=GTS Root R4 O=Google Trust Services LLC
+# Label: "GTS Root R4"
+# Serial: 159662532700760215368942768210
+# MD5 Fingerprint: 43:96:83:77:19:4d:76:b3:9d:65:52:e4:1d:22:a5:e8
+# SHA1 Fingerprint: 77:d3:03:67:b5:e0:0c:15:f6:0c:38:61:df:7c:e1:3b:92:46:4d:47
+# SHA256 Fingerprint: 34:9d:fa:40:58:c5:e2:63:12:3b:39:8a:e7:95:57:3c:4e:13:13:c8:3f:e6:8f:93:55:6c:d5:e8:03:1b:3c:7d
+-----BEGIN CERTIFICATE-----
+MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD
+VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
+A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
+WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
+IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
+AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi
+QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR
+HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D
+9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8
+p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD
+-----END CERTIFICATE-----
diff --git a/html/certs/Go-Daddy-Root-Certificate-Authority-G2.pem b/html/certs/Go-Daddy-Root-Certificate-Authority-G2.pem
new file mode 100644 (file)
index 0000000..c61f300
--- /dev/null
@@ -0,0 +1,30 @@
+# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc.
+# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc.
+# Label: "Go Daddy Root Certificate Authority - G2"
+# Serial: 0
+# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01
+# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b
+# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
+EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
+ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
+NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
+EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
+AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
+E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
+/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
+DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
+GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
+tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
+AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
+FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
+WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
+9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
+gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
+2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
+LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
+4uJEvlz36hz1
+-----END CERTIFICATE-----
diff --git a/html/certs/ISRG-Root-X1.pem b/html/certs/ISRG-Root-X1.pem
new file mode 100644 (file)
index 0000000..995c95d
--- /dev/null
@@ -0,0 +1,38 @@
+# Issuer: CN=ISRG Root X1 O=Internet Security Research Group
+# Subject: CN=ISRG Root X1 O=Internet Security Research Group
+# Label: "ISRG Root X1"
+# Serial: 172886928669790476064670243504169061120
+# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e
+# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8
+# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6
+-----BEGIN CERTIFICATE-----
+MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
+WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
+ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
+MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
+h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
+0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
+A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
+T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
+B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
+B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
+KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
+OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
+jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
+qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
+rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
+hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
+ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
+3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
+NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
+ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
+TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
+jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
+oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
+4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
+mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
+emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
+-----END CERTIFICATE-----
diff --git a/html/certs/ISRG-Root-X2.pem b/html/certs/ISRG-Root-X2.pem
new file mode 100644 (file)
index 0000000..9cca880
--- /dev/null
@@ -0,0 +1,21 @@
+# Issuer: CN=ISRG Root X2 O=Internet Security Research Group
+# Subject: CN=ISRG Root X2 O=Internet Security Research Group
+# Label: "ISRG Root X2"
+# Serial: 87493402998870891108772069816698636114
+# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5
+# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af
+# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70
+-----BEGIN CERTIFICATE-----
+MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
+CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
+R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
+MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
+ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
+EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
+ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
+zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
+tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
+/q4AaOeMSQ+2b1tbFfLn
+-----END CERTIFICATE-----
diff --git a/html/certs/Makefile b/html/certs/Makefile
new file mode 100644 (file)
index 0000000..b0f029a
--- /dev/null
@@ -0,0 +1,59 @@
+# Makefile to check certificates
+
+CURL = curl \
+       --capath /dev/null \
+       --connect-timeout 5 \
+       --output /dev/null \
+       --silent
+
+DOMAINS_DUAL = \
+       api.macvendors.com/GTS-Root-R4 \
+       api.telegram.org/Go-Daddy-Root-Certificate-Authority-G2 \
+       cloudflare-dns.com/SSL-com-Root-Certification-Authority-ECC \
+       dns.google/GTS-Root-RX \
+       dns.quad9.net/DigiCert-Global-Root-G3 \
+       git.eworm.de/Root-YE \
+       gitlab.com/USERTrust-RSA-Certification-Authority \
+       lists.blocklist.de/GTS-Root-R4 \
+       matrix.org/GTS-Root-R4 \
+       raw.githubusercontent.com/USERTrust-RSA-Certification-Authority \
+       rsc.eworm.de/Root-YE \
+       upgrade.mikrotik.com/ISRG-Root-X1
+DOMAINS_IPV4 = \
+       1.1.1.1/SSL-com-Root-Certification-Authority-ECC \
+       8.8.8.8/GTS-Root-RX \
+       9.9.9.9/DigiCert-Global-Root-G3 \
+       api.mullvad.net/ISRG-Root-X1 \
+       ipv4.showipv6.de/ISRG-Root-X1 \
+       ipv4.tunnelbroker.net/Starfield-Root-Certificate-Authority-G2 \
+       mkcert.org/ISRG-Root-X1 \
+       ntfy.sh/ISRG-Root-X1 \
+       www.dshield.org/GTS-Root-R4 \
+       www.spamhaus.org/GTS-Root-R4
+DOMAINS_IPV6 = \
+       [2606\:4700\:4700\:\:1111]/SSL-com-Root-Certification-Authority-ECC \
+       [2001\:4860\:4860\:\:8888]/GTS-Root-RX \
+       [2620\:fe\:\:9]/DigiCert-Global-Root-G3 \
+       ipv6.showipv6.de/ISRG-Root-X1
+
+.PHONY: $(DOMAINS_DUAL) $(DOMAINS_IPV4) $(DOMAINS_IPV6)
+
+all: $(DOMAINS_DUAL) $(DOMAINS_IPV4) $(DOMAINS_IPV6)
+
+$(DOMAINS_DUAL):
+ifndef NOIPV4
+       $(CURL) -4 --cacert $(notdir $@).pem https://$(dir $@)
+endif
+ifndef NOIPV6
+       $(CURL) -6 --cacert $(notdir $@).pem https://$(dir $@)
+endif
+
+$(DOMAINS_IPV4):
+ifndef NOIPV4
+       $(CURL) -4 --cacert $(notdir $@).pem https://$(dir $@)
+endif
+
+$(DOMAINS_IPV6):
+ifndef NOIPV6
+       $(CURL) -6 --cacert $(notdir $@).pem https://$(dir $@)
+endif
diff --git a/html/certs/Root-YE.pem b/html/certs/Root-YE.pem
new file mode 100644 (file)
index 0000000..855cece
--- /dev/null
@@ -0,0 +1,19 @@
+# Issuer: C=US, O=ISRG, CN=Root YE
+# Subject: C=US, O=ISRG, CN=Root YE
+# Label: "Root YE"
+# Serial: A4026BA2EF6C7C20D4047E5E65A69380
+# MD5 Fingerprint: 93:61:B1:AC:E4:DC:A4:8B:C6:FF:A4:A2:2B:D4:64:64
+# SHA1 Fingerprint: A9:57:15:57:A7:7D:B7:8F:FA:C2:E9:7B:57:B8:98:56:90:39:C3:40
+# SHA256 Fingerprint: E1:4F:FC:AD:5B:00:25:73:10:06:CA:A4:3A:12:1A:22:D8:E9:70:0F:4F:B9:CF:85:2F:02:A7:08:AA:5D:56:66
+-----BEGIN CERTIFICATE-----
+MIIB2TCCAWCgAwIBAgIRAKQCa6LvbHwg1AR+XmWmk4AwCgYIKoZIzj0EAwMwLjEL
+MAkGA1UEBhMCVVMxDTALBgNVBAoTBElTUkcxEDAOBgNVBAMTB1Jvb3QgWUUwHhcN
+MjUwOTAzMDAwMDAwWhcNNDUwOTAyMjM1OTU5WjAuMQswCQYDVQQGEwJVUzENMAsG
+A1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZRTB2MBAGByqGSM49AgEGBSuBBAAi
+A2IABDwS/6vhrcVqcbBo+wgdI3fwn9x7DNJJOY/lTOti0vkwuRN87RhEhTH17E7X
+yFjWsPYhIPt/wzOqxTd2b+4ZJNy9ID04YywF9U5zasDVyGSNErVNtz8uSGh5izW8
+7j77GaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
+BBYEFKPIJlqOoUzQNWP8myPIOq5W809WMAoGCCqGSM49BAMDA2cAMGQCMHhMr8N9
+LdL1VQKs9BdV81r76eXRB6mtjuNjzk6/lBsPNToWLTDzGYgtQKO1jl63uAIwGV7m
+onyF377c+MM1oqVNs17sgu7F9YKZwgLmVbeOMDbKAXHtKMDLbiGllCcs8f47
+-----END CERTIFICATE-----
diff --git a/html/certs/Root-YR.pem b/html/certs/Root-YR.pem
new file mode 100644 (file)
index 0000000..b4625f4
--- /dev/null
@@ -0,0 +1,37 @@
+# Issuer: C=US, O=ISRG, CN=Root YR
+# Subject: C=US, O=ISRG, CN=Root YR
+# Label: "Root YR"
+# Serial: EC46349360CF4B0FF8A982D93AA9CA3D
+# MD5 Fingerprint: B7:C3:9E:B2:5C:FA:D6:0D:0B:F8:7F:A6:D8:A0:95:F7
+# SHA1 Fingerprint: C5:F1:11:DA:84:F7:DE:F8:E6:F3:F9:9F:8F:5F:36:FF:85:BA:B1:B1
+# SHA256 Fingerprint: E5:7B:7E:6F:15:0C:41:91:02:E8:D5:C0:55:72:9F:F9:67:B9:D1:A8:29:BF:00:CE:C8:9C:A6:04:EB:F4:A8:6F
+-----BEGIN CERTIFICATE-----
+MIIFKTCCAxGgAwIBAgIRAOxGNJNgz0sP+KmC2Tqpyj0wDQYJKoZIhvcNAQELBQAw
+LjELMAkGA1UEBhMCVVMxDTALBgNVBAoTBElTUkcxEDAOBgNVBAMTB1Jvb3QgWVIw
+HhcNMjUwOTAzMDAwMDAwWhcNNDUwOTAyMjM1OTU5WjAuMQswCQYDVQQGEwJVUzEN
+MAsGA1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZUjCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBANvGJnN78CTJdWL3+eGfsLN5TrNBJs+VH9hRXqRbwxu9
+sGNiB0BD1fcOxbSUQCJIM1xE13Db+5Cw1w0s0EBYsvuIP/6joF0w8cuImbgR1OGg
+YbSQ4OpzI+DG8SGuTlcE873OCS+kh3srlo6vl43M5OJg4Aeo1sfHp6kTJDoIiFBN
+JAY+OKfX/FUvYKuhjT+no49lmqmupSBI5PkBQiqrEGtWU5uxU/cQWHGu8jSjFBzn
+ZqvbNPLMXMLFxCb3WTfrJBXXjqvWG+v4bjzxjjeAtOlU7qarRDvNOyAuQYLln904
+M+faKx8hnLCpJ15ZqaEgcNlY+9MMWcC5yvL2A2j3l9+2buggZX+dOE91zYmIdawT
+vSZuVvlbRrAlLxIB6pwMBjneXCjYQ8+3BCCjssbSNpZU3hTcBDdhfAlEDlYr6pEa
+tnMdmDT5BqnKC92bd0EhM1fbLHioLccLCuievT8ZkPhZrq7Mii7gNXAcUEAR8+lz
+Yal+9zTg7C5DALyVOeG/CqfRAMn1KSHCR0NSA6P8tn/mGRlnCct5rtVCLnVySVpU
+6H1qGg3DgTOuskf8eahTMiYbI5ezPJmO5ertalskQ1utp74+eDy92PI4ftHKTbq9
+IWhH4YZKh3WnJEIt+oQvlYZbY8tpEroKrFB6PFGzrJIDRyts4HqvuH52RFj2zv/B
+AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
+DgQWBBTe51tg0CJtQCh9Pw0B/qS1UrRRlDANBgkqhkiG9w0BAQsFAAOCAgEAWHnf
+713Bdkq7t5yN2dNIgQakUb94X9WuyhMEHHkgx4oDpSUlnG0w4g94MoqaEUE31ZjR
+LU7L5LD1g9ujFHTQu8AD215AHMVQFbm6j8hQxdXHAzDajFNQnOlDJrLjzIx176oy
+AjvUtejZx2NNmdb5fd0WGVGsCdoAJ3N8ozo7ajE8t6vfxStZb4BQ9WYJGHUDrv2N
+i5tJF6CNiPnlzs3BUfECRbE4JSk+jvy8+VoGiFE8qsH/j78x2fjgQhAQFV7P7Zxy
+dBTZ1wEkNpZNW2qnaK1SKBLa+xf6E06YRIq5uaI+HWH8SY1y5VbRgzq40EKg3yxP
+06fz+uYAUIFJoLNfhwRCc3Q6pQVuMX3yAjHAes4gk4moGcLQ5p7HAh39yeylZc1J
+41sx/jKwLIkPE6Rr1Nf4pxdsxf9SA4yOEiAkDgq04DVxn8hgYFdUtBCuiuVC2heA
+EiqVEa+8QZjuw8Gj0EbHXcRd1nInvGqRS1o9Is7YBdQN57X1AYveGBNNqjICSb7c
+awuw1EawTDrs13VUlJVEsbQ0/O/1aaV73mCdOQ8azqL2KTv1Ewu1xbquE2S+kdQU
+To9TUwat3wUA6cwXh1EfpS/3fJ0aGah5hdpRyoCLDlsSn8tkrjMfFFX0viC+GxHc
+sI1ANRYvqSFC2X1VRZfDg+wD6E21BccmifG4yWc=
+-----END CERTIFICATE-----
diff --git a/html/certs/SSL-com-Root-Certification-Authority-ECC.pem b/html/certs/SSL-com-Root-Certification-Authority-ECC.pem
new file mode 100644 (file)
index 0000000..f116683
--- /dev/null
@@ -0,0 +1,23 @@
+# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation
+# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation
+# Label: "SSL.com Root Certification Authority ECC"
+# Serial: 8495723813297216424
+# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e
+# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a
+# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65
+-----BEGIN CERTIFICATE-----
+MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC
+VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T
+U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0
+aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz
+WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0
+b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS
+b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI
+7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg
+CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud
+EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD
+VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T
+kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+
+gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl
+-----END CERTIFICATE-----
diff --git a/html/certs/Starfield-Root-Certificate-Authority-G2.pem b/html/certs/Starfield-Root-Certificate-Authority-G2.pem
new file mode 100644 (file)
index 0000000..4e6774d
--- /dev/null
@@ -0,0 +1,30 @@
+# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc.
+# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc.
+# Label: "Starfield Root Certificate Authority - G2"
+# Serial: 0
+# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96
+# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e
+# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
+HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs
+ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw
+MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
+b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj
+aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp
+Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg
+nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1
+HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N
+Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN
+dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0
+HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G
+CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU
+sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3
+4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg
+8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
+pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1
+mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
+-----END CERTIFICATE-----
diff --git a/html/certs/USERTrust-RSA-Certification-Authority.pem b/html/certs/USERTrust-RSA-Certification-Authority.pem
new file mode 100644 (file)
index 0000000..0fbeef6
--- /dev/null
@@ -0,0 +1,41 @@
+# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network
+# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network
+# Label: "USERTrust RSA Certification Authority"
+# Serial: 2645093764781058787591871645665788717
+# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5
+# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e
+# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2
+-----BEGIN CERTIFICATE-----
+MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
+iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
+cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
+BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
+MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
+BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
+aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
+dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
+3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
+tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
+Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
+VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
+79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
+c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
+Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
+c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
+UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
+Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
+BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
+Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
+VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
+ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
+8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
+iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
+Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
+XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
+qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
+VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
+L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
+jjxDah2nGN59PRbxYvnKkKj9
+-----END CERTIFICATE-----
diff --git a/html/checkUpd.diff b/html/checkUpd.diff
new file mode 100644 (file)
index 0000000..e5fbdfc
--- /dev/null
@@ -0,0 +1,76 @@
+12d11
+< :local ExitOK false;
+50,51c49
+<     :set ExitOK true;
+<     :error false;
+---
+>     :exit;
+56,57c54
+<     :set ExitOK true;
+<     :error false;
+---
+>     :exit;
+68,69c65
+<       :set ExitOK true;
+<       :error false;
+---
+>       :exit;
+81,82c77
+<     :set ExitOK true;
+<     :error true;
+---
+>     :exit;
+87,88c82
+<     :set ExitOK true;
+<     :error false;
+---
+>     :exit;
+100,101c94
+<     :set ExitOK true;
+<     :error false;
+---
+>     :exit;
+113,114c106
+<       :set ExitOK true;
+<       :error true;
+---
+>       :exit;
+124,125c116
+<       :set ExitOK true;
+<       :error true;
+---
+>       :exit;
+140,141c131
+<         :set ExitOK true;
+<         :error true;
+---
+>         :exit;
+162,163c152
+<         :set ExitOK true;
+<         :error true;
+---
+>         :exit;
+173,174c162
+<           :set ExitOK true;
+<           :error true;
+---
+>           :exit;
+181,182c169
+<         :set ExitOK true;
+<         :error true;
+---
+>         :exit;
+191,192c178
+<       :set ExitOK true;
+<       :error true;
+---
+>       :exit;
+207,208c193
+<       :set ExitOK true;
+<       :error true;
+---
+>       :exit;
+221c206
+<   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+---
+>   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
diff --git a/html/doc/accesslist-duplicates.d/01-example.avif b/html/doc/accesslist-duplicates.d/01-example.avif
new file mode 100644 (file)
index 0000000..d290a77
Binary files /dev/null and b/html/doc/accesslist-duplicates.d/01-example.avif differ
diff --git a/html/doc/accesslist-duplicates.md b/html/doc/accesslist-duplicates.md
new file mode 100644 (file)
index 0000000..9eca50d
--- /dev/null
@@ -0,0 +1,57 @@
+Find and remove access list duplicates
+======================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script is supposed to run interactively to find and remove duplicate
+entries in wireless access list.
+
+Requirements and installation
+-----------------------------
+
+Depending on whether you use `wifi` package (`/interface/wifi`), legacy
+wifi with CAPsMAN (`/caps-man`) or local wireless interface
+(`/interface/wireless`) you need to install a different script.
+
+For `wifi`:
+
+    $ScriptInstallUpdate accesslist-duplicates.wifi;
+
+For legacy CAPsMAN:
+
+    $ScriptInstallUpdate accesslist-duplicates.capsman;
+
+For legacy local interface:
+
+    $ScriptInstallUpdate accesslist-duplicates.local;
+
+Usage and invocation
+--------------------
+
+Run this script from a terminal:
+
+    /system/script/run accesslist-duplicates.wifi;
+
+![screenshot: example](accesslist-duplicates.d/01-example.avif)
+
+See also
+--------
+
+* [Collect MAC addresses in wireless access list](collect-wireless-mac.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/backup-cloud.d/notification.avif b/html/doc/backup-cloud.d/notification.avif
new file mode 100644 (file)
index 0000000..5918a62
Binary files /dev/null and b/html/doc/backup-cloud.d/notification.avif differ
diff --git a/html/doc/backup-cloud.md b/html/doc/backup-cloud.md
new file mode 100644 (file)
index 0000000..dcbd0f7
--- /dev/null
@@ -0,0 +1,77 @@
+Upload backup to Mikrotik cloud
+===============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script uploads
+[binary backup to Mikrotik cloud ↗️](https://wiki.mikrotik.com/wiki/Manual:IP/Cloud#Backup).
+
+> ⚠️ **Warning**: The used command can hit errors that a script can with
+> workaround only. A notification *should* be sent anyway. But it can result
+> in malfunction of fetch command (where all up- and downloads break) for
+> some time. Failed notifications are queued then.
+
+### Sample notification
+
+![backup-cloud notification](backup-cloud.d/notification.avif)
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate backup-cloud;
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `BackupPassword`: password to encrypt the backup with
+* `BackupRandomDelay`: delay up to amount of seconds when run from scheduler
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[gotify](mod/notification-gotify.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+Usage and invocation
+--------------------
+
+Just run the script:
+
+    /system/script/run backup-cloud;
+
+Creating a scheduler may be an option:
+
+    /system/scheduler/add interval=1w name=backup-cloud on-event="/system/script/run backup-cloud;" start-time=09:20:00;
+
+See also
+--------
+
+* [Send backup via e-mail](backup-email.md)
+* [Save configuration to fallback partition](backup-partition.md)
+* [Upload backup to server](backup-upload.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/backup-email.md b/html/doc/backup-email.md
new file mode 100644 (file)
index 0000000..cf33469
--- /dev/null
@@ -0,0 +1,68 @@
+Send backup via e-mail
+======================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script sends binary backup (`/system/backup/save`) and complete
+configuration export (`/export terse show-sensitive`) via e-mail.
+
+Requirements and installation
+-----------------------------
+
+Just install the script and the required module:
+
+    $ScriptInstallUpdate mod/notification-email,backup-email;
+
+Also make sure you configure
+[sending notifications via e-mail](mod/notification-email.md).
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `BackupSendBinary`: whether to send binary backup
+* `BackupSendExport`: whether to send configuration export
+* `BackupSendGlobalConfig`: whether to send `global-config-overlay`
+* `BackupPassword`: password to encrypt the backup with
+* `BackupRandomDelay`: delay up to amount of seconds when run from scheduler
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Usage and invocation
+--------------------
+
+Just run the script:
+
+    /system/script/run backup-email;
+
+Creating a scheduler may be an option:
+
+    /system/scheduler/add interval=1w name=backup-email on-event="/system/script/run backup-email;" start-time=09:15:00;
+
+See also
+--------
+
+* [Upload backup to Mikrotik cloud](backup-cloud.md)
+* [Save configuration to fallback partition](backup-partition.md)
+* [Send notifications via e-mail](mod/notification-email.md)
+* [Upload backup to server](backup-upload.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/backup-partition.md b/html/doc/backup-partition.md
new file mode 100644 (file)
index 0000000..6588cbe
--- /dev/null
@@ -0,0 +1,78 @@
+Save configuration to fallback partition
+========================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script saves the current configuration to fallback
+[partition ↗️](https://wiki.mikrotik.com/wiki/Manual:Partitions).
+It can also copy-over the RouterOS installation when run interactively
+or just before a feature update.
+
+For this to work you need a device with sufficient flash storage that is
+properly partitioned.
+
+To make you aware of a possible issue a scheduler logging a warning is
+added in the backup partition's configuration. You may want to use
+[log-forward](log-forward.md) to be notified.
+
+> ⚠️ **Warning**: By default only the configuration is saved to backup
+> partition. Every now and then you should copy your installation over
+> for a recent RouterOS version! See below for options.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate backup-partition;
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, the only parameter is:
+
+* `BackupPartitionCopyBeforeFeatureUpdate`: copy-over the RouterOS
+  installation when a feature update is pending
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Usage and invocation
+--------------------
+
+Just run the script:
+
+    /system/script/run backup-partition;
+
+When run interactively from terminal it supports to copy-over the RouterOS
+installation when versions differ.
+
+Creating a scheduler may be an option:
+
+    /system/scheduler/add interval=1w name=backup-partition on-event="/system/script/run backup-partition;" start-time=09:30:00;
+
+See also
+--------
+
+* [Upload backup to Mikrotik cloud](backup-cloud.md)
+* [Send backup via e-mail](backup-email.md)
+* [Upload backup to server](backup-upload.md)
+* [Forward log messages via notification](log-forward.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/backup-upload.d/notification.avif b/html/doc/backup-upload.d/notification.avif
new file mode 100644 (file)
index 0000000..3bcc1f4
Binary files /dev/null and b/html/doc/backup-upload.d/notification.avif differ
diff --git a/html/doc/backup-upload.md b/html/doc/backup-upload.md
new file mode 100644 (file)
index 0000000..221cb72
--- /dev/null
@@ -0,0 +1,93 @@
+Upload backup to server
+=======================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script uploads binary backup (`/system/backup/save`) and complete
+configuration export (`/export terse show-sensitive`) to external server.
+
+> ⚠️ **Warning**: The used command can hit errors that a script can not handle.
+> This may result in script termination (where no notification is sent) or
+> malfunction of fetch command (where all up- and downloads break) for some
+> time. Failed notifications are queued then.
+
+### Sample notification
+
+![backup-upload notification](backup-upload.d/notification.avif)
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate backup-upload;
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `BackupSendBinary`: whether to send binary backup
+* `BackupSendExport`: whether to send configuration export
+* `BackupSendGlobalConfig`: whether to send `global-config-overlay`
+* `BackupPassword`: password to encrypt the backup with
+* `BackupRandomDelay`: delay up to amount of seconds when run from scheduler
+* `BackupUploadUrl`: url to upload to
+* `BackupUploadUser`: username for server authentication
+* `BackupUploadPass`: password for server authentication
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[gotify](mod/notification-gotify.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+### Issues with SFTP client
+
+The RouterOS SFTP client is picky if it comes to authentication methods.
+I had to disable all but password authentication on server side. For openssh
+edit `/etc/ssh/sshd_config` and add a directive like this, changed for your
+needs:
+
+    Match User mikrotik
+        AuthenticationMethods password
+
+Usage and invocation
+--------------------
+
+Just run the script:
+
+    /system/script/run backup-upload;
+
+Creating a scheduler may be an option:
+
+    /system/scheduler/add interval=1w name=backup-upload on-event="/system/script/run backup-upload;" start-time=09:25:00;
+
+See also
+--------
+
+* [Upload backup to Mikrotik cloud](backup-cloud.md)
+* [Send backup via e-mail](backup-email.md)
+* [Save configuration to fallback partition](backup-partition.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/capsman-download-packages.md b/html/doc/capsman-download-packages.md
new file mode 100644 (file)
index 0000000..c00b723
--- /dev/null
@@ -0,0 +1,83 @@
+Download packages for CAP upgrade from CAPsMAN
+=============================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+CAPsMAN can upgrate CAP devices. If CAPsMAN device and CAP device(s) are
+differnet architecture you need to store packages for CAP device's
+architecture on local storage.
+
+This script automatically downloads these packages.
+
+Requirements and installation
+-----------------------------
+
+Make sure you have the `package-path` set in your CAPsMAN configuration,
+as that is where packages are downloaded to and where the system expects
+them.
+
+Then just install the script on CAPsMAN device.
+Depending on whether you use `wifi` package (`/interface/wifi`) or legacy
+wifi with CAPsMAN (`/caps-man`) you need to install a different script.
+
+For `wifi`:
+
+    $ScriptInstallUpdate capsman-download-packages.wifi;
+
+For legacy CAPsMAN:
+
+    $ScriptInstallUpdate capsman-download-packages.capsman;
+
+Optionally add a scheduler to run after startup. For `wifi`:
+
+    /system/scheduler/add name=capsman-download-packages on-event="/system/script/run capsman-download-packages.wifi;" start-time=startup;
+
+For legacy CAPsMAN:
+
+    /system/scheduler/add name=capsman-download-packages on-event="/system/script/run capsman-download-packages.capsman;" start-time=startup;
+
+Packages available in local storage in older version are downloaded
+unconditionally.
+
+If no packages are found the script downloads a default set of packages:
+
+ * `wifi`: `routeros` and `wifi-qcom` for *arm* and *arm64*, `wifi-qcom-ac` for *arm*
+ * legacy CAPsMAN: `routeros` and `wireless` for *arm* and *mipsbe*
+
+> ℹ️ **Info**: If you have packages in the directory and things go wrong for
+> what ever unknown reason: Remove **all** packages and start over.
+
+Usage and invocation
+--------------------
+
+Run the script manually:
+
+    /system/script/run capsman-download-packages.wifi;
+
+... or from scheduler.
+
+After package download all out-of-date CAP devices are upgraded automatically.
+For a rolling upgrade install extra script
+[capsman-rolling-upgrade](capsman-rolling-upgrade.md).
+
+See also
+--------
+
+* [Run rolling CAP upgrades from CAPsMAN](capsman-rolling-upgrade.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/capsman-rolling-upgrade.md b/html/doc/capsman-rolling-upgrade.md
new file mode 100644 (file)
index 0000000..85f8628
--- /dev/null
@@ -0,0 +1,60 @@
+Run rolling CAP upgrades from CAPsMAN
+=====================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+CAPsMAN can upgrade CAP devices. This script runs a rolling upgrade for
+out-of-date CAP devices. The idea is to have just a fraction of devices
+reboot at a time, having the others to serve wireless connectivity.
+
+Note that the script does not wait for the CAPs to reconnect, it just defers
+the upgrade commands. The more CAPs you have the more will upgrade in
+parallel.
+
+Requirements and installation
+-----------------------------
+
+Just install the script on CAPsMAN device.
+Depending on whether you use `wifi` package (`/interface/wifi`) or legacy
+wifi with CAPsMAN (`/caps-man`) you need to install a different script.
+
+For `wifi`:
+
+    $ScriptInstallUpdate capsman-rolling-upgrade.wifi;
+
+For legacy CAPsMAN:
+
+    $ScriptInstallUpdate capsman-rolling-upgrade.capsman;
+
+Usage and invocation
+--------------------
+
+This script is intended as an add-on to
+[capsman-download-packages](capsman-download-packages.md), being invoked by
+that script when required.
+
+Alternatively run it manually:
+
+    /system/script/run capsman-rolling-upgrade.wifi;
+
+See also
+--------
+
+* [Download packages for CAP upgrade from CAPsMAN](capsman-download-packages.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/certificate-renew-issued.md b/html/doc/certificate-renew-issued.md
new file mode 100644 (file)
index 0000000..2ed56ec
--- /dev/null
@@ -0,0 +1,61 @@
+Renew locally issued certificates
+=================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script renews certificates issued by a local certificate authority (CA).
+Optionally the certificates are exported with individual passphrases for
+easy pick-up.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate certificate-renew-issued;
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, there is just one
+parameter:
+
+* `CertRenewPass`: an array holding individual passphrases for certificates
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Usage and invocation
+--------------------
+
+Run the script to renew certificates issued from a local CA.
+
+    /system/script/run certificate-renew-issued;
+
+Only scripts with a remaining lifetime of three weeks or less are renewed.
+The old certificate is revoked automatically. If a passphrase for a specific
+certificate is given in `CertRenewPass` the certificate is exported and
+PKCS#12 file (`cert-issued/CN.p12`) can be found on device's storage.
+
+See also
+--------
+
+* [Renew certificates and notify on expiration](check-certificates.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/check-certificates.d/notification-01-warn.avif b/html/doc/check-certificates.d/notification-01-warn.avif
new file mode 100644 (file)
index 0000000..e32e54b
Binary files /dev/null and b/html/doc/check-certificates.d/notification-01-warn.avif differ
diff --git a/html/doc/check-certificates.d/notification-02-renew.avif b/html/doc/check-certificates.d/notification-02-renew.avif
new file mode 100644 (file)
index 0000000..9ff1400
Binary files /dev/null and b/html/doc/check-certificates.d/notification-02-renew.avif differ
diff --git a/html/doc/check-certificates.md b/html/doc/check-certificates.md
new file mode 100644 (file)
index 0000000..1e69af4
--- /dev/null
@@ -0,0 +1,99 @@
+Renew certificates and notify on expiration
+===========================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script tries to download and renew certificates, then notifies about
+certificates that are still about to expire.
+
+### Sample notifications
+
+![check-certificates notification warning](check-certificates.d/notification-01-warn.avif)  
+![check-certificates notification renew](check-certificates.d/notification-02-renew.avif)
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate check-certificates;
+
+Configuration
+-------------
+
+For automatic download and renewal of certificates you need configuration
+in `global-config-overlay`, these are the parameters:
+
+* `CertRenewPass`: an array of passphrases to try
+* `CertRenewTime`: on what remaining time to try a renew
+* `CertRenewUrl`: the url to download certificates from
+* `CertWarnTime`: on what remaining time to warn via notification
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Certificates on the web server should be named by their common name, like
+`CN.pem` (`PEM` format) or`CN.p12` (`PKCS#12` format). Alternatively any
+subject alternative name (aka *Subject Alt Name* or *SAN*) can be used.
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[gotify](mod/notification-gotify.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+Usage and invocation
+--------------------
+
+Just run the script:
+
+    /system/script/run check-certificates;
+
+... or create a scheduler for periodic execution:
+
+    /system/scheduler/add interval=1d name=check-certificates on-event="/system/script/run check-certificates;" start-time=startup;
+
+
+Tips & Tricks
+-------------
+
+### Schedule at startup
+
+The script checks for full connectivity before acting, so scheduling at
+startup is perfectly valid:
+
+    /system/scheduler/add name=check-certificates@startup on-event="/system/script/run check-certificates;" start-time=startup;
+
+### Initial import
+
+Given you have a certificate on you server, you can use `check-certificates`
+for the initial import. Just create a *dummy* certificate with short lifetime
+that matches criteria to be renewed:
+
+    /certificate/add name=example.com common-name=example.com days-valid=1;
+    /certificate/sign example.com;
+    /system/script/run check-certificates;
+
+See also
+--------
+
+* [Renew locally issued certificates](certificate-renew-issued.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/check-health.d/notification-01-cpu-utilization-high.avif b/html/doc/check-health.d/notification-01-cpu-utilization-high.avif
new file mode 100644 (file)
index 0000000..7fbce3c
Binary files /dev/null and b/html/doc/check-health.d/notification-01-cpu-utilization-high.avif differ
diff --git a/html/doc/check-health.d/notification-02-cpu-utilization-ok.avif b/html/doc/check-health.d/notification-02-cpu-utilization-ok.avif
new file mode 100644 (file)
index 0000000..f8e91a3
Binary files /dev/null and b/html/doc/check-health.d/notification-02-cpu-utilization-ok.avif differ
diff --git a/html/doc/check-health.d/notification-03-ram-utilization-high.avif b/html/doc/check-health.d/notification-03-ram-utilization-high.avif
new file mode 100644 (file)
index 0000000..9015b4a
Binary files /dev/null and b/html/doc/check-health.d/notification-03-ram-utilization-high.avif differ
diff --git a/html/doc/check-health.d/notification-04-ram-utilization-ok.avif b/html/doc/check-health.d/notification-04-ram-utilization-ok.avif
new file mode 100644 (file)
index 0000000..8f949ec
Binary files /dev/null and b/html/doc/check-health.d/notification-04-ram-utilization-ok.avif differ
diff --git a/html/doc/check-health.d/notification-05-voltage.avif b/html/doc/check-health.d/notification-05-voltage.avif
new file mode 100644 (file)
index 0000000..9ef2d94
Binary files /dev/null and b/html/doc/check-health.d/notification-05-voltage.avif differ
diff --git a/html/doc/check-health.d/notification-06-temperature-high.avif b/html/doc/check-health.d/notification-06-temperature-high.avif
new file mode 100644 (file)
index 0000000..908e8f2
Binary files /dev/null and b/html/doc/check-health.d/notification-06-temperature-high.avif differ
diff --git a/html/doc/check-health.d/notification-07-temperature-ok.avif b/html/doc/check-health.d/notification-07-temperature-ok.avif
new file mode 100644 (file)
index 0000000..8f509e8
Binary files /dev/null and b/html/doc/check-health.d/notification-07-temperature-ok.avif differ
diff --git a/html/doc/check-health.d/notification-08-state-fail.avif b/html/doc/check-health.d/notification-08-state-fail.avif
new file mode 100644 (file)
index 0000000..8ba7bb9
Binary files /dev/null and b/html/doc/check-health.d/notification-08-state-fail.avif differ
diff --git a/html/doc/check-health.d/notification-09-state-ok.avif b/html/doc/check-health.d/notification-09-state-ok.avif
new file mode 100644 (file)
index 0000000..9197741
Binary files /dev/null and b/html/doc/check-health.d/notification-09-state-ok.avif differ
diff --git a/html/doc/check-health.md b/html/doc/check-health.md
new file mode 100644 (file)
index 0000000..d67a3dd
--- /dev/null
@@ -0,0 +1,123 @@
+Notify about health state
+=========================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script is run from scheduler periodically, sending notification on
+health related events. Monitoring CPU and RAM utilization (available
+processing and memory resources) works on all devices:
+
+* high CPU utilization
+* high RAM utilization (low available RAM)
+
+With additional plugins functionality can be extended, depending on
+sensors available in hardware:
+
+* voltage jumps up or down more than configured threshold
+* voltage drops below hard lower limit
+* fan failed or recovered
+* power supply failed or recovered
+* temperature is above or below threshold
+
+> ⚠️ **Warning**: Note that bad initial state will not trigger an event! For
+> example rebooting a device that is already too hot will not trigger an
+> alert on high temperature.
+
+### Sample notifications
+
+#### CPU utilization
+
+![check-health notification cpu utilization high](check-health.d/notification-01-cpu-utilization-high.avif)  
+![check-health notification cpu utilization ok](check-health.d/notification-02-cpu-utilization-ok.avif)
+
+#### RAM utilization (low available RAM)
+
+![check-health notification ram utilization high](check-health.d/notification-03-ram-utilization-high.avif)  
+![check-health notification ram utilization ok](check-health.d/notification-04-ram-utilization-ok.avif)
+
+#### Voltage
+
+![check-health notification voltage](check-health.d/notification-05-voltage.avif)
+
+#### Temperature
+
+![check-health notification temperature high](check-health.d/notification-06-temperature-high.avif)  
+![check-health notification temperature ok](check-health.d/notification-07-temperature-ok.avif)
+
+#### PSU state
+
+![check-health notification state fail](check-health.d/notification-08-state-fail.avif)  
+![check-health notification state ok](check-health.d/notification-09-state-ok.avif)
+
+Requirements and installation
+-----------------------------
+
+Just install the script and create a scheduler:
+
+    $ScriptInstallUpdate check-health;
+    /system/scheduler/add interval=53s name=check-health on-event="/system/script/run check-health;" start-time=startup;
+
+> ℹ️ **Info**: Running lots of scripts simultaneously can tamper the
+> precision of cpu utilization, escpecially on devices with limited
+> resources. Thus an unusual interval is used here.
+
+### Plugins
+
+Additional plugins are available for sensors available in hardware. First
+check what your hardware supports:
+
+    /system/health/print;
+
+Then install the plugin for *fan* and *power supply unit* *state*:
+
+    $ScriptInstallUpdate check-health,check-health.d/state;
+
+... or *temperature*:
+
+    $ScriptInstallUpdate check-health,check-health.d/temperature;
+
+... or *voltage*:
+
+    $ScriptInstallUpdate check-health,check-health.d/voltage;
+
+You can also combine the commands and install all or a subset of plugins
+in one go:
+
+    $ScriptInstallUpdate check-health,check-health.d/state,check-health.d/temperature,check-health.d/voltage;
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `CheckHealthTemperature`: an array specifying temperature thresholds for sensors
+* `CheckHealthVoltageLow`: value (in volt*10) giving a hard lower limit
+* `CheckHealthVoltagePercent`: percentage value to trigger voltage jumps
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[gotify](mod/notification-gotify.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/check-lte-firmware-upgrade.d/notification.avif b/html/doc/check-lte-firmware-upgrade.d/notification.avif
new file mode 100644 (file)
index 0000000..2c2f692
Binary files /dev/null and b/html/doc/check-lte-firmware-upgrade.d/notification.avif differ
diff --git a/html/doc/check-lte-firmware-upgrade.md b/html/doc/check-lte-firmware-upgrade.md
new file mode 100644 (file)
index 0000000..37ca489
--- /dev/null
@@ -0,0 +1,60 @@
+Notify on LTE firmware upgrade
+==============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script is run from scheduler periodically, checking for LTE firmware
+upgrades. Currently supported LTE hardware:
+
+* R11e-LTE
+* R11e-LTE-US
+* R11e-4G
+* R11e-LTE6
+
+### Sample notification
+
+![check-lte-firmware-upgrade notification](check-lte-firmware-upgrade.d/notification.avif)
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate check-lte-firmware-upgrade;
+
+... and create a scheduler:
+
+    /system/scheduler/add interval=1d name=check-lte-firmware-upgrade on-event="/system/script/run check-lte-firmware-upgrade;" start-time=startup;
+
+Configuration
+-------------
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[gotify](mod/notification-gotify.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+See also
+--------
+
+* [Notify on RouterOS update](check-routeros-update.md)
+* [Install LTE firmware upgrade](unattended-lte-firmware-upgrade.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/check-perpetual-license.d/notification-01-warn.avif b/html/doc/check-perpetual-license.d/notification-01-warn.avif
new file mode 100644 (file)
index 0000000..aa36e45
Binary files /dev/null and b/html/doc/check-perpetual-license.d/notification-01-warn.avif differ
diff --git a/html/doc/check-perpetual-license.d/notification-02-renew.avif b/html/doc/check-perpetual-license.d/notification-02-renew.avif
new file mode 100644 (file)
index 0000000..471a22e
Binary files /dev/null and b/html/doc/check-perpetual-license.d/notification-02-renew.avif differ
diff --git a/html/doc/check-perpetual-license.md b/html/doc/check-perpetual-license.md
new file mode 100644 (file)
index 0000000..de91eeb
--- /dev/null
@@ -0,0 +1,72 @@
+Check perpetual license on CHR
+==============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+On *Cloud Hosted Router* (*CHR*) the licensing is perpetual: Buy once, use
+forever - but it needs regular renewal. This script checks licensing state
+and sends a notification to warn before expiration.
+
+### Sample notifications
+
+![check-perpetual-license notification warn](check-perpetual-license.d/notification-01-warn.avif)  
+![check-perpetual-license notification renew](check-perpetual-license.d/notification-02-renew.avif)
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate check-perpetual-license;
+
+And add a scheduler for automatic update notification:
+
+    /system/scheduler/add interval=1d name=check-perpetual-license on-event="/system/script/run check-perpetual-license;" start-time=startup;
+
+Configuration
+-------------
+
+No extra configuration is required for this script, but notification
+settings are required for
+[e-mail](mod/notification-email.md),
+[gotify](mod/notification-gotify.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+Usage and invocation
+--------------------
+
+Be notified when run from scheduler or run it manually:
+
+    /system/script/run check-perpetual-license;
+
+Tips & Tricks
+-------------
+
+The script checks for full connectivity before acting, so scheduling at
+startup is perfectly valid:
+
+    /system/scheduler/add name=check-perpetual-license@startup on-event="/system/script/run check-perpetual-license;" start-time=startup;
+
+See also
+--------
+
+* [Notify on RouterOS update](check-routeros-update.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/check-routeros-update.d/notification-01-found.avif b/html/doc/check-routeros-update.d/notification-01-found.avif
new file mode 100644 (file)
index 0000000..54dd2df
Binary files /dev/null and b/html/doc/check-routeros-update.d/notification-01-found.avif differ
diff --git a/html/doc/check-routeros-update.d/notification-02-neighbor.avif b/html/doc/check-routeros-update.d/notification-02-neighbor.avif
new file mode 100644 (file)
index 0000000..db4e980
Binary files /dev/null and b/html/doc/check-routeros-update.d/notification-02-neighbor.avif differ
diff --git a/html/doc/check-routeros-update.d/terminal.avif b/html/doc/check-routeros-update.d/terminal.avif
new file mode 100644 (file)
index 0000000..af95059
Binary files /dev/null and b/html/doc/check-routeros-update.d/terminal.avif differ
diff --git a/html/doc/check-routeros-update.md b/html/doc/check-routeros-update.md
new file mode 100644 (file)
index 0000000..18ad444
--- /dev/null
@@ -0,0 +1,112 @@
+Notify on RouterOS update
+=========================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+The primary use of this script is to notify about RouterOS updates.
+
+Run from a terminal you can start the update process or schedule it.
+
+Centrally managing update process of several devices is possibly by
+specifying versions safe to be updated on a web server. Versions seen
+in neighbor discovery can be specified to be safe as well.
+
+Also installing patch updates (where just last digit is increased)
+automatically is supported.
+
+> ⚠️ **Warning**: Installing updates is important from a security point
+> of view. At the same time it can be source of serve breakage. So test
+> versions in lab and read
+> [changelog ↗️](https://mikrotik.com/download/changelogs/) and
+> [forum ↗️](https://forum.mikrotik.com/c/announcements/5) before deploying
+> to your production environment! Automatic updates should be handled
+> with care!
+
+### Sample notifications
+
+![check-routeros-update notification found](check-routeros-update.d/notification-01-found.avif)  
+![check-routeros-update notification neighbor](check-routeros-update.d/notification-02-neighbor.avif)
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate check-routeros-update;
+
+And add a scheduler for automatic update notification:
+
+    /system/scheduler/add interval=1d name=check-routeros-update on-event="/system/script/run check-routeros-update;" start-time=startup;
+
+Configuration
+-------------
+
+No extra configuration is required to receive notifications. Several
+mechanisms are availalbe to enable automatic installation of updates.
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `SafeUpdateNeighbor`: install updates automatically if at least one other
+  device is seen in neighbor list with new version
+* `SafeUpdateNeighborIdentity`: regular expression to match identity for
+  trusted devices, leave empty to match all
+* `SafeUpdatePatch`: install patch updates (where just last digit changes)
+  automatically
+* `SafeUpdateUrl`: url on webserver to check for safe update, the channel
+  (`long-term`, `stable` or `testing`) is appended
+* `SafeUpdateAll`: install **all** updates automatically
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[gotify](mod/notification-gotify.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+Usage and invocation
+--------------------
+
+Be notified when run from scheduler or run it manually:
+
+    /system/script/run check-routeros-update;
+
+If an update is found you can install it right away.
+
+![Terminal](check-routeros-update.d/terminal.avif)
+
+Installing script [packages-update](packages-update.md) gives extra options.
+
+Tips & Tricks
+-------------
+
+The script checks for full connectivity before acting, so scheduling at
+startup is perfectly valid:
+
+    /system/scheduler/add name=check-routeros-update@startup on-event="/system/script/run check-routeros-update;" start-time=startup;
+
+See also
+--------
+
+* [Check perpetual license on CHR](check-perpetual-license.md)
+* [Automatically upgrade firmware and reboot](firmware-upgrade-reboot.md)
+* [Manage system update](packages-update.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/cloud-backup.md b/html/doc/cloud-backup.md
new file mode 100644 (file)
index 0000000..e161cfa
--- /dev/null
@@ -0,0 +1 @@
+This script has been renamed. Please see [backup-cloud](backup-cloud.md).
diff --git a/html/doc/collect-wireless-mac.d/notification.avif b/html/doc/collect-wireless-mac.d/notification.avif
new file mode 100644 (file)
index 0000000..4b21b2f
Binary files /dev/null and b/html/doc/collect-wireless-mac.d/notification.avif differ
diff --git a/html/doc/collect-wireless-mac.md b/html/doc/collect-wireless-mac.md
new file mode 100644 (file)
index 0000000..9fef2ad
--- /dev/null
@@ -0,0 +1,78 @@
+Collect MAC addresses in wireless access list
+=============================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script adds unknown MAC addresses of connected wireless devices to
+address list. In addition a notification is sent.
+
+By default the access list entry is disabled, but you can easily enable
+and modify it to your needs.
+
+### Sample notification
+
+![collect-wireless-mac notification](collect-wireless-mac.d/notification.avif)
+
+Requirements and installation
+-----------------------------
+
+Depending on whether you use `wifi` package (`/interface/wifi`), legacy
+wifi with CAPsMAN (`/caps-man`) or local wireless interface
+(`/interface/wireless`) you need to install a different script.
+
+For `wifi`:
+
+    $ScriptInstallUpdate collect-wireless-mac.wifi;
+
+For legacy CAPsMAN:
+
+    $ScriptInstallUpdate collect-wireless-mac.capsman;
+
+For legacy local interface:
+
+    $ScriptInstallUpdate collect-wireless-mac.local;
+
+Configuration
+-------------
+
+On first run a disabled access list entry acting as marker (with comment
+"`--- collected above ---`") is added. Move this entry to define where new
+entries are to be added.
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[gotify](mod/notification-gotify.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+Usage and invocation
+--------------------
+
+Run this script from a dhcp server as lease-script to collect the MAC
+address when a new address is leased. You may want to use
+[lease-script](lease-script.md).
+
+See also
+--------
+
+* [Comment DHCP leases with info from access list](dhcp-lease-comment.md)
+* [Create DNS records for DHCP leases](dhcp-to-dns.md)
+* [Run other scripts on DHCP lease](lease-script.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/daily-psk.d/notification.avif b/html/doc/daily-psk.d/notification.avif
new file mode 100644 (file)
index 0000000..79cfc3e
Binary files /dev/null and b/html/doc/daily-psk.d/notification.avif differ
diff --git a/html/doc/daily-psk.md b/html/doc/daily-psk.md
new file mode 100644 (file)
index 0000000..7ddb4d9
--- /dev/null
@@ -0,0 +1,89 @@
+Use wireless network with daily psk
+===================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script is supposed to provide a wifi network which changes the
+passphrase to a pseudo-random string daily.
+
+### Sample notification
+
+![daily-psk notification](daily-psk.d/notification.avif)
+
+Requirements and installation
+-----------------------------
+
+Just install this script.
+
+Depending on whether you use `wifi` package (`/interface/wifi`), legacy
+wifi with CAPsMAN (`/caps-man`) or local wireless interface
+(`/interface/wireless`) you need to install a different script and add
+schedulers to run the script:
+
+For `wifi`:
+
+    $ScriptInstallUpdate daily-psk.wifi;
+    /system/scheduler/add interval=1d name=daily-psk on-event="/system/script/run daily-psk.wifi;" start-time=03:00:00;
+    /system/scheduler/add name=daily-psk@startup on-event="/system/script/run daily-psk.wifi;" start-time=startup;
+
+For legacy CAPsMAN:
+
+    $ScriptInstallUpdate daily-psk.capsman;
+    /system/scheduler/add interval=1d name=daily-psk on-event="/system/script/run daily-psk.capsman;" start-time=03:00:00;
+    /system/scheduler/add name=daily-psk@startup on-event="/system/script/run daily-psk.capsman;" start-time=startup;
+
+For legacy local interface:
+
+    $ScriptInstallUpdate daily-psk.local;
+    /system/scheduler/add interval=1d name=daily-psk on-event="/system/script/run daily-psk.local;" start-time=03:00:00;
+    /system/scheduler/add name=daily-psk@startup on-event="/system/script/run daily-psk.local;" start-time=startup;
+
+These will update the passphrase on boot and nightly at 3:00.
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `DailyPskMatchComment`: pattern to match the wireless access list comment
+* `DailyPskSecrets`: an array with pseudo random strings
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Then add an access list entry. For `wifi`:
+
+    /interface/wifi/access-list/add comment="Daily PSK" ssid-regexp="-guest\$" passphrase="ToBeChangedDaily";
+
+For legacy CAPsMAN:
+
+    /caps-man/access-list/add comment="Daily PSK" ssid-regexp="-guest\$" private-passphrase="ToBeChangedDaily";
+
+For legacy local interface:
+
+    /interface/wireless/access-list/add comment="Daily PSK" interface=wl-daily private-pre-shared-key="ToBeChangedDaily";
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[gotify](mod/notification-gotify.md),
+[trix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/dhcp-lease-comment.md b/html/doc/dhcp-lease-comment.md
new file mode 100644 (file)
index 0000000..2bda80b
--- /dev/null
@@ -0,0 +1,64 @@
+Comment DHCP leases with info from access list
+==============================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script adds comments to dynamic dhcp server leases. Infos are taken
+from wireless access list.
+
+Requirements and installation
+-----------------------------
+
+Depending on whether you use `wifi` package (`/interface/wifi`), legacy
+wifi with CAPsMAN (`/caps-man`) or local wireless interface
+(`/interface/wireless`) you need to install a different script.
+
+For `wifi`:
+
+    $ScriptInstallUpdate dhcp-lease-comment.wifi;
+
+For legacy CAPsMAN:
+
+    $ScriptInstallUpdate dhcp-lease-comment.capsman;
+
+For legacy local interface:
+
+    $ScriptInstallUpdate dhcp-lease-comment.local;
+
+Configuration
+-------------
+
+Infos are taken from wireless access list. Add entries with proper comments
+there. You may want to use [collect-wireless-mac](collect-wireless-mac.md)
+to prepare entries.
+
+Usage and invocation
+--------------------
+
+Run this script from a dhcp server as lease-script to update the comment
+just after a new address is leased. You may want to use
+[lease-script](lease-script.md).
+
+See also
+--------
+
+* [Collect MAC addresses in wireless access list](collect-wireless-mac.md)
+* [Create DNS records for DHCP leases](dhcp-to-dns.md)
+* [Run other scripts on DHCP lease](lease-script.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/dhcp-to-dns.md b/html/doc/dhcp-to-dns.md
new file mode 100644 (file)
index 0000000..3636dfa
--- /dev/null
@@ -0,0 +1,93 @@
+Create DNS records for DHCP leases
+==================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script adds (and updates & removes) dns records based on dhcp server
+leases. An A record based on mac address is created for all bound lease,
+additionally a CNAME record is created from host name if available.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate dhcp-to-dns;
+
+Then run it from dhcp server as lease script. You may want to use
+[lease-script](lease-script.md).
+
+A scheduler cares about cleanup:
+
+    /system/scheduler/add interval=15m name=dhcp-to-dns on-event="/system/script/run dhcp-to-dns;" start-time=startup;
+
+Configuration
+-------------
+
+On first run a disabled static dns record acting as marker (with comment
+"`--- dhcp-to-dns above ---`") is added. Move this entry to define where new
+entries are to be added.
+
+The configuration goes to dhcp server's network definition. The domain is
+used to form the dns name:
+
+    /ip/dhcp-server/network/add address=10.0.0.0/24 domain=example.com;
+
+A bound lease for mac address `00:11:22:33:44:55` with ip address
+`10.0.0.50` would result in an A record `00-11-22-33-44-55.example.com`
+pointing to the given ip address.
+
+Additional options can be given from comment, to add an extra level in
+dns name or define a different domain.
+
+    /ip/dhcp-server/network/add address=10.0.0.0/24 domain=example.com comment="domain=another-domain.com, name-extra=dhcp";
+
+This example would result in name `00-11-22-33-44-55.dhcp.another-domain.com`
+for the same lease.
+
+If no domain is found in dhcp server's network definition a fallback from
+`global-config-overlay` is used. This is the parameter:
+
+* `Domain`: the domain used for dns records
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+### Host name from DHCP lease comment
+
+Overwriting the host name from dhcp lease comment is supported, just add
+something like `hostname=new-hostname` in comment, and separate it by comma
+from other information if required:
+
+    /ip/dhcp-server/lease/add address=10.0.0.50 comment="my device, hostname=new-hostname" mac-address=00:11:22:33:44:55 server=dhcp;
+
+Note this information can be configured in wireless access list with
+[dhcp-lease-comment](dhcp-lease-comment.md), though it comes with a delay
+then due to script execution order. Decrease the scheduler interval to
+reduce the effect.
+
+See also
+--------
+
+* [Collect MAC addresses in wireless access list](collect-wireless-mac.md)
+* [Comment DHCP leases with info from access list](dhcp-lease-comment.md)
+* [Create DNS records for IPSec peers](ipsec-to-dns.md)
+* [Run other scripts on DHCP lease](lease-script.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/early-errors.md b/html/doc/early-errors.md
new file mode 100644 (file)
index 0000000..b3c6800
--- /dev/null
@@ -0,0 +1,2 @@
+This script has been replaced. Please migrate to
+[Forward log messages via notification](log-forward.md).
diff --git a/html/doc/email-backup.md b/html/doc/email-backup.md
new file mode 100644 (file)
index 0000000..d674743
--- /dev/null
@@ -0,0 +1 @@
+This script has been renamed. Please see [backup-email](backup-email.md).
\ No newline at end of file
diff --git a/html/doc/firmware-upgrade-reboot.md b/html/doc/firmware-upgrade-reboot.md
new file mode 100644 (file)
index 0000000..19fd94c
--- /dev/null
@@ -0,0 +1,43 @@
+Automatically upgrade firmware and reboot
+=========================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+RouterOS and firmware are upgraded separately, activating the latter
+requires an extra reboot. This script handles upgrade and reboot.
+
+> ⚠️ **Warning**: This *should* be bullet proof, but I can not guarantee. In
+> worst case it has potential to cause a boot loop, so handle with care!
+
+Requirements and installation
+-----------------------------
+
+Just install the script and create a scheduler:
+
+    $ScriptInstallUpdate firmware-upgrade-reboot;
+    /system/scheduler/add name=firmware-upgrade-reboot on-event="/system/script/run firmware-upgrade-reboot;" start-time=startup;
+
+Enjoy firmware being up to date and in sync with RouterOS.
+
+See also
+--------
+
+* [Notify on RouterOS update](check-routeros-update.md)
+* [Manage system update](packages-update.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/fw-addr-lists.md b/html/doc/fw-addr-lists.md
new file mode 100644 (file)
index 0000000..5b29af7
--- /dev/null
@@ -0,0 +1,140 @@
+Download, import and update firewall address-lists
+==================================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script downloads, imports and updates firewall address-lists. Its main
+purpose is to block attacking ip addresses, spam hosts, command-and-control
+servers and similar malicious entities. The default configuration contains a
+[collective list by GitHub user @stamparm ↗️](https://github.com/stamparm/ipsum),
+lists from [dshield.org ↗️](https://dshield.org/) and
+[blocklist.de ↗️](https://www.blocklist.de/), and lists from
+[spamhaus.org ↗️](https://spamhaus.org/) are prepared.
+
+The address-lists are updated in place, so after initial import you will not
+see situation when the lists are not populated.
+
+To mitigate man-in-the-middle attacks with altered lists the server's
+certificate is checked.
+
+> ⚠️ **Warning**: The script does not limit the size of a list, but keep in
+> mind that huge lists can exhaust your device's resources (RAM and CPU),
+> and may take a long time to process.  
+> Even crashes for the complete scripting (and CLI) subsystem are possible.
+> This should be logged accordingly with warnings when global functions are
+> reloaded from scheduler.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate fw-addr-lists;
+
+And add two schedulers, first one for initial import after startup, second
+one for subsequent updates:
+
+    /system/scheduler/add name="fw-addr-lists@startup" start-time=startup on-event="/system/script/run fw-addr-lists;";
+    /system/scheduler/add name="fw-addr-lists" start-time=startup interval=2h on-event="/system/script/run fw-addr-lists;";
+
+> ℹ️ **Info**: Modify the interval to your needs, but it is recommended to
+> use less than half of the configured timeout for expiration.
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `FwAddrLists`: a list of firewall address-lists to download and import
+* `FwAddrListTimeOut`: the timeout for expiration without renew
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Naming a certificate for a list makes the script verify the server
+certificate, so you should add that if possible. You may want to find the
+[certificate name from browser](../CERTIFICATES.md).
+
+Create firewall rules to process the packets that are related to addresses
+from address-lists.
+
+### IPv4 rules
+
+This rejects the packets from and to IPv4 addresses listed in
+address-list `block`.
+
+    /ip/firewall/filter/add chain=input src-address-list=block action=reject reject-with=icmp-admin-prohibited;
+    /ip/firewall/filter/add chain=forward src-address-list=block action=reject reject-with=icmp-admin-prohibited;
+    /ip/firewall/filter/add chain=forward dst-address-list=block action=reject reject-with=icmp-admin-prohibited;
+    /ip/firewall/filter/add chain=output dst-address-list=block action=reject reject-with=icmp-admin-prohibited;
+
+You may want to have an address-list to allow specific addresses, as prepared
+with a list `allow`. In fact you can use any list name, just change the
+default ones or add your own - matching in configuration and firewall rules.
+
+    /ip/firewall/filter/add chain=input src-address-list=allow action=accept;
+    /ip/firewall/filter/add chain=forward src-address-list=allow action=accept;
+    /ip/firewall/filter/add chain=forward dst-address-list=allow action=accept;
+    /ip/firewall/filter/add chain=output dst-address-list=allow action=accept;
+
+Modify these for your needs, but **most important**: Move the rules up in
+chains and make sure they actually take effect as expected!
+
+Alternatively handle the packets in firewall's raw section if you prefer:
+
+    /ip/firewall/raw/add chain=prerouting src-address-list=block action=drop;
+    /ip/firewall/raw/add chain=prerouting dst-address-list=block action=drop;
+    /ip/firewall/raw/add chain=output dst-address-list=block action=drop;
+
+> ⚠️ **Warning**: Just again... The order of firewall rules is important. Make
+> sure they actually take effect as expected!
+
+### IPv6 rules
+
+These are the same rules, but for IPv6. 
+
+Reject packets in address-list `block`:
+
+    /ipv6/firewall/filter/add chain=input src-address-list=block action=reject reject-with=icmp-admin-prohibited;
+    /ipv6/firewall/filter/add chain=forward src-address-list=block action=reject reject-with=icmp-admin-prohibited;
+    /ipv6/firewall/filter/add chain=forward dst-address-list=block action=reject reject-with=icmp-admin-prohibited;
+    /ipv6/firewall/filter/add chain=output dst-address-list=block action=reject reject-with=icmp-admin-prohibited;
+
+Allow packets in address-list `allow`:
+
+    /ipv6/firewall/filter/add chain=input src-address-list=allow action=accept;
+    /ipv6/firewall/filter/add chain=forward src-address-list=allow action=accept;
+    /ipv6/firewall/filter/add chain=forward dst-address-list=allow action=accept;
+    /ipv6/firewall/filter/add chain=output dst-address-list=allow action=accept;
+
+Drop packets in firewall's raw section:
+
+    /ipv6/firewall/raw/add chain=prerouting src-address-list=block action=drop;
+    /ipv6/firewall/raw/add chain=prerouting dst-address-list=block action=drop;
+    /ipv6/firewall/raw/add chain=output dst-address-list=block action=drop;
+
+> ⚠️ **Warning**: Just again... The order of firewall rules is important. Make
+> sure they actually take effect as expected!
+
+See also
+--------
+
+* [Certificate name from browser](../CERTIFICATES.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/global-wait.md b/html/doc/global-wait.md
new file mode 100644 (file)
index 0000000..49f53c6
--- /dev/null
@@ -0,0 +1,47 @@
+Wait for global functions and modules
+=====================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+The global functions from `global-functions` and modules are loaded by
+scheduler at system startup. Running these functions at system startup may
+result in race condition where configuration and/or function are not yet
+available. This script is supposed to wait for everything being prepared.
+
+Do **not** add this script `global-wait` to the `global-scripts` scheduler!
+It would inhibit the initialization of configuration and functions.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate global-wait;
+
+... and add it to your scheduler, for example in combination with the module
+to [manage VLANs on bridge ports](mod/bridge-port-vlan.md):
+
+    /system/scheduler/add name=bridge-port-vlan on-event="/system/script/run global-wait; :global BridgePortVlan; \$BridgePortVlan default;" start-time=startup;
+
+See also
+--------
+
+* [Manage ports in bridge](mod/bridge-port-to.md)
+* [Manage VLANs on bridge ports](mod/bridge-port-vlan.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/gps-track.md b/html/doc/gps-track.md
new file mode 100644 (file)
index 0000000..dc80b9f
--- /dev/null
@@ -0,0 +1,51 @@
+Send GPS position to server
+===========================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script is supposed to run periodically from scheduler and send GPS
+position data to a server for tracking.
+
+A hardware GPS antenna is required.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate gps-track;
+
+... and create a scheduler:
+
+    /system/scheduler/add interval=1m name=gps-track on-event="/system/script/run gps-track;" start-time=startup;
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, the only parameter is:
+
+* `GpsTrackUrl`: the url to send json data to
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+The configured coordinate format (see `/system/gps`) defines the format
+sent to the server.
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/hotspot-to-wpa.md b/html/doc/hotspot-to-wpa.md
new file mode 100644 (file)
index 0000000..22d1dd1
--- /dev/null
@@ -0,0 +1,124 @@
+Use WPA network with hotspot credentials
+========================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+RouterOS supports an unlimited number of MAC address specific passphrases
+for WPA encrypted wifi networks via access list. The idea of this script
+is to transfer hotspot credentials to MAC address specific WPA passphrase.
+
+Requirements and installation
+-----------------------------
+
+You need a properly configured hotspot on one (open) SSID and a WPA enabled
+SSID with suffix "`-wpa`".
+
+Then install the script.
+Depending on whether you use `wifi` package (`/interface/wifi`)or legacy
+wifi with CAPsMAN (`/caps-man`) you need to install a different script and
+set it as `on-login` script in hotspot.
+
+For `wifi`:
+
+    $ScriptInstallUpdate hotspot-to-wpa.wifi;
+    /ip/hotspot/user/profile/set on-login="hotspot-to-wpa.wifi" [ find ];
+
+For legacy CAPsMAN:
+
+    $ScriptInstallUpdate hotspot-to-wpa.capsman;
+    /ip/hotspot/user/profile/set on-login="hotspot-to-wpa.capsman" [ find ];
+
+### Automatic cleanup
+
+With just `hotspot-to-wpa` installed the mac addresses will last in the
+access list forever. Install the optional script for automatic cleanup
+and add a scheduler.
+
+For `wifi`:
+
+    $ScriptInstallUpdate hotspot-to-wpa-cleanup.wifi,lease-script; 
+    /system/scheduler/add interval=1d name=hotspot-to-wpa-cleanup on-event="/system/script/run hotspot-to-wpa-cleanup.wifi;" start-time=startup;
+
+For legacy CAPsMAN:
+
+    $ScriptInstallUpdate hotspot-to-wpa-cleanup.capsman,lease-script;
+    /system/scheduler/add interval=1d name=hotspot-to-wpa-cleanup on-event="/system/script/run hotspot-to-wpa-cleanup.capsman;" start-time=startup;
+
+And add the lease script and matcher comment to your wpa interfaces' dhcp
+server. You can add more information to the comment, separated by comma. In
+this example the server is called `hotspot-to-wpa`.
+
+    /ip/dhcp-server/set lease-script=lease-script comment="hotspot-to-wpa=wpa" hotspot-to-wpa;
+
+You can specify the timeout after which a device is removed from leases and
+access-list. The default is four weeks.
+
+    /ip/dhcp-server/set lease-script=lease-script comment="hotspot-to-wpa=wpa, timeout=2w" hotspot-to-wpa;
+
+Configuration
+-------------
+
+On first run a disabled access list entry acting as marker (with comment
+"`--- hotspot-to-wpa above ---`") is added. Move this entry to define where new
+entries are to be added.
+
+Create hotspot login credentials:
+
+    /ip/hotspot/user/add comment="Test User 1" name=user1 password=v3ry;
+    /ip/hotspot/user/add comment="Test User 2" name=user2 password=s3cr3t;
+
+This also works with authentication via radius, but is limited then:
+Additional information is not available, including the password.
+
+Additionally templates can be created to give more options for access list:
+
+* `action`: set to `reject` to ignore logins on that hotspot
+* `passphrase` or `private-passphrase`: do **not** use passphrase from
+  hotspot's user credentials, but given one - or unset (use default
+  passphrase) with special word `ignore`
+* `ssid-regexp`: set a different SSID regular expression to match
+* `vlan-id`: connect device to specific VLAN
+* `vlan-mode`: set the VLAN mode for device
+
+For a hotspot called `example` the template could look like this.
+For `wifi`:
+
+    /interface/wifi/access-list/add comment="hotspot-to-wpa template example" disabled=yes passphrase="ignore" ssid-regexp="^example\$" vlan-id=10;
+
+For legacy CAPsMAN:
+
+    /caps-man/access-list/add comment="hotspot-to-wpa template example" disabled=yes private-passphrase="ignore" ssid-regexp="^example\$" vlan-id=10 vlan-mode=use-tag;
+
+The same settings are available in hotspot user's comment and take precedence
+over the template settings:
+
+    /ip/hotspot/user/add comment="private-passphrase=ignore, ssid-regexp=^example\\\$, vlan-id=10, vlan-mode=use-tag" name=user password=v3ry-s3cr3t;
+
+Usage and invocation
+--------------------
+
+Now let the users connect and login to the hotspot. After that the devices
+(identified by MAC address) can connect to the WPA network, using the
+passphrase from hotspot credentials.
+
+See also
+--------
+
+* [Run other scripts on DHCP lease](lease-script.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/ip-addr-bridge.md b/html/doc/ip-addr-bridge.md
new file mode 100644 (file)
index 0000000..1868bc7
--- /dev/null
@@ -0,0 +1,39 @@
+Manage IP addresses with bridge status
+======================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+Description
+-----------
+
+With RouterOS an IP address is always active, even if an interface is down.
+Other venders handle this differently - and sometimes this behavior is
+expected. This script mimics this behavior.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate ip-addr-bridge;
+
+... and make it run from scheduler:
+
+    /system/scheduler/add name=ip-addr-bridge on-event="/system/script/run ip-addr-bridge;" start-time=startup;
+
+This will disable IP addresses on bridges without at least one running port.
+The IP address is enabled if at least one port is running.
+
+Note that IP addresses on bridges without a single port (acting as loopback
+interface) are ignored.
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/ipsec-to-dns.md b/html/doc/ipsec-to-dns.md
new file mode 100644 (file)
index 0000000..cf4abee
--- /dev/null
@@ -0,0 +1,57 @@
+Create DNS records for IPSec peers
+==================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script adds (and removes) dns records based on IPSec peers and their
+dynamic addresses from mode-config.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate ipsec-to-dns;
+
+This script is run from scheduler:
+
+    /system/scheduler/add interval=1m name=ipsec-to-dns on-event="/system/script/run ipsec-to-dns;" start-time=startup;
+
+Configuration
+-------------
+
+On first run a disabled static dns record acting as marker (with comment
+"`--- ipsec-to-dns above ---`") is added. Move this entry to define where new
+entries are to be added.
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `Domain`: the domain used for dns records
+* `HostNameInZone`: whether or not to add the ipsec/dns server's hostname
+* `PrefixInZone`: whether or not to add prefix `ipsec`
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+See also
+--------
+
+* [Create DNS records for DHCP leases](dns-to-dhcp.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/ipv6-update.md b/html/doc/ipv6-update.md
new file mode 100644 (file)
index 0000000..792f97e
--- /dev/null
@@ -0,0 +1,84 @@
+Update configuration on IPv6 prefix change
+==========================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+With changing IPv6 prefix from ISP this script handles to update...
+
+* ipv6 firewall address-list (prefixes (`/64`) and host addresses (`/128`))
+* dns records
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate ipv6-update;
+
+Your ISP needs to provide an IPv6 prefix, your device receives it via dhcp:
+
+    /ipv6/dhcp-client/add add-default-route=yes interface=ppp-isp pool-name=isp request=prefix script=ipv6-update;
+
+Note this already adds this script as `script`. The pool name (here: "`isp`")
+is important, we need it later.
+
+Also this expects there is an address assigned from pool to an interface:
+
+    /ipv6/address/add from-pool=isp interface=br-local;
+
+Sometimes dhcp client is stuck on reconnect and needs to be released.
+Installing [ppp-on-up](ppp-on-up.md) may solve this.
+
+Configuration
+-------------
+
+As an address-list entry is mandatory a dynamic one is created automatically.
+It is updated with current prefix and can be used in firewall rules.
+
+Alternatively a static address-list entry can be used, where comment has to
+be "`ipv6-pool-`" and actual pool name. Use what ever list is desired, and
+create it with:
+
+    /ipv6/firewall/address-list/add address=2003:cf:2f0f:de00::/56 comment=ipv6-pool-isp list=extern;
+
+If the dynamic entry exists already you need to remove it before creating
+the static one..
+
+Address list entries for specific interfaces can be updated as well. The
+interface needs to get its address from pool `isp` and the address list entry
+has to be associated to an interface in comment:
+
+    /ipv6/firewall/address-list/add address=2003:cf:2f0f:de01::/64 comment="ipv6-pool-isp, interface=br-local" list=local;
+
+Updating address list entries with host addresses works as well, the new
+prefix is combinded with given suffix then:
+
+    /ipv6/firewall/address-list/add address=2003:cf:2f0f:de01:e3e0:f8fa:8cd6:dbe1/128 comment="ipv6-pool-isp, interface=br-local" list=hosts;
+
+Static DNS records need a special comment to be updated. Again it has to
+start with "`ipv6-pool-`" and actual pool name, followed by a comma,
+"`interface=`" and the name of interface this address is connected to:
+
+    /ip/dns/static/add address=2003:cf:2f0f:de00:1122:3344:5566:7788 comment="ipv6-pool-isp, interface=br-local" name=test.example.com ttl=15m;
+
+See also
+--------
+
+* [Run scripts on ppp connection](ppp-on-up.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/lease-script.md b/html/doc/lease-script.md
new file mode 100644 (file)
index 0000000..6bcf7e1
--- /dev/null
@@ -0,0 +1,54 @@
+Run other scripts on DHCP lease
+===============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script is supposed to run from dhcp server as lease script. On a dhcp
+lease it runs each script containing the following line, where `##` is a
+decimal number for ordering:
+
+    # provides: lease-script, order=##
+
+Currently it runs if available, in order:
+
+* [dhcp-to-dns](dhcp-to-dns.md)
+* [collect-wireless-mac](collect-wireless-mac.md)
+* [dhcp-lease-comment](dhcp-lease-comment.md)
+* `hotspot-to-wpa-cleanup`, which is an optional cleanup script
+  of [hotspot-to-wpa](hotspot-to-wpa.md)
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate lease-script;
+
+... and add it as `lease-script` to your dhcp server:
+
+    /ip/dhcp-server/set lease-script=lease-script [ find ];
+
+See also
+--------
+
+* [Collect MAC addresses in wireless access list](collect-wireless-mac.md)
+* [Comment DHCP leases with info from access list](dhcp-lease-comment.md)
+* [Create DNS records for DHCP leases](dhcp-to-dns.md)
+* [Use WPA network with hotspot credentials](hotspot-to-wpa.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/leds-mode.md b/html/doc/leds-mode.md
new file mode 100644 (file)
index 0000000..84098d5
--- /dev/null
@@ -0,0 +1,57 @@
+Manage LEDs dark mode
+=====================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+Description
+-----------
+
+These scripts control LEDs mode and allow to run your device
+completely dark. Hardware support for dark mode is required.
+
+Requirements and installation
+-----------------------------
+
+Just install the scripts:
+
+    $ScriptInstallUpdate leds-day-mode,leds-night-mode,leds-toggle-mode;
+
+Usage and invocation
+--------------------
+
+To switch the device to dark mode:
+
+    /system/script/run leds-night-mode;
+
+... and back to normal mode:
+
+    /system/script/run leds-day-mode;
+
+To toggle between the two modes:
+
+    /system/script/run leds-toggle-mode;
+
+Add these schedulers to switch to dark mode in the evening and back to
+normal mode in the morning:
+
+    /system/scheduler/add interval=1d name=leds-day-mode on-event="/system/script/run leds-day-mode;" start-time=07:00:00;
+    /system/scheduler/add interval=1d name=leds-night-mode on-event="/system/script/run leds-night-mode;" start-time=21:00:00;
+
+The script `leds-toggle-mode` can be used from [mode button](mode-button.md)
+to toggle mode.
+
+See also
+--------
+
+* [Mode button with multiple presses](mode-button.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/log-forward.d/notification-01-info.avif b/html/doc/log-forward.d/notification-01-info.avif
new file mode 100644 (file)
index 0000000..6d25467
Binary files /dev/null and b/html/doc/log-forward.d/notification-01-info.avif differ
diff --git a/html/doc/log-forward.d/notification-02-warn.avif b/html/doc/log-forward.d/notification-02-warn.avif
new file mode 100644 (file)
index 0000000..ff61c0c
Binary files /dev/null and b/html/doc/log-forward.d/notification-02-warn.avif differ
diff --git a/html/doc/log-forward.md b/html/doc/log-forward.md
new file mode 100644 (file)
index 0000000..7bee120
--- /dev/null
@@ -0,0 +1,103 @@
+Forward log messages via notification
+=====================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+RouterOS itself supports sending log messages via e-mail or to a syslog
+server (see `/system/logging`). This has some limitation, however:
+
+* does not work early after boot if network connectivity is not
+  yet established, or breaks intermittently
+* lots of messages generate a flood of mails
+* Gotify, Matrix, Ntfy and Telegram are not supported
+
+The script works around the limitations, for example it does:
+
+* read from `/log`, including messages from early boot
+* skip multi-repeated messages
+* rate-limit itself to mitigate flooding
+* forward via notification (which includes *e-mail*, *Gotify*, *Matrix*,
+  *Ntfy* and *Telegram* when installed and configured, see below)
+
+It is intended to be run periodically from scheduler, then collects new
+log messages and forwards them via notification.
+
+### Sample notifications
+
+![log-forward notification info](log-forward.d/notification-01-info.avif)  
+![log-forward notification warn](log-forward.d/notification-02-warn.avif)
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate log-forward;
+
+... and add a scheduler:
+
+    /system/scheduler/add interval=1m name=log-forward on-event="/system/script/run log-forward;" start-time=startup;
+
+Configuration
+-------------
+
+The default configuration should provide reasonable presets, filtering
+*info*, and effectively forwarding *warning* and *error*.
+
+> 💡️ **Hint**: Please try with defaults first, especially if you are not
+> familiar with regular expressions!
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `LogForwardFilter`: define topics *not* to be forwarded
+* `LogForwardFilterMessage`: define message text *not* to be forwarded
+* `LogForwardInclude`: define topics to be forwarded (even if filter matches)
+* `LogForwardIncludeMessage`: define message text to be forwarded (even if
+  filter matches)
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+These patterns are matched as
+[regular expressions ↗️](https://wiki.mikrotik.com/wiki/Manual:Regular_Expressions).
+To forward **all** (ignoring severity) log messages with topics `account`
+(which includes user logins) and `dhcp` you need something like:
+
+    :global LogForwardInclude "(account|dhcp)";
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[gotify](mod/notification-gotify.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+Tips & Tricks
+-------------
+
+### Notification on reboot
+
+You want to receive a notification on every device (re-)boot? Quite easy,
+just add:
+
+    :global LogForwardIncludeMessage "(^router rebooted)";
+
+This will match on every log message beginning with `router rebooted`.
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/mod/bridge-port-to.md b/html/doc/mod/bridge-port-to.md
new file mode 100644 (file)
index 0000000..e0e75b2
--- /dev/null
@@ -0,0 +1,88 @@
+Manage ports in bridge
+======================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module and its functio are are supposed to handle interfaces and
+switching them from one bridge to another.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+    $ScriptInstallUpdate mod/bridge-port-to;
+
+Configuration
+-------------
+
+The configuration goes to ports' comments (`/interface/bridge/port`).
+
+    /interface/bridge/port/add bridge=br-guest comment="default=dhcp-client, alt=br-guest" disabled=yes interface=en1;
+    /interface/bridge/port/add bridge=br-intern comment="default=br-intern, alt=br-guest" interface=en2;
+    /interface/bridge/port/add bridge=br-guest comment="default=br-guest, extra=br-extra" interface=en3;
+
+Also dhcp client can be handled:
+
+    /ip/dhcp-client/add comment="toggle with bridge port" disabled=no interface=en1;
+
+Add a scheduler to start with default setup on system startup:
+
+    $ScriptInstallUpdate global-wait;
+    /system/scheduler/add name=bridge-port-to on-event="/system/script/run global-wait; :global BridgePortTo; \$BridgePortTo default;" start-time=startup;
+
+Usage and invocation
+--------------------
+
+The usage examples show what happens with the configuration from above.
+
+Running the function `$BridgePortTo` with parameter `default` applies all
+configuration given with `default=`:
+
+    $BridgePortTo default;
+
+For the three interfaces we get this configuration:
+
+* The special value `dhcp-client` enables the dhcp client for interface `en1`. The bridge port entry is disabled.
+* Interface `en2` is put in bridge `br-intern`.
+* Interface `en3` is put in bridge `br-guest`.
+
+Running the function `$BridgePortTo` with parameter `alt` applies all
+configuration given with `alt=`:
+
+    $BridgePortTo alt;
+
+* Interface `en1` is put in bridge `br-guest`, dhcp client for the interface is disabled.
+* Interface `en2` is put in bridge `br-guest`.
+* Interface `en3` is unchanged, stays in bridge `br-guest`.
+
+Running the function `$BridgePortTo` with parameter `extra` applies another
+configuration:
+
+    $BridgePortTo extra;
+
+* Interfaces `en1` and `en2` are unchanged.
+* Interface `en3` is put in bridge `br-intern`.
+
+See also
+--------
+
+* [Wait for global functions und modules](../global-wait.md)
+* [Manage VLANs on bridge ports](bridge-port-vlan.md)
+
+---
+[⬅️ Go back to main README](../../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/mod/bridge-port-vlan.md b/html/doc/mod/bridge-port-vlan.md
new file mode 100644 (file)
index 0000000..a8593fb
--- /dev/null
@@ -0,0 +1,92 @@
+Manage VLANs on bridge ports
+============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module and its function are supposed to handle VLANs on bridge ports.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+    $ScriptInstallUpdate mod/bridge-port-vlan;
+
+Configuration
+-------------
+
+Using named VLANs you have to add comments in bridge vlan menu:
+
+    /interface/bridge/vlan/add bridge=bridge comment=intern tagged=br-local vlan-ids=10;
+    /interface/bridge/vlan/add bridge=bridge comment=geust tagged=br-local vlan-ids=20;
+    /interface/bridge/vlan/add bridge=bridge comment=extra tagged=br-local vlan-ids=30;
+
+The configuration goes to ports' comments (`/interface/bridge/port`).
+
+    /interface/bridge/port/add bridge=bridge comment="default=dhcp-client, alt=guest" disabled=yes interface=en1;
+    /interface/bridge/port/add bridge=bridge comment="default=intern, alt=guest, extra=30" interface=en2;
+    /interface/bridge/port/add bridge=bridge comment="default=guest, extra=extra" interface=en3;
+
+Also dhcp client can be handled:
+
+    /ip/dhcp-client/add comment="toggle with bridge port" disabled=no interface=en1;
+
+Add a scheduler to start with default setup on system startup:
+
+    $ScriptInstallUpdate global-wait;
+    /system/scheduler/add name=bridge-port-vlan on-event="/system/script/run global-wait; :global BridgePortVlan; \$BridgePortVlan default;" start-time=startup;
+
+Usage and invocation
+--------------------
+
+The usage examples show what happens with the configuration from above.
+
+Running the function `$BridgePortVlan` with parameter `default` applies all
+configuration given with `default=`:
+
+    $BridgePortVlan default;
+
+For the three interfaces we get this configuration:
+
+* The special value `dhcp-client` enables the dhcp client for interface `en1`. The bridge port entry is disabled.
+* Primary VLAN `intern` (ID `10`) is configured on `en2`.
+* Primary VLAN `guest` (ID `20`) is configured on `en3`.
+
+Running the function `$BridgePortVlan` with parameter `alt` applies all
+configuration given with `alt=`:
+
+    $BridgePortVlan alt;
+
+* Primary VLAN `guest` (ID `20`) is configured on `en1`, dhcp client for the interface is disabled.
+* Primary VLAN `guest` (ID `20`) is configured on `en2`.
+* Interface `en3` is unchanged, primary VLAN `guest` (ID `20`) is unchanged.
+
+Running the function `$BridgePortVlan` with parameter `extra` applies another
+configuration:
+
+* Interface `en1` is unchanged.
+* Primary VLAN `extra` (via its ID `30`) is configured on `en2`.
+* Primary VLAN `extra` (ID `30`) is configured on `en3`.
+
+See also
+--------
+
+* [Wait for global functions und modules](../global-wait.md)
+* [Manage ports in bridge](bridge-port-to.md)
+
+---
+[⬅️ Go back to main README](../../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/mod/inspectvar.d/01-inspectvar.avif b/html/doc/mod/inspectvar.d/01-inspectvar.avif
new file mode 100644 (file)
index 0000000..e4e9bd5
Binary files /dev/null and b/html/doc/mod/inspectvar.d/01-inspectvar.avif differ
diff --git a/html/doc/mod/inspectvar.md b/html/doc/mod/inspectvar.md
new file mode 100644 (file)
index 0000000..2803265
--- /dev/null
@@ -0,0 +1,40 @@
+Inspect variables
+=================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+RouterOS handles not just scalar variables, but also arrays - even nested.
+This module adds a function to inspect variables.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+    $ScriptInstallUpdate mod/inspectvar;
+
+Usage and invocation
+--------------------
+
+Call the function `$InspectVar` with a variable as parameter:
+
+    $InspectVar $ModeButton;
+
+![InspectVar](inspectvar.d/01-inspectvar.avif)
+
+---
+[⬅️ Go back to main README](../../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/mod/ipcalc.d/01-ipcalc.avif b/html/doc/mod/ipcalc.d/01-ipcalc.avif
new file mode 100644 (file)
index 0000000..aaad616
Binary files /dev/null and b/html/doc/mod/ipcalc.d/01-ipcalc.avif differ
diff --git a/html/doc/mod/ipcalc.d/02-ipcalcreturn.avif b/html/doc/mod/ipcalc.d/02-ipcalcreturn.avif
new file mode 100644 (file)
index 0000000..2459eaf
Binary files /dev/null and b/html/doc/mod/ipcalc.d/02-ipcalcreturn.avif differ
diff --git a/html/doc/mod/ipcalc.md b/html/doc/mod/ipcalc.md
new file mode 100644 (file)
index 0000000..c393ec3
--- /dev/null
@@ -0,0 +1,60 @@
+IP address calculation
+======================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module adds functions for IP address calculation.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+    $ScriptInstallUpdate mod/ipcalc;
+
+Usage and invocation
+--------------------
+
+### IPCalc
+
+The function `$IPCalc` prints information to terminal, including:
+
+* address
+* netmask
+* network in CIDR notation
+* minimum host address
+* maximum host address
+* broadcast address
+
+It expects an IP address in CIDR notation as argument.
+
+    $IPCalc 192.168.88.1/24;
+
+![IPCalc](ipcalc.d/01-ipcalc.avif)
+
+### IPCalcReturn
+
+The function `$IPCalcReturn` expects an IP address in CIDR notation as
+argument as well. But it does not print to terminal, instead it returns
+the information in a named array.
+
+    :put ([ $IPCalcReturn  192.168.88.1/24 ]->"broadcast");
+
+![IPCalcReturn](ipcalc.d/02-ipcalcreturn.avif)
+
+---
+[⬅️ Go back to main README](../../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/mod/notification-email.md b/html/doc/mod/notification-email.md
new file mode 100644 (file)
index 0000000..c45e917
--- /dev/null
@@ -0,0 +1,89 @@
+Send notifications via e-mail
+=============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module adds support for sending notifications via e-mail. A queue is
+used to make sure notifications are not lost on failure but sent later.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+    $ScriptInstallUpdate mod/notification-email;
+
+Also you need a valid e-mail account with smtp login credentials.
+
+Configuration
+-------------
+
+Set up your device's
+[e-mail settings ↗️](https://wiki.mikrotik.com/wiki/Manual:Tools/email).
+Also make sure the device has correct time configured, best is to set up
+the ntp client.
+
+Then edit `global-config-overlay`, add `EmailGeneralTo` with a valid
+recipient address. Finally reload the configuration.
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+### Sending to several recipients
+
+Sending notifications to several recipients is possible as well. Add
+`EmailGeneralCc` on top, which can have a single mail address or a comma
+separated list.
+
+Usage and invocation
+--------------------
+
+There's nothing special to do. Every script or function sending a notification
+will now send it to your e-mail account.
+
+But of course you can use the function to send notifications directly. Give
+it a try:
+
+    $SendEMail "Subject..." "Body...";
+
+Alternatively this sends a notification with all available and configured
+methods:
+
+    $SendNotification "Subject..." "Body...";
+
+To use the functions in your own scripts you have to declare them first.
+Place this before you call them:
+
+    :global SendEMail;
+    :global SendNotification;
+
+In case there is a situation when the queue needs to be purged there is a
+function available:
+
+    $PurgeEMailQueue;
+
+See also
+--------
+
+* [Send notifications via Gotify](notification-gotify.md)
+* [Send notifications via Matrix](notification-matrix.md)
+* [Send notifications via Ntfy](notification-ntfy.md)
+* [Send notifications via Telegram](notification-telegram.md)
+
+---
+[⬅️ Go back to main README](../../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/mod/notification-gotify.d/appsetup.avif b/html/doc/mod/notification-gotify.d/appsetup.avif
new file mode 100644 (file)
index 0000000..58f57a8
Binary files /dev/null and b/html/doc/mod/notification-gotify.d/appsetup.avif differ
diff --git a/html/doc/mod/notification-gotify.md b/html/doc/mod/notification-gotify.md
new file mode 100644 (file)
index 0000000..7482ba7
--- /dev/null
@@ -0,0 +1,97 @@
+Send notifications via Gotify
+===========================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module adds support for sending notifications via
+[Gotify ↗️](https://gotify.net/). A queue is used to make sure
+notifications are not lost on failure but sent later.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+    $ScriptInstallUpdate mod/notification-gotify;
+
+Also deploy the [Gotify server ↗️](https://github.com/gotify/server) and
+optionally install a Gotify client on your mobile device.
+
+Configuration
+-------------
+
+Follow the [Installation ↗️](https://gotify.net/docs/install) instructions
+and the [First Login ↗️](https://gotify.net/docs/first-login) setup. Once
+you have a user and account you can start creating apps. Each app is an
+independent notification feed for a device or application.
+
+![Create new app](notification-gotify.d/appsetup.avif)
+On creation apps are assigned a *Token* for authentification, you will need
+that in configuration.
+
+Edit `global-config-overlay`, add `GotifyServer` with your server address
+(just the address, no protocol - `https://` is assumed) and `GotifyToken`
+with the *Token* from your configured app on the Gotify server. Then reload
+the configuration.
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+For a custom service installing an additional certificate may be required.
+You may want to install that certificate manually, after finding the
+[certificate name from browser](../../CERTIFICATES.md).
+
+Usage and invocation
+--------------------
+
+There's nothing special to do. Every script or function sending a notification
+will now send it to your Gotify application feed.
+
+But of course you can use the function to send notifications directly. Give
+it a try:
+
+    $SendGotify "Subject..." "Body...";
+
+Alternatively this sends a notification with all available and configured
+methods:
+
+    $SendNotification "Subject..." "Body...";
+
+To use the functions in your own scripts you have to declare them first.
+Place this before you call them:
+
+    :global SendGotify;
+    :global SendNotification;
+
+In case there is a situation when the queue needs to be purged there is a
+function available:
+
+    $PurgeGotifyQueue;
+
+See also
+--------
+
+* [Certificate name from browser](../../CERTIFICATES.md)
+* [Send notifications via e-mail](notification-email.md)
+* [Send notifications via Matrix](notification-matrix.md)
+* [Send notifications via Ntfy](notification-ntfy.md)
+* [Send notifications via Telegram](notification-telegram.md)
+
+---
+[⬅️ Go back to main README](../../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/mod/notification-matrix.d/01-authenticate.avif b/html/doc/mod/notification-matrix.d/01-authenticate.avif
new file mode 100644 (file)
index 0000000..d5b8025
Binary files /dev/null and b/html/doc/mod/notification-matrix.d/01-authenticate.avif differ
diff --git a/html/doc/mod/notification-matrix.d/02-join-room.avif b/html/doc/mod/notification-matrix.d/02-join-room.avif
new file mode 100644 (file)
index 0000000..20c4e79
Binary files /dev/null and b/html/doc/mod/notification-matrix.d/02-join-room.avif differ
diff --git a/html/doc/mod/notification-matrix.md b/html/doc/mod/notification-matrix.md
new file mode 100644 (file)
index 0000000..6000141
--- /dev/null
@@ -0,0 +1,140 @@
+Send notifications via Matrix
+=============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module adds support for sending notifications via
+[Matrix ↗️](https://matrix.org/) via client server api. A queue is used to
+make sure notifications are not lost on failure but sent later.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+    $ScriptInstallUpdate mod/notification-matrix;
+
+Also install a Matrix client on at least one of your mobile and/or desktop
+devices. Create and setup an account there, we will reference that as
+"*general account*" later.
+
+Configuration
+-------------
+
+Edit `global-config-overlay`, add `MatrixHomeServer`, `MatrixAccessToken` and
+`MatrixRoom` - see below on hints how to retrieve this information. Then
+reload the configuration.
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+The Matrix server is connected via encrypted https, and certificate
+verification is applied. So make sure you have the certificate chain for
+your server in device's certificate store.
+
+The example below is for `matrix.org`, which uses a trust chain from *Google
+Trust Services*. Run this to import the required certificate:
+
+    $CertificateAvailable "GTS Root R4" "fetch";
+
+Replace the CA certificate name with what ever is needed for your server.
+You may want to find the
+[certificate name from browser](../../CERTIFICATES.md).
+
+### From other device
+
+If you have setup your Matrix *notification account* before just reuse that.
+Copy the relevant configuration to the device to be configured.
+
+### Setup new account
+
+As there is no privilege separation you should create a dedicated account
+for use with these scripts, in addition to your *general account*.
+We will reference that as "*notification account*" in the following steps.
+
+#### Authenticate
+
+Matrix user accounts are identified by a unique user id in the form of
+`@localpart:domain`. Use that and your password to generate an access token
+and write first part of the configuration:
+
+    $SetupMatrixAuthenticate "@example:matrix.org" "v3ry-s3cr3t";
+
+![authenticate](notification-matrix.d/01-authenticate.avif)
+
+The configuration is written to a new configuration snippet
+`global-config-overlay.d/mod/notification-matrix`.
+
+#### Join Room
+
+Every Matix chat is a room, so we have to create one. Do that with your
+*general account*, this makes sure your *general account* is the room owner.
+Then join the room and invite the *notification account* by its user id
+"*@example:matrix.org*".
+Look up the *room id* within the Matrix client, it should read like
+"*!WUcxpSjKyxSGelouhA:matrix.org*" (starting with an exclamation mark and
+ending with the domain).
+
+Finally make the *notification account* join into the room by accepting
+the invite.
+
+    $SetupMatrixJoinRoom "!WUcxpSjKyxSGelouhA:matrix.org";
+
+![join room](notification-matrix.d/02-join-room.avif)
+
+The configuration is appended to the configuration snippet
+`global-config-overlay.d/mod/notification-matrix`.
+
+Usage and invocation
+--------------------
+
+There's nothing special to do. Every script or function sending a notification
+will now send it to your Matrix account.
+
+But of course you can use the function to send notifications directly. Give
+it a try:
+
+    $SendMatrix "Subject..." "Body...";
+
+Alternatively this sends a notification with all available and configured
+methods:
+
+    $SendNotification "Subject..." "Body...";
+
+To use the functions in your own scripts you have to declare them first.
+Place this before you call them:
+
+    :global SendMatrix;
+    :global SendNotification;
+
+In case there is a situation when the queue needs to be purged there is a
+function available:
+
+    $PurgeMatrixQueue;
+
+See also
+--------
+
+* [Certificate name from browser](../../CERTIFICATES.md)
+* [Send notifications via e-mail](notification-email.md)
+* [Send notifications via Gotify](notification-gotify.md)
+* [Send notifications via Ntfy](notification-ntfy.md)
+* [Send notifications via Telegram](notification-telegram.md)
+
+---
+[⬅️ Go back to main README](../../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/mod/notification-ntfy.md b/html/doc/mod/notification-ntfy.md
new file mode 100644 (file)
index 0000000..b353401
--- /dev/null
@@ -0,0 +1,99 @@
+Send notifications via Ntfy
+===========================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module adds support for sending notifications via
+[Ntfy ↗️](https://ntfy.sh/). A queue is used to make sure
+notifications are not lost on failure but sent later.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+    $ScriptInstallUpdate mod/notification-ntfy;
+
+Also install the Ntfy app on your mobile device or use the
+[web app ↗️](https://ntfy.sh/app) in a browser of your choice.
+
+Configuration
+-------------
+
+Creating an account is not required. Just choose a topic and you are good
+to go.
+
+> ⚠️ **Warning**: If you use ntfy without sign-up, the topic is essentially
+> a password, so pick something that's not easily guessable.
+
+Edit `global-config-overlay`, add `NtfyServer` (leave it unchanged, unless
+you are self-hosting the service) and `NtfyTopic` with your choosen topic.
+Then reload the configuration.
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Using a paid account or running a server on-premises allows to add additional
+basic authentication. Configure `NtfyServerUser` and `NtfyServerPass` for this.
+Even authentication via access token is possible, adding it as password with
+a blank username.
+
+Also available is `NtfyServerToken` to add a bearer token for authentication.
+
+For a custom service installing an additional certificate may be required.
+You may want to install that certificate manually, after finding the
+[certificate name from browser](../../CERTIFICATES.md).
+
+Usage and invocation
+--------------------
+
+There's nothing special to do. Every script or function sending a notification
+will now send it to your Ntfy topic.
+
+But of course you can use the function to send notifications directly. Give
+it a try:
+
+    $SendNtfy "Subject..." "Body...";
+
+Alternatively this sends a notification with all available and configured
+methods:
+
+    $SendNotification "Subject..." "Body...";
+
+To use the functions in your own scripts you have to declare them first.
+Place this before you call them:
+
+    :global SendNtfy;
+    :global SendNotification;
+
+In case there is a situation when the queue needs to be purged there is a
+function available:
+
+    $PurgeNtfyQueue;
+
+See also
+--------
+
+* [Certificate name from browser](../../CERTIFICATES.md)
+* [Send notifications via e-mail](notification-email.md)
+* [Send notifications via Gotify](notification-gotify.md)
+* [Send notifications via Matrix](notification-matrix.md)
+* [Send notifications via Telegram](notification-telegram.md)
+
+---
+[⬅️ Go back to main README](../../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/mod/notification-telegram.d/01-newbot.avif b/html/doc/mod/notification-telegram.d/01-newbot.avif
new file mode 100644 (file)
index 0000000..1fc7355
Binary files /dev/null and b/html/doc/mod/notification-telegram.d/01-newbot.avif differ
diff --git a/html/doc/mod/notification-telegram.d/02-getchatid.avif b/html/doc/mod/notification-telegram.d/02-getchatid.avif
new file mode 100644 (file)
index 0000000..0112449
Binary files /dev/null and b/html/doc/mod/notification-telegram.d/02-getchatid.avif differ
diff --git a/html/doc/mod/notification-telegram.d/03-setuserpic.avif b/html/doc/mod/notification-telegram.d/03-setuserpic.avif
new file mode 100644 (file)
index 0000000..2017d20
Binary files /dev/null and b/html/doc/mod/notification-telegram.d/03-setuserpic.avif differ
diff --git a/html/doc/mod/notification-telegram.md b/html/doc/mod/notification-telegram.md
new file mode 100644 (file)
index 0000000..1d2cec8
--- /dev/null
@@ -0,0 +1,127 @@
+Send notifications via Telegram
+===============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module adds support for sending notifications via
+[Telegram ↗️](https://telegram.org/) via bot api. A queue is used to make sure
+notifications are not lost on failure but sent later.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+    $ScriptInstallUpdate mod/notification-telegram;
+
+Also install Telegram on at least one of your mobile and/or desktop devices
+and create an account.
+
+Configuration
+-------------
+
+Open Telegram, then start a chat with [BotFather ↗️](https://t.me/BotFather) and
+create your own bot:
+
+![create new bot](notification-telegram.d/01-newbot.avif)
+
+Set that token from *BotFather* (use your own!) to `TelegramTokenId`, for
+now just temporarily:
+
+    :set TelegramTokenId "5214364459:AAHLwf1o7ybbKDo6pY24Kd2bZ5rjCakDXTc";
+
+Now open a chat with your bot and start it by clicking the `START` button,
+then send your first message. Any text will do. On your device run
+`$GetTelegramChatId` to retrieve the chat id:
+
+    $GetTelegramChatId;
+
+![get chat id](notification-telegram.d/02-getchatid.avif)
+
+Finally edit `global-config-overlay`, add `TelegramTokenId` with the token
+from *BotFather* and `TelegramChatId` with your retrieved chat id. Then
+reload the configuration.
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+### Notifications to a group
+
+Sending notifications to a group is possible as well. Add your bot to a group
+and make it an admin (required for read access!) and send a message and run
+`$GetTelegramChatId` again. Then use that chat id (which starts with a dash)
+for `TelegramChatId`.
+
+Groups can enable the `Topics` feature. Use `TelegramThreadId` to send to a
+specific topic in a group.
+
+Usage and invocation
+--------------------
+
+There's nothing special to do. Every script or function sending a notification
+will now send it to your Telegram account.
+
+But of course you can use the function to send notifications directly. Give
+it a try:
+
+    $SendTelegram "Subject..." "Body...";
+
+Alternatively this sends a notification with all available and configured
+methods:
+
+    $SendNotification "Subject..." "Body...";
+
+To use the functions in your own scripts you have to declare them first.
+Place this before you call them:
+
+    :global SendTelegram;
+    :global SendNotification;
+
+In case there is a situation when the queue needs to be purged there is a
+function available:
+
+    $PurgeTelegramQueue;
+
+Tips & Tricks
+-------------
+
+### Set a profile photo
+
+You can use a profile photo for your bot to make it recognizable. Open the
+chat with [BotFather ↗️](https://t.me/BotFather) and set it there.
+
+![set profile photo](notification-telegram.d/03-setuserpic.avif)
+
+Have a look at my [Logo Color Changer](../../contrib/logo-color.html)
+to create a colored version of this scripts' logo.
+
+> 💡️ **Hint**: The above link may be broken on code hosting sites.
+> Use [Logo Color Changer](https://rsc.eworm.de/main/contrib/logo-color.html)
+> instead.
+
+See also
+--------
+
+* [Chat with your router and send commands via Telegram bot](../telegram-chat.md)
+* [Send notifications via e-mail](notification-email.md)
+* [Send notifications via Gotify](notification-gotify.md)
+* [Send notifications via Matrix](notification-matrix.md)
+* [Send notifications via Ntfy](notification-ntfy.md)
+
+---
+[⬅️ Go back to main README](../../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/mod/scriptrunonce.d/01-scriptrunonce.avif b/html/doc/mod/scriptrunonce.d/01-scriptrunonce.avif
new file mode 100644 (file)
index 0000000..461c398
Binary files /dev/null and b/html/doc/mod/scriptrunonce.d/01-scriptrunonce.avif differ
diff --git a/html/doc/mod/scriptrunonce.d/hello-world.rsc b/html/doc/mod/scriptrunonce.d/hello-world.rsc
new file mode 100644 (file)
index 0000000..6404781
--- /dev/null
@@ -0,0 +1,3 @@
+#!rsc by RouterOS
+
+:put ("Hello World from " . [ /system/identity/get name ] . "!");
diff --git a/html/doc/mod/scriptrunonce.md b/html/doc/mod/scriptrunonce.md
new file mode 100644 (file)
index 0000000..e960d76
--- /dev/null
@@ -0,0 +1,59 @@
+Download script and run it once
+===============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+This module adds a function that downloads a script, checks for syntax
+validity and runs it once.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+    $ScriptInstallUpdate mod/scriptrunonce;
+
+Configuration
+-------------
+
+The optional configuration goes to `global-config-overlay`.
+
+* `ScriptRunOnceBaseUrl`: base url, prepended to parameter
+* `ScriptRunOnceUrlSuffix`: url suffix, appended to parameter
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+If the parameter passed to the function is not a complete URL (starting
+with protocol `ftp://`, `http://`, `https://` or `sftp://`) the base-url is
+prepended, and file extension `.rsc` and url-suffix are appended.
+
+Usage and invocation
+--------------------
+
+The function `$ScriptRunOnce` expects an URL (or name if
+`ScriptRunOnceBaseUrl` is given) pointing to a script as parameter.
+
+    $ScriptRunOnce https://rsc.eworm.de/main/doc/mod/scriptrunonce.d/hello-world.rsc;
+
+![ScriptRunOnce](scriptrunonce.d/01-scriptrunonce.avif)
+
+Giving multiple scripts is possible, separated by comma.
+
+---
+[⬅️ Go back to main README](../../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/mod/ssh-keys-import.md b/html/doc/mod/ssh-keys-import.md
new file mode 100644 (file)
index 0000000..c2d3c95
--- /dev/null
@@ -0,0 +1,68 @@
+Import ssh keys for public key authentication
+=============================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../../README.md)
+
+> ℹ️️ **Info**: This module can not be used on its own but requires the base
+> installation. See [main README](../../README.md) for details.
+
+Description
+-----------
+
+RouterOS supports ssh login with public key authentication. The functions
+in this module help importing the keys.
+
+Requirements and installation
+-----------------------------
+
+Just install the module:
+
+    $ScriptInstallUpdate mod/ssh-keys-import;
+
+Usage and invocation
+--------------------
+
+### Import single key from terminal
+
+Call the function `$SSHKeysImport` with key and user as parameter to
+import that key:
+
+    $SSHKeysImport "ssh-ed25519 AAAAC3Nza...ZVugJT user" admin;
+    $SSHKeysImport "ssh-rsa AAAAB3Nza...QYZk8= user" admin;
+
+The third part of the key (`user` in this example) is inherited as
+`info` in RouterOS (or `key-owner` with RouterOS 7.20.x and before). Also
+the `MD5` fingerprint is recorded, this helps to audit and verify the
+available keys.
+
+> ℹ️️ **Info**: Use `ssh-keygen` to show a fingerprint of an existing public
+> key file: `ssh-keygen -l -E md5 -f ~/.ssh/id_ed25519.pub`
+
+### Import several keys from file
+
+The functions `$SSHKeysImportFile` can read an `authorized_keys`-style file
+and import all the keys. The user given to the function can be overwritting
+from comments in the file. Create a file `keys.pub` with this content:
+
+    ssh-ed25519 AAAAC3Nza...3OcN8A user@client
+    ssh-rsa AAAAB3Nza...ozyts= worker@station
+    # user=example
+    ssh-rsa AAAAB3Nza...GXQVk= person@host
+
+Then import it with:
+
+    $SSHKeysImportFile keys.pub admin;
+
+This will import the first two keys for user `admin` (as given to function)
+and the third one for user `example` (as defined in comment).
+
+---
+[⬅️ Go back to main README](../../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/mode-button.md b/html/doc/mode-button.md
new file mode 100644 (file)
index 0000000..46a66b0
--- /dev/null
@@ -0,0 +1,73 @@
+Mode button with multiple presses
+=================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script extend the functionality of mode button. Instead of just one
+you can trigger several actions by pressing the mode button several times.
+
+The hardware needs to have a mode button, see
+`/system/routerboard/mode-button`. Starting with RouterOS 6.47beta60 you
+can configure the reset button to act the same, see
+`/system/routerboard/reset-button`.
+
+Copy this code to terminal to check:
+
+    :if ([ :len [ /system/routerboard/mode-button/print as-value ] ] > 0) do={
+      :put "Mode button is supported.";
+    } else={
+      :if ([ :len [ /system/routerboard/reset-button/print as-value ] ] > 0) do={
+        :put "Mode button is not supported, but reset button is.";
+      } else={
+        :put "Neither mode button nor reset button is supported.";
+      }
+    }
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate mode-button;
+
+Then configure the mode button to run `mode-button`:
+
+    /system/routerboard/mode-button/set enabled=yes on-event="/system/script/run mode-button;";
+
+To use the reset button instead:
+
+    /system/routerboard/reset-button/set enabled=yes on-event="/system/script/run mode-button;";
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, these are the parameters:
+
+* `ModeButton`: an array with defined actions
+* `ModeButtonLED`: led to give visual feedback, `type` must be `on` or `off`
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Usage and invocation
+--------------------
+
+Press the mode button. 😜
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/netwatch-dns.md b/html/doc/netwatch-dns.md
new file mode 100644 (file)
index 0000000..c80b819
--- /dev/null
@@ -0,0 +1,105 @@
+Manage DNS and DoH servers from netwatch
+========================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script reads server state from netwatch and manages used DNS and
+DoH (DNS over HTTPS) servers.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate netwatch-dns;
+
+Then add a scheduler to run it periodically:
+
+    /system/scheduler/add interval=1m name=netwatch-dns on-event="/system/script/run netwatch-dns;" start-time=startup;
+
+Configuration
+-------------
+
+The DNS and DoH servers to be checked have to be added to netwatch with
+specific comment:
+
+    /tool/netwatch/add comment="doh" host=1.1.1.1;
+    /tool/netwatch/add comment="doh, dns" host=9.9.9.9;
+    /tool/netwatch/add comment="dns" host=8.8.8.8;
+
+This will configure *cloudflare-dns* for DoH (`https://1.1.1.1/dnsquery`), and
+*quad-nine* and *google-dns* for regular DNS (`9.9.9.9,8.8.8.8`) if up.
+If *cloudflare-dns* is down the script will fall back to *quad-nine* for DoH.
+
+Giving a specific query url for DoH is possible:
+
+    /tool/netwatch/add comment="doh, doh-url=https://dns.nextdns.io/dns-query" host=199.247.16.158;
+
+Note that using a name in DoH url may introduce a chicken-and-egg issue!
+
+Adding a static DNS record has the same result for the url, but always
+resolves to the same address.
+
+    /ip/dns/static/add name="cloudflare-dns.com" address=1.1.1.1;
+    /tool/netwatch/add comment="doh" host=1.1.1.1;
+    /ip dns static add name=dns.quad9.net address=9.9.9.9;
+    /tool/netwatch/add comment="doh" host=9.9.9.9;
+    /ip/dns/static/add name=dns.google address=8.8.8.8;
+    /tool/netwatch/add comment="doh" host=8.8.8.8;
+
+Be aware that you have to keep the ip address in sync with real world
+manually!
+
+Importing a certificate automatically is possible. You may want to find the
+[certificate name from browser](../CERTIFICATES.md). Sometimes a service
+randomly switches the CA used to issue the certificate, or it just depends
+geolocation - give several certificate delimited with colon (`:`) then.
+
+    /tool/netwatch/add comment="doh, doh-cert=SSL.com Root Certification Authority ECC" host=1.1.1.1;
+    /tool/netwatch/add comment="doh, doh-cert=DigiCert Global Root G3" host=9.9.9.9;
+    /tool/netwatch/add comment="doh, doh-cert=GTS Root R1:GTS Root R4" host=8.8.8.8;
+
+> ⚠️ **Warning**: Combining these techniques can cause some confusion and
+> troubles! Chances are that a service uses different certificates based
+> on indicated server name (or ip address).
+
+Sometimes using just one specific (possibly internal) DNS server may be
+desired, with fallback in case it fails. This is possible as well:
+
+    /tool/netwatch/add comment="dns" host=10.0.0.10;
+    /tool/netwatch/add comment="dns-fallback" host=1.1.1.1;
+
+Tips & Tricks
+-------------
+
+### Use in combination with notifications
+
+Netwatch entries can be created to work with both - this script and
+[netwatch-notify](netwatch-notify.md). Just give options for both:
+
+    /tool/netwatch/add comment="doh, notify, name=cloudflare-dns" host=1.1.1.1;
+
+Also this allows to update host address, see option `resolve`.
+
+See also
+--------
+
+* [Certificate name from browser](../CERTIFICATES.md)
+* [Notify on host up and down](netwatch-notify.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/netwatch-notify.d/notification-01-down.avif b/html/doc/netwatch-notify.d/notification-01-down.avif
new file mode 100644 (file)
index 0000000..2257a0d
Binary files /dev/null and b/html/doc/netwatch-notify.d/notification-01-down.avif differ
diff --git a/html/doc/netwatch-notify.d/notification-02-up.avif b/html/doc/netwatch-notify.d/notification-02-up.avif
new file mode 100644 (file)
index 0000000..4147cb7
Binary files /dev/null and b/html/doc/netwatch-notify.d/notification-02-up.avif differ
diff --git a/html/doc/netwatch-notify.md b/html/doc/netwatch-notify.md
new file mode 100644 (file)
index 0000000..70d6fe0
--- /dev/null
@@ -0,0 +1,195 @@
+Notify on host up and down
+==========================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script sends notifications about host UP and DOWN events. In comparison
+to just netwatch (`/tool/netwatch`) and its `up-script` and `down-script`
+this script implements a simple state machine and dependency model. Host
+down events are triggered only if the host is down for several checks and
+optional parent host is not down to avoid false alerts.
+
+### Sample notifications
+
+![netwatch-notify notification down](netwatch-notify.d/notification-01-down.avif)  
+![netwatch-notify notification up](netwatch-notify.d/notification-02-up.avif)
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate netwatch-notify;
+
+Then add a scheduler to run it periodically:
+
+    /system/scheduler/add interval=1m name=netwatch-notify on-event="/system/script/run netwatch-notify;" start-time=startup;
+
+Configuration
+-------------
+
+The hosts to be checked have to be added to netwatch with specific comment:
+
+    /tool/netwatch/add comment="notify, name=example.com" host=[ :resolve "example.com" ];
+
+Also notification settings are required for
+[e-mail](mod/notification-email.md),
+[gotify](mod/notification-gotify.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+### Hooks
+
+It is possible to run an up hook command (`up-hook`) or down hook command
+(`down-hook`) when a notification is triggered. This has to be added in
+comment, note that some characters need extra escaping:
+
+    /tool/netwatch/add comment=("notify, name=device, down-hook=/interface/ethernet \\{ disable \\\"en2\\\"; enable \\\"en2\\\"; \\}") host=10.0.0.20;
+
+Also there is a `pre-down-hook` that fires at two thirds of failed checks
+required for the notification. The idea is to fix the issue before a
+notification is sent.
+
+Getting the escaping right may be troublesome. Please consider adding a
+script in `/system/script`, then running that from hook.
+
+### Count threshold
+
+The count threshold (default is 5 checks) is configurable as well:
+
+    /tool/netwatch/add comment="notify, name=example.com, count=10" host=104.18.144.11;
+
+### Parents & dependencies
+
+If the host is behind another checked host add a dependency, this will
+suppress notification if the parent host is down:
+
+    /tool/netwatch/add comment="notify, name=gateway" host=93.184.216.1;
+    /tool/netwatch/add comment="notify, name=example.com, parent=gateway" host=93.184.216.34;
+
+Note that every configured parent in a chain increases the check count
+threshold by one.
+
+### Update from DNS
+
+The host address can be updated dynamically. Give extra parameter `resolve`
+with a resolvable name:
+
+    /tool/netwatch/add comment="notify, name=example.com, resolve=example.com" host=0.0;
+
+This supports multiple A records for a name just fine, even a CNAME
+to those. An update happens only if no more record with the configured host
+address is found.
+
+The address family is preserved, so if you want AAAA records (for IPv6)
+use this:
+
+    /tool/netwatch/add comment="notify, name=example.com, resolve=example.com" host=::;
+
+### No notification on host down
+
+Also suppressing the notification on host down is possible with parameter
+`no-down-notification`. This may be desired for devices that are usually
+powered off, but accessibility is of interest.
+
+    /tool/netwatch/add comment="notify, name=printer, no-down-notification" host=10.0.0.30;
+
+Go and get your coffee ☕️ before sending the print job.
+
+### No log on failed resolve
+
+A message is writting to log after three failed attemts to resolve a host.
+However this can cause some noise for hosts that are expected to have
+failures, for example when the name is dynamically added by
+[`dhcp-to-dns`](dhcp-to-dns.md). This can be suppressed:
+
+    /tool/netwatch/add comment="notify, name=client, resolve=client.dhcp.example.com, no-resolve-fail" host=10.0.0.0;
+
+### Add a note in notification
+
+For some extra information it is possible to add a text note. This is
+included verbatim into the notification.
+
+    /tool/netwatch/add comment="notify, name=example, note=Do not touch!" host=10.0.0.31;
+
+### Add a link in notification
+
+It is possible to add a link in notification, that is added below the
+formatted notification text.
+
+    /tool/netwatch/add comment="notify, name=example.com, resolve=example.com, link=https://example.com/" host=0.0;
+
+Tips & Tricks
+-------------
+
+### One of several hosts
+
+Sometimes it is sufficient if one of a number of hosts is available. You can
+make `netwatch-notify` check for that by adding several items with same
+`name`. Note that `count` has to be multiplied to keep the actual time.
+
+    /tool/netwatch/add comment="notify, name=service, count=10" host=10.0.0.10;
+    /tool/netwatch/add comment="notify, name=service, count=10" host=10.0.0.20;
+
+### Checking internet connectivity
+
+Sometimes you can not check your gateway for internet connectivity, for
+example when it does not respond to pings or has a dynamic address. You could
+check `1.1.1.1` (Cloudflare DNS), `9.9.9.9` (Quad-nine DNS), `8.8.8.8`
+(Google DNS) or any other reliable address that indicates internet
+connectivity.
+
+    /tool/netwatch/add comment="notify, name=internet" host=1.1.1.1;
+
+A target like this suits well to be parent for other checks.
+
+    /tool/netwatch/add comment="notify, name=example.com, parent=internet" host=93.184.216.34;
+
+### Checking specific ISP
+
+Having several ISPs for redundancy a failed link may go unnoticed without
+proper monitoring. You can use routing-mark to monitor specific connections.
+Create a route and firewall mangle rule.
+
+    /routing/table/add fib name=via-isp1;
+    /ip/route/add distance=1 gateway=isp1 routing-table=via-isp1;
+    /ip/firewall/mangle/add action=mark-routing chain=output new-routing-mark=via-isp1 dst-address=1.0.0.1 passthrough=yes;
+
+Finally monitor the address with `netwatch-notify`.
+
+    /tool/netwatch/add comment="notify, name=quad-one via isp1" host=1.0.0.1;
+
+Note that *all* traffic to the given address is routed that way. In case of
+link failure this address is not available, so use something reliable but
+non-essential. In this example the address `1.0.0.1` is used, the same service
+(Cloudflare DNS) is available at `1.1.1.1`.
+
+### Use in combination with DNS and DoH management
+
+Netwatch entries can be created to work with both - this script and
+[netwatch-dns](netwatch-dns.md). Just give options for both:
+
+    /tool/netwatch/add comment="doh, notify, name=cloudflare-dns" host=1.1.1.1;
+
+See also
+--------
+
+* [Manage DNS and DoH servers from netwatch](netwatch-dns.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/netwatch-syslog.md b/html/doc/netwatch-syslog.md
new file mode 100644 (file)
index 0000000..6a337d4
--- /dev/null
@@ -0,0 +1,5 @@
+This script has been dropped. Filtering in firewall is advised, which should
+look something like this:
+
+    /ip/firewall/filter/add action=reject chain=output out-interface-list=WAN port=514 protocol=udp reject-with=icmp-admin-prohibited;
+    /ip/firewall/filter/add action=reject chain=forward out-interface-list=WAN port=514 protocol=udp reject-with=icmp-admin-prohibited;
diff --git a/html/doc/ospf-to-leds.md b/html/doc/ospf-to-leds.md
new file mode 100644 (file)
index 0000000..aa66007
--- /dev/null
@@ -0,0 +1,44 @@
+Visualize OSPF state via LEDs
+=============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+Physical interfaces have their state LEDs, software-defined connectivity
+does not. This script helps to visualize whether or not an OSPF instance
+is running.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate ospf-to-leds;
+
+... and add a scheduler to run the script periodically:
+
+    /system/scheduler/add interval=20s name=ospf-to-leds on-event="/system/script/run ospf-to-leds;" start-time=startup;
+
+Configuration
+-------------
+
+The configuration goes to OSPF instance's comment. To visualize state for
+instance `default` via LED `user-led` set this:
+
+    /routing/ospf/instance/set default comment="ospf-to-leds, leds=user-led";
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/packages-update.md b/html/doc/packages-update.md
new file mode 100644 (file)
index 0000000..7335449
--- /dev/null
@@ -0,0 +1,78 @@
+Manage system update
+====================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+In rare cases RouterOS fails to properly downlaod package on update
+(`/system/package/update/install`), resulting in borked system with missing
+packages. This script tries to avoid this situation by doing some basic
+verification.
+
+But it provides some extra functionality:
+
+* upload backup to Mikrotik cloud if [backup-cloud](backup-cloud.md) is
+  installed
+* send backup via e-mail if [backup-email](backup-email.md) is installed
+* save configuration to fallback partition if
+  [backup-partition](backup-partition.md) is installed
+* upload backup to server if [backup-upload](backup-upload.md) is installed
+* schedule reboot at night
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate packages-update;
+
+It is automatically run by [check-routeros-update](check-routeros-update.md)
+if available.
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, this is the only parameter:
+
+* `PackagesUpdateDeferReboot`: defer the reboot for night (between 3 AM and
+  5 AM), use a numerical value in days suffixed with a `d` to defer further
+
+By modifying the scheduler's `start-time` you can force the reboot at
+different time.
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Usage and invocation
+--------------------
+
+Alternatively run it manually:
+
+    /system/script/run packages-update;
+
+See also
+--------
+
+* [Upload backup to Mikrotik cloud](backup-cloud.md)
+* [Send backup via e-mail](backup-email.md)
+* [Save configuration to fallback partition](backup-partition.md)
+* [Upload backup to server](backup-upload.md)
+* [Notify on RouterOS update](check-routeros-update.md)
+* [Automatically upgrade firmware and reboot](firmware-upgrade-reboot.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/ppp-on-up.md b/html/doc/ppp-on-up.md
new file mode 100644 (file)
index 0000000..e92601a
--- /dev/null
@@ -0,0 +1,44 @@
+Run scripts on ppp connection
+=============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script is supposed to run on established ppp connection. Currently
+it does:
+
+* release IPv6 dhcp leases (and thus force a renew)
+* run [update-tunnelbroker](update-tunnelbroker.md)
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate ppp-on-up;
+
+... and make it the `on-up` script for ppp profile:
+
+    /ppp/profile/set on-up=ppp-on-up [ find ];
+
+See also
+--------
+
+* [Update configuration on IPv6 prefix change](ipv6-update.md)
+* [Update tunnelbroker configuration](update-tunnelbroker.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/rotate-ntp.md b/html/doc/rotate-ntp.md
new file mode 100644 (file)
index 0000000..9a016a3
--- /dev/null
@@ -0,0 +1,3 @@
+This script has been dropped as the limitation does no longer exist with
+RouterOS 7.x, where you can enable a ntp server and use a name for the client
+at the same time.
diff --git a/html/doc/sms-action.md b/html/doc/sms-action.md
new file mode 100644 (file)
index 0000000..af44ae4
--- /dev/null
@@ -0,0 +1,63 @@
+Act on received SMS
+===================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+RouterOS can act on received SMS. Reboot the device from remote or do
+whatever is required.
+
+A broadband interface with SMS support is required.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate sms-action;
+
+Configuration
+-------------
+
+The configuration goes to `global-config-overlay`, this is the only parameter:
+
+* `SmsAction`: an array with pre-defined actions
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Then enable SMS actions:
+
+    /tool/sms/set allowed-number=+491234567890 receive-enabled=yes secret=s3cr3t;
+
+Usage and invocation
+--------------------
+
+Send a SMS from allowed number to your device's phone number:
+
+    :cmd s3cr3t script sms-action action=reboot;
+
+The value given by "`action=`" is one of the pre-defined actions from
+`SmsAction`.
+
+See also
+--------
+
+* [Forward received SMS](sms-forward.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/sms-forward.d/notification.avif b/html/doc/sms-forward.d/notification.avif
new file mode 100644 (file)
index 0000000..14764a3
Binary files /dev/null and b/html/doc/sms-forward.d/notification.avif differ
diff --git a/html/doc/sms-forward.md b/html/doc/sms-forward.md
new file mode 100644 (file)
index 0000000..5f03892
--- /dev/null
@@ -0,0 +1,99 @@
+Forward received SMS
+====================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+RouterOS can receive SMS. This script forwards SMS as notification.
+
+A broadband interface with SMS support is required.
+
+### Sample notification
+
+![sms-forward notification](sms-forward.d/notification.avif)
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate sms-forward;
+
+... and add a scheduler to run it periodically:
+
+    /system/scheduler/add interval=2m name=sms-forward on-event="/system/script/run sms-forward;" start-time=startup;
+
+Configuration
+-------------
+
+You have to enable receiving of SMS:
+
+    /tool/sms/set receive-enabled=yes;
+
+The configuration goes to `global-config-overlay`, this is the only parameter:
+
+* `SmsForwardHooks`: an array with pre-defined hooks, where each hook consists
+  of `match` (which is matched against the received message), `allowed-number`
+  (which is matched against the sending phone number or name) and `command`.
+  For `match` and `allowed-number` regular expressions are supported. Actual
+  phone number (`$Phone`) and message (`$Message`) are available for the hook.
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Notification settings are required for
+[e-mail](mod/notification-email.md),
+[gotify](mod/notification-gotify.md),
+[matrix](mod/notification-matrix.md),
+[ntfy](mod/notification-ntfy.md) and/or
+[telegram](mod/notification-telegram.md).
+
+Tips & Tricks
+-------------
+
+### Take care of harmful commands!
+
+It is easy to fake the sending phone number! So make sure you do not rely on
+that number for potentially harmful commands. Add a shared secret to match
+into the text instead, for example: `reboot-53cr3t-5tr1n9` instead of just
+`reboot`.
+
+### Order new volume
+
+Most broadband providers include a volume limit for their data plans. The
+hook functionality can be used to order new volume automatically.
+
+Let's assume an imaginary provider **ABC** sends a message when the available
+volume is about to deplete. The message is sent from `ABC` and the text
+contains the string `80%`. New volume can be ordered by sending a SMS back to
+the phone number `1234` with the text `data-plan`.
+
+    :global SmsForwardHooks {
+      { match="80%";
+        allowed-number="ABC";
+        command="/tool/sms/send lte1 phone-number=1234 message=\"data-plan\";" };
+    };
+
+Adjust the values to your own needs.
+
+See also
+--------
+
+* [Act on received SMS](sms-action.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/ssh-keys-import.md b/html/doc/ssh-keys-import.md
new file mode 100644 (file)
index 0000000..d1325aa
--- /dev/null
@@ -0,0 +1,2 @@
+This script has been replaced by a module. Please see
+[Import ssh keys for public key authentication](mod/ssh-keys-import.md).
diff --git a/html/doc/super-mario-theme.md b/html/doc/super-mario-theme.md
new file mode 100644 (file)
index 0000000..badb1c5
--- /dev/null
@@ -0,0 +1,38 @@
+Play Super Mario theme
+======================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+Description
+-----------
+
+This script plays Super Mario theme.
+
+The hardware needs a beeper.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate super-mario-theme;
+
+Usage and invocation
+--------------------
+
+Just run the script to play:
+
+    /system/script/run super-mario-theme;
+
+For extra fun use it for dhcp lease script. :)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/telegram-chat.d/01-chat-specific.avif b/html/doc/telegram-chat.d/01-chat-specific.avif
new file mode 100644 (file)
index 0000000..ab75f78
Binary files /dev/null and b/html/doc/telegram-chat.d/01-chat-specific.avif differ
diff --git a/html/doc/telegram-chat.d/02-chat-all.avif b/html/doc/telegram-chat.d/02-chat-all.avif
new file mode 100644 (file)
index 0000000..ed1a389
Binary files /dev/null and b/html/doc/telegram-chat.d/02-chat-all.avif differ
diff --git a/html/doc/telegram-chat.d/03-reply.avif b/html/doc/telegram-chat.d/03-reply.avif
new file mode 100644 (file)
index 0000000..515853e
Binary files /dev/null and b/html/doc/telegram-chat.d/03-reply.avif differ
diff --git a/html/doc/telegram-chat.md b/html/doc/telegram-chat.md
new file mode 100644 (file)
index 0000000..51cd3d6
--- /dev/null
@@ -0,0 +1,152 @@
+Chat with your router and send commands via Telegram bot
+========================================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+This script makes your device poll a Telegram bot for new messages. With
+these messages you can send commands to your device and make it run them.
+The resulting output is send back to you.
+
+Requirements and installation
+-----------------------------
+
+Just install the script and the module for notifications via Telegram:
+
+    $ScriptInstallUpdate telegram-chat,mod/notification-telegram;
+
+Then create a schedule that runs the script periodically:
+
+    /system/scheduler/add start-time=startup interval=30s name=telegram-chat on-event="/system/script/run telegram-chat;";
+
+> ⚠️ **Warning**: Make sure to keep the interval in sync when installing
+> on several devices. Differing polling intervals will result in missed
+> messages.
+
+Configuration
+-------------
+
+Make sure to configure
+[notifications via telegram](mod/notification-telegram.md) first. The
+additional configuration goes to `global-config-overlay`, these are the
+parameters:
+
+* `TelegramChatIdsTrusted`: an array with trusted chat ids or user names
+* `TelegramChatGroups`: define the groups a device should belong to
+
+> ℹ️ **Info**: Copy relevant configuration from
+> [`global-config`](../global-config.rsc) (the one without `-overlay`) to
+> your local `global-config-overlay` and modify it to your specific needs.
+
+Usage and invocation
+--------------------
+
+### Activating device(s)
+
+This script is capable of chatting with multiple devices. By default a
+device is passive and not acting on messages. To activate it send a message
+containing `! identity` (exclamation mark, optional space and system's
+identity). To query all dynamic ip addresses form a device named "*MikroTik*"
+send `! MikroTik`, followed by `/ip/address/print where dynamic;`.
+
+![chat to specific device](telegram-chat.d/01-chat-specific.avif)
+
+Devices can be grouped to chat with them simultaneously. The default group
+"*all*" can be activated by sending `! @all`, which will make all devices
+act on your commands.
+
+![chat to all devices](telegram-chat.d/02-chat-all.avif)
+
+Send a single exclamation mark or non-existent identity to make all
+devices passive again.
+
+### Reply to message
+
+Let's assume you received a message from a device before, and want to send
+a command to that device. No need to activate it, you can just reply to
+that message.
+
+![reply to message](telegram-chat.d/03-reply.avif)
+
+Associated messages are cleared on device reboot.
+
+### Ask for devices
+
+Send a message with a single question mark (`?`) to query for devices
+currenty online. The answer can be used for command via reply then.
+
+Known limitations
+-----------------
+
+### Do not use numeric ids!
+
+Numeric ids are valid within a session only. Usually you can use something
+like this to print all ip addresses and remove the first one:
+
+    /ip/address/print;
+    /ip/address/remove 0;
+
+This will fail when sent in separate messages. Instead you should use basic
+scripting capabilities. Try to print what you want to act on...
+
+    /ip/address/print where interface=eth;
+
+... verify and finally remove it.
+
+    /ip/address/remove [ find where interface=eth ];
+
+What does work is using the persistent ids:
+
+    /ip/address/print show-ids;
+
+The output contains an id starting with asterisk that can be used:
+
+    /ip/address/remove *E;
+
+### Mind command runtime
+
+The command is run in background while the script waits for it - about
+20 seconds at maximum. A command exceeding that time continues to run in
+background, but the output in the message is missing or truncated then.
+
+If you still want a response you can work around this by making your code
+send information on its own. Something like this should do the job:
+
+    :global SendTelegram;
+    :delay 30s;
+    $SendTelegram "Command finished" "Your command finished...";
+
+### Output size
+
+Telegram messages have a limit of 4096 characters. If output is too large it
+is truncated, and a warning is added to the message.
+
+### Sending commands to a group
+
+Adding a bot to a group allows it to send messages to that group. To allow
+it to receive messages you have to make it an admin of that group! It is
+fine to deny all permissions, though.
+
+Also adding an admin to a group can cause the group id to change, so check
+that if notifications break suddenly.
+
+See also
+--------
+
+* [Send notifications via Telegram](mod/notification-telegram.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/unattended-lte-firmware-upgrade.md b/html/doc/unattended-lte-firmware-upgrade.md
new file mode 100644 (file)
index 0000000..3e4e2d4
--- /dev/null
@@ -0,0 +1,54 @@
+Install LTE firmware upgrade
+============================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+Description
+-----------
+
+This script upgrades LTE firmware on compatible devices:
+
+* R11e-LTE
+* R11e-LTE-US
+* R11e-4G
+* R11e-LTE6
+* ... and more - probably what ever Mikrotik builds into their devices
+
+A temporary scheduler is created to be independent from terminal. Thus
+starting the upgrade process over the broadband connection is supported.
+
+Requirements and installation
+-----------------------------
+
+The firmware is downloaded over the air, so a working broadband connection
+on the lte interface to be updated is required! Having internet access from
+different gateway is not sufficient!
+
+Just install the script:
+
+    $ScriptInstallUpdate unattended-lte-firmware-upgrade;
+
+Usage and invocation
+--------------------
+
+Run the script if an upgrade for your LTE hardware is available:
+
+    /system/script/run unattended-lte-firmware-upgrade;
+
+Then be patient, go for a coffee and wait for the upgrade process to finish.
+
+See also
+--------
+
+* [Notify on LTE firmware upgrade](check-lte-firmware-upgrade.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/update-gre-address.md b/html/doc/update-gre-address.md
new file mode 100644 (file)
index 0000000..64c61f2
--- /dev/null
@@ -0,0 +1,48 @@
+Update GRE configuration with dynamic addresses
+===============================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+Running a GRE tunnel over IPSec with IKEv2 is a common scenario. This is
+easy to configure on client, but has an issue on server side: client IP
+addresses are assigned dynamically via mode-config and have to be updated
+for GRE interface.
+
+This script handles the address updates and disables the interface if the
+client is disconnected.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate update-gre-address;
+
+... and add a scheduler to run the script periodically:
+
+    /system/scheduler/add interval=30s name=update-gre-address on-event="/system/script/run update-gre-address;" start-time=startup;
+
+Configuration
+-------------
+
+The configuration goes to interface's comment. Add the client's IKEv2
+certificate CN into the comment:
+
+    /interface/gre/set comment="ikev2-client1" gre-client1;
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/update-tunnelbroker.md b/html/doc/update-tunnelbroker.md
new file mode 100644 (file)
index 0000000..342b1a1
--- /dev/null
@@ -0,0 +1,50 @@
+Update tunnelbroker configuration
+=================================
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+> ℹ️ **Info**: This script can not be used on its own but requires the base
+> installation. See [main README](../README.md) for details.
+
+Description
+-----------
+
+Connecting to [tunnelbroker.net](//tunnelbroker.net) from dynamic public
+ip address requires the address to be sent to the remote, and to be set
+locally. This script does both.
+
+Requirements and installation
+-----------------------------
+
+Just install the script:
+
+    $ScriptInstallUpdate update-tunnelbroker;
+
+Installing [ppp-on-up](ppp-on-up.md) makes this script run when ever a ppp
+connection is established.
+
+Configuration
+-------------
+
+The configuration goes to interface's comment:
+
+    /interface/6to4/set comment="tunnelbroker, user=user, id=12345, pass=s3cr3t" tunnelbroker;
+
+You should know you user name from login. The `id` is the tunnel's numeric
+id, `pass` is the *update key* found on the tunnel's advanced tab.
+
+See also
+--------
+
+* [Run scripts on ppp connection](ppp-on-up.md)
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/doc/upload-backup.md b/html/doc/upload-backup.md
new file mode 100644 (file)
index 0000000..83c9991
--- /dev/null
@@ -0,0 +1 @@
+This script has been renamed. Please see [backup-upload](backup-upload.md).
diff --git a/html/func-collection/CustomBackups.rsc b/html/func-collection/CustomBackups.rsc
new file mode 100644 (file)
index 0000000..f36b4ed
--- /dev/null
@@ -0,0 +1,22 @@
+#!rsc by Vados
+# RouterOS function-collection
+# CustomBackups Functions 
+
+:global DoLocalBackup;
+
+:set DoLocalBackup do={
+#:foreach BakScript in=([ /system/script/find where name~"*" ]) do={ import file=Scripts/$BakScript}; 
+tool e-mail export show-sensitive file=export/email.rsc;
+system scheduler export file=export/scheduler.rsc;
+system script export show-sensitive file=export/script.rsc;
+certificate export file=export/certificate_settings.rsc;
+ip firewall nat export file=export/nat.rsc;
+ip firewall filter export file=export/filter.rsc;
+ip firewall address-list export file=export/address-list.rsc;
+ip route export file=export/route.rsc;
+ip dhcp-server network export file=export/network.rsc;
+interface ethernet export file=export/ethernet.rsc;
+ip pool export file=export/pool.rsc;
+ip dns export file=export/dns.rsc;
+}
+
diff --git a/html/func-collection/DateTime.rsc b/html/func-collection/DateTime.rsc
new file mode 100644 (file)
index 0000000..2a8ad5d
--- /dev/null
@@ -0,0 +1,94 @@
+#!rsc by Vados
+# RouterOS function-collection
+# DateTime Functions 
+
+:global CurrentDateTime;
+:global FullDateTimeStamp;
+:global MiniDateTimeStamp;
+
+:set CurrentDateTime do={
+  :local rawTime [/system clock get time];
+  :local rawDate [/system clock get date];
+  :local currDate "undefined";
+     :local currTime ([:pick $rawTime 0 2] . ":" . [:pick $rawTime 3 5] . ":" . [:pick $rawTime 6 8]);
+     :if ([:len [:tonum [:pick $rawDate 0 1]]] = 0) do={
+         :set currDate ([:pick $rawDate 7 11] . "-" . [:pick $rawDate 0 3] . "-" . [:pick $rawDate 4 6]);
+       } else={:set currDate $rawDate}
+:return ($currDate . " " . $currTime);
+}
+:set FullDateTimeStamp do={
+    /system clock
+    :local vdate [get date]
+    :local vtime [get time]
+    :local vgmt  [:tonum [get gmt-offset]]; :if ($vgmt > 0x7FFFFFFF) do={:set vgmt ($vgmt - 0x100000000)}
+    :local prMntDays [:toarray "0,0,31,59,90,120,151,181,212,243,273,304,334"]
+    :local daysOnMnt [:toarray "0,31,28,31,30,31,30,31,31,30,31,30,31"]
+    :local LcaseMnts [:toarray "0,jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec"]
+    :local PcaseMnts [:toarray "0,Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec"]
+    :local UcaseMnts [:toarray "0,JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC"]
+    :local LcaseWeekDays [:toarray "thu,fri,sat,sun,mon,tue,wed"]
+    :local PcaseWeekDays [:toarray "Thu,Fri,Sat,Sun,Mon,Tue,Wed"]
+    :local UcaseWeekDays [:toarray "THU,FRI,SAT,SUN,MON,TUE,WED"]
+    :local NumbrWeekDays [:toarray "4,5,6,7,1,2,3"]
+    :local Fzerofill do={:return [:pick (100 + $1) 1 3]}
+    :local gmtSg "+"; :if ($vgmt < 0) do={:set gmtSg "-"; :set vgmt ($vgmt * -1)}
+    :local gmtHr [:pick [:totime $vgmt] 0 2]
+    :local gmtMn [:pick [:totime $vgmt] 3 5]
+    :local vdoff [:toarray "0,4,5,7,8,10"]
+    :local MM    [:pick $vdate ($vdoff->2) ($vdoff->3)]
+    :local M     [:tonum $MM]
+    :if ($vdate ~ ".../../....") do={
+        :set vdoff [:toarray "7,11,1,3,4,6"]
+        :set M     ([:find "xxanebarprayunulugepctovecANEBARPRAYUNULUGEPCTOVEC" [:pick $vdate ($vdoff->2) ($vdoff->3)] -1] / 2)
+        :if ($M>12) do={:set M ($M - 12)}
+        :set MM    [:pick (100 + $M) 1 3]
+    }
+    :local yyyy [:pick $vdate ($vdoff->0) ($vdoff->1)]
+    :local Leap "No-Leap"
+    :if ((($yyyy - 1968) % 4) = 0) do={:set Leap "Leap"; :set ($prMntDays->1) -1; :set ($prMntDays->2) 30; :set ($daysOnMnt->2) 29}
+    :local mmm  ($LcaseMnts->$M)
+    :local Mmm  ($PcaseMnts->$M)
+    :local MMM  ($UcaseMnts->$M)
+    :local MD   ($daysOnMnt->$M)
+    :local dd   [:pick $vdate ($vdoff->4) ($vdoff->5)]
+    :local d    [:tonum $dd] ; :local totd ((($yyyy - 1970) * 365) + (($yyyy - 1968) / 4) + ($prMntDays->$M) + ($d - 1))
+    :local YD   (($prMntDays->$M) + $d)
+    :local www  ($LcaseWeekDays->($totd % 7))
+    :local Www  ($PcaseWeekDays->($totd % 7))
+    :local WWW  ($UcaseWeekDays->($totd % 7))
+    :local WD   ($NumbrWeekDays->($totd % 7))
+    :local HH   [:pick $vtime 0  2]
+    :local H    [:tonum $HH]
+    :local hh   ([:tonum $HH] % 12); :if ($hh = 0) do={:set hh 12}; :set hh [$Fzerofill $hh]
+    :local h    [:tonum $hh]
+    :local a    "A"; :if ([:tonum $HH] > 11) do={:set a "P"}
+    :local aa   "$a\4D"
+    :local mm   [:pick $vtime 3  5]
+    :local m    [:tonum $mm]
+    :local ss   [:pick $vtime 6  8]
+    :local s    [:tonum $ss]
+    :local Z    "$gmtSg$gmtHr:$gmtMn"
+    :local Unix (((((($totd * 24) + $H) * 60) + $m) * 60) + $s - $vgmt)
+#    :return "$yyyy-$MM-$dd\54$HH:$mm:$ss$Z $Www (YD: $YD) (MD: $MD) (WD: $WD) $Leap $Unix"
+    :return "$yyyy$MM$dd$HH$mm$ss"
+}
+:set MiniDateTimeStamp do={
+    /system clock
+    :local vdate [get date]
+    :local vtime [get time]
+    :local vdoff [:toarray "0,4,5,7,8,10"]
+    :local MM    [:pick $vdate ($vdoff->2) ($vdoff->3)]
+    :local M     [:tonum $MM]
+    :if ($vdate ~ ".../../....") do={
+        :set vdoff [:toarray "7,11,1,3,4,6"]
+        :set M     ([:find "xxanebarprayunulugepctovecANEBARPRAYUNULUGEPCTOVEC" [:pick $vdate ($vdoff->2) ($vdoff->3)] -1] / 2)
+        :if ($M>12) do={:set M ($M - 12)}
+        :set MM    [:pick (100 + $M) 1 3]
+    }
+    :local yyyy [:pick $vdate ($vdoff->0) ($vdoff->1)]
+    :local dd   [:pick $vdate ($vdoff->4) ($vdoff->5)]
+    :local HH   [:pick $vtime 0  2]
+    :local mm   [:pick $vtime 3  5]
+    :local ss   [:pick $vtime 6  8]
+    :return "$yyyy$MM$dd$HH$mm$ss"
+}
diff --git a/html/func-collection/README.md b/html/func-collection/README.md
new file mode 100644 (file)
index 0000000..1792274
--- /dev/null
@@ -0,0 +1,17 @@
+Functions collection\r
+====================\r
+\r
+### [Section main](README.md)\r
+* [CustomBackups.rsc](CustomBackups.rsc) \r
+* [DateTime.rsc](DateTime.rsc)\r
+\r
+### [Section bash scripts](bash)\r
+* [ros-deploy.sh](bash/ros-deploy.sh)\r
+* [setup-ssh-keys.sh](bash/setup-ssh-keys.sh)\r
+* [sync-script.sh](bash/sync-script.sh)\r
+\r
+### [Section urls](urls)\r
+* [Urls for intresting scripts](urls/README.md)\r
+\r
+---\r
+[⬆️ Go back to top](#top)  \r
diff --git a/html/func-collection/bash/ros-deploy.sh b/html/func-collection/bash/ros-deploy.sh
new file mode 100644 (file)
index 0000000..1b203c8
--- /dev/null
@@ -0,0 +1,328 @@
+#!/bin/bash
+#
+# ros-deploy.sh - Bulk RouterOS Script Deployment Tool
+# Version: 1.2.1 (2025-06-26)
+#
+# A powerful and flexible tool for deploying RouterOS scripts to multiple
+# MikroTik devices simultaneously via SSH. It supports both single-host
+# deployment and batch deployment from a hosts file.
+#
+# Features:
+# - Deploy scripts to a single host or a list of hosts from a file
+# - Securely uploads and executes scripts using SCP and SSH
+# - Supports user, host, and port specification ([user@]host[:port])
+# - Automatic cleanup of temporary script files on the remote device
+# - Configurable connection timeout
+# - Detailed summary of successful and failed deployments
+# - Supports SSH key-based authentication for passwordless execution
+#
+# Usage: ./ros-deploy.sh [OPTIONS] (-h HOST | -H HOSTS_FILE) -s SCRIPT_FILE [-i IDENTITY_FILE]
+#
+# Author: Nikita Tarikin <nikita@tarikin.com>
+# GitHub: https://github.com/tarikin/ros-deploy
+# License: MIT
+#
+# Copyright (c) 2025 Nikita Tarikin
+#
+set -euo pipefail
+
+# Default values
+DEFAULT_CONNECT_TIMEOUT=5  # Default connection timeout in seconds
+NO_COLOR=false  # Default color output enabled
+
+# Color codes (only used when output is a terminal and NO_COLOR is false)
+if [ -t 1 ] && ! $NO_COLOR; then
+    COLOR_RESET='\033[0m'
+    COLOR_BOLD='\033[1m'
+    COLOR_RED='\033[1;31m'
+    COLOR_GREEN='\033[1;32m'
+    COLOR_YELLOW='\033[1;33m'
+    COLOR_BLUE='\033[1;34m'
+    COLOR_CYAN='\033[1;36m'
+else
+    COLOR_RESET='' COLOR_BOLD='' COLOR_RED='' COLOR_GREEN='' COLOR_YELLOW='' COLOR_BLUE='' COLOR_CYAN=''
+fi
+
+# Helper functions for colored output
+info() {
+    echo -e "${COLOR_BLUE}ℹ $*${COLOR_RESET}"
+}
+
+success() {
+    echo -e "${COLOR_GREEN}✅ $*${COLOR_RESET}"
+}
+
+error() {
+    echo -e "${COLOR_RED}❌ Error: $*${COLOR_RESET}" >&2
+}
+
+warning() {
+    echo -e "${COLOR_YELLOW}⚠ $*${COLOR_RESET}" >&2
+}
+
+section() {
+    echo -e "\n${COLOR_CYAN}=== $* ===${COLOR_RESET}"
+}
+
+# Help message - uses plain echo to avoid color codes in output
+show_help() {
+    cat << 'EOF'
+Deploy RouterOS scripts to one or more devices
+
+Usage: ros-deploy [OPTIONS] (-h HOST | -H HOSTS_FILE) -s SCRIPT_FILE
+
+Options:
+      --help            Show this help message and exit
+  -h, --host HOST        Single RouterOS device to deploy to (format: [user@]hostname[:port])
+  -H, --hosts FILE       File containing list of RouterOS devices (one per line, format: [user@]hostname[:port])
+  -s, --script FILE     RouterOS script file to execute
+  -t, --timeout SECONDS Connection timeout in seconds (default: 5)
+  -i, --identity FILE  SSH private key file to use for authentication
+      --no-color       Disable colored output
+
+Examples:
+  ros-deploy -H routers.txt -s config.rsc -t 10
+  ros-deploy -h admin@router.local -s config.rsc --no-color
+EOF
+    exit 0
+}
+
+# Parse command line arguments
+HOSTS_FILE=""
+SINGLE_HOST=""
+SCRIPT_FILE=""
+CONNECT_TIMEOUT="$DEFAULT_CONNECT_TIMEOUT"
+IDENTITY_FILE=""
+NO_COLOR=false
+
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --help)
+            show_help
+            ;;
+        --no-color)
+            NO_COLOR=true
+            # Re-initialize colors if needed
+            if $NO_COLOR; then
+                COLOR_RESET='' COLOR_BOLD='' COLOR_RED='' COLOR_GREEN='' COLOR_YELLOW='' COLOR_BLUE='' COLOR_CYAN=''
+            fi
+            shift
+            ;;
+        -h|--host)
+            if [ -z "$2" ] || [[ "$2" == -* ]]; then
+                echo "Error: Missing host argument for $1" >&2
+                show_help
+                exit 1
+            fi
+            SINGLE_HOST="$2"
+            shift 2
+            ;;
+        -H|--hosts)
+            if [ -z "$2" ] || [[ "$2" == -* ]]; then
+                echo "Error: Missing hosts file argument for $1" >&2
+                show_help
+                exit 1
+            fi
+            HOSTS_FILE="$2"
+            shift 2
+            ;;
+        -s|--script)
+            if [ -z "$2" ] || [[ "$2" == -* ]]; then
+                echo "Error: Missing script file argument for $1" >&2
+                show_help
+                exit 1
+            fi
+            SCRIPT_FILE="$2"
+            shift 2
+            ;;
+        -t|--timeout)
+            if [ -z "$2" ] || [[ "$2" == -* ]]; then
+                echo "Error: Missing timeout value for $1" >&2
+                show_help
+                exit 1
+            fi
+            # Validate timeout is a positive number
+            if ! [[ "$2" =~ ^[0-9]+$ ]] || [ "$2" -eq 0 ]; then
+                echo "Error: Timeout must be a positive integer" >&2
+                exit 1
+            fi
+            CONNECT_TIMEOUT="$2"
+            shift 2
+            ;;
+        -i|--identity-file)
+            if [ -z "$2" ] || [[ "$2" == -* ]]; then
+                echo "Error: Missing identity file argument for $1" >&2
+                show_help
+                exit 1
+            fi
+            IDENTITY_FILE="$2"
+            shift 2
+            ;;
+        *)
+            echo "Error: Unknown option or missing argument: $1" >&2
+            show_help
+            exit 1
+            ;;
+    esac
+done
+
+# Validate required parameters
+if { [ -z "$HOSTS_FILE" ] && [ -z "$SINGLE_HOST" ]; } || [ -z "$SCRIPT_FILE" ]; then
+    echo "Error: You must specify either --host or --hosts, and --script" >&2
+    show_help
+    exit 1
+fi
+
+TEMP_SCRIPT_NAME="$(basename "$SCRIPT_FILE")"
+
+# Check if files exist
+if [ -n "$HOSTS_FILE" ] && [ ! -f "$HOSTS_FILE" ]; then
+    error "Hosts file '$HOSTS_FILE' not found"
+    echo "Please create a file with a list of routers, one per line, in format: [user@]hostname[:port]" >&2
+    exit 1
+fi
+
+if [ -n "$IDENTITY_FILE" ] && [ ! -f "$IDENTITY_FILE" ]; then
+    error "Identity file '$IDENTITY_FILE' not found"
+    exit 1
+fi
+
+if [ ! -f "$SCRIPT_FILE" ]; then
+    error "RouterOS script file '$SCRIPT_FILE' not found"
+    echo "Please specify a valid RouterOS script file to execute" >&2
+    exit 1
+fi
+
+# Function to execute RouterOS script
+execute_routeros_script() {
+    local host="$1"
+    local user="admin"  # default user
+    local port="22"     # default SSH/SCP port (RouterOS uses the same port for both)
+    local target
+    
+    # Extract user if specified
+    if [[ "$host" == *"@"* ]]; then
+        user="${host%%@*}"
+        host="${host#*@}"
+    fi
+    
+    # Extract port if specified
+    # Extract port if specified (format: hostname:port or user@hostname:port)
+    if [[ "$host" == *":"* ]]; then
+        port="${host##*:}"
+        host="${host%:*}"
+    fi
+    
+    target="$user@$host"
+    
+    section "[$(date +'%Y-%m-%d %H:%M:%S')] Processing $target (port $port)"
+    
+    # Build base SSH/SCP options
+    local ssh_opts=("-o BatchMode=yes" "-o ConnectTimeout=$CONNECT_TIMEOUT" "-o StrictHostKeyChecking=accept-new")
+    if [ -n "$IDENTITY_FILE" ]; then
+        ssh_opts+=("-i $IDENTITY_FILE")
+    fi
+
+    # 1. First, copy the script to the router using SCP
+    info "Uploading script to router..."
+    # shellcheck disable=SC2086
+    if scp ${ssh_opts[*]} -P "$port" "$SCRIPT_FILE" "$target:$TEMP_SCRIPT_NAME"; then
+        
+        info "Script uploaded successfully, executing..."
+        
+        # 2. Only execute SSH if SCP was successful
+        # shellcheck disable=SC2086
+        if ssh ${ssh_opts[*]} -p "$port" "$target" "/import verbose=no $TEMP_SCRIPT_NAME; /file/remove $TEMP_SCRIPT_NAME"; then
+            success "Successfully executed script on $target"
+            return 0
+        else
+            error "Failed to execute script on $target"
+            return 1
+        fi
+    else
+        error "Failed to upload script to $target"
+        return 1
+    fi
+}
+
+# Initialize tracking variables
+FAILED_HOSTS=()
+TOTAL=0
+SUCCESS=0
+
+# Process hosts
+section "Starting RouterOS deployment"
+if [ -n "$SINGLE_HOST" ]; then
+    info "Single host:   $SINGLE_HOST"
+fi
+if [ -n "$HOSTS_FILE" ]; then
+    info "Hosts file:    $HOSTS_FILE"
+fi
+info "Script file:   $SCRIPT_FILE"
+info "Connect timeout: $CONNECT_TIMEOUT seconds"
+info "SSH Key:       $(ssh-add -l 2>/dev/null || echo "No SSH key loaded in agent")"
+echo -e "${COLOR_YELLOW}----------------------------------------${COLOR_RESET}"
+
+# Process single host if specified
+if [ -n "$SINGLE_HOST" ]; then
+    ((TOTAL++))
+    if execute_routeros_script "$SINGLE_HOST"; then
+        ((SUCCESS++))
+    else
+        FAILED_HOSTS+=("$SINGLE_HOST")
+    fi
+fi
+
+# Process hosts file if specified
+if [ -n "$HOSTS_FILE" ]; then
+    # Read hosts file into an array, skipping comments and empty lines
+    HOSTS=()
+    while IFS= read -r line; do
+        # Remove comments and trim whitespace
+        line="${line%%#*}"  # Remove comments
+        line="${line##*([[:space:]])}"  # Remove leading whitespace
+        line="${line%%*([[:space:]])}"  # Remove trailing whitespace
+        
+        # Skip empty lines
+        [ -n "$line" ] && HOSTS+=("$line")
+    done < "$HOSTS_FILE"
+
+    if [ ${#HOSTS[@]} -eq 0 ]; then
+        error "${COLOR_RED}No valid hosts found in $HOSTS_FILE${COLOR_RESET}"
+        exit 1
+    fi
+
+    success "${COLOR_GREEN}Found ${#HOSTS[@]} host(s) in file${COLOR_RESET}"
+
+    # Process each host from the file
+    for host in "${HOSTS[@]}"; do
+        ((TOTAL++))
+        if execute_routeros_script "$host"; then
+            ((SUCCESS++))
+        else
+            FAILED_HOSTS+=("$host")
+        fi
+    done
+fi
+
+# Print summary
+section "Deployment Summary"
+info "Total hosts:    $TOTAL"
+if [ $SUCCESS -gt 0 ]; then
+    success "Successful:     $SUCCESS"
+else
+    info "Successful:     $SUCCESS"
+fi
+
+if [ ${#FAILED_HOSTS[@]} -gt 0 ]; then
+    error "Failed:         ${#FAILED_HOSTS[@]}"
+    echo -e "\n${COLOR_RED}Failed hosts:${COLOR_RESET}"
+    printf '  - %s\n' "${FAILED_HOSTS[@]}"
+    exit 1
+else
+    success "Failed:         ${#FAILED_HOSTS[@]}"
+fi
+
+echo
+success "All deployments completed successfully!"
+exit 0
diff --git a/html/func-collection/bash/setup-ssh-keys.sh b/html/func-collection/bash/setup-ssh-keys.sh
new file mode 100644 (file)
index 0000000..418b981
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/bash
+# Setup SSH keys for RouterOS router
+
+ROUTER_IP="10.30.30.10:2222"
+ROUTER_USER="mikro_bak"
+KEY_NAME="mikro_bak_rsa"
+KEY_PATH="$HOME/.ssh/$KEY_NAME"
+
+# Check/create .ssh directory
+if [ ! -d "$HOME/.ssh" ]; then
+    mkdir -p "$HOME/.ssh"
+    chmod 700 "$HOME/.ssh"
+fi
+
+# Generate SSH key if it doesn't exist
+if [ ! -f "$KEY_PATH" ]; then
+    ssh-keygen -t rsa -b 4096 -f "$KEY_PATH" -N "" -C "routeros-$ROUTER_IP"
+fi
+
+# Upload and import key on router
+scp "$KEY_PATH.pub" "ssh://$ROUTER_USER@$ROUTER_IP":/
+ssh "ssh://$ROUTER_USER@$ROUTER_IP" "/user ssh-keys import public-key-file=$KEY_NAME.pub user=$ROUTER_USER"
+
+# Verify key works
+if ! ssh -i "$KEY_PATH" -o PasswordAuthentication=no "$ROUTER_USER@$ROUTER_IP" "/system identity print" >/dev/null 2>&1; then
+    echo "Error: SSH key authentication failed"
+    exit 1
+fi
diff --git a/html/func-collection/bash/sync-script.sh b/html/func-collection/bash/sync-script.sh
new file mode 100644 (file)
index 0000000..f559052
--- /dev/null
@@ -0,0 +1,139 @@
+#!/bin/bash
+# Upload all scripts and sync to RouterOS
+
+ROUTER_IP="192.168.88.1"
+ROUTER_USER="admin"
+SSH_KEY="$HOME/.ssh/id_rsa_routeros"
+RSC_FILE="import_scripts.rsc"
+
+# Require router-specific SSH key
+if [ ! -f "$SSH_KEY" ]; then
+    echo "Error: SSH key not found at $SSH_KEY"
+    echo "Run ./setup-ssh-keys.sh to generate the key"
+    exit 1
+fi
+
+SSH_OPTS="-i $SSH_KEY -o PasswordAuthentication=no"
+
+# Generate .rsc import file
+echo "Generating import file..."
+
+# Get git commit hash and dirty status
+GIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
+if [ -z "$(git status --porcelain 2>/dev/null)" ]; then
+    GIT_STATUS="$GIT_HASH"
+else
+    GIT_STATUS="$GIT_HASH-dirty"
+fi
+
+# Get current date with timezone offset
+GEN_DATE=$(date '+%Y-%m-%d %H:%M:%S %z')
+
+# Write header
+cat > "$RSC_FILE" << RSC_HEADER
+# RouterOS script import file
+# Generated by sync-script.sh
+# Date: $GEN_DATE
+# Git: $GIT_STATUS
+
+RSC_HEADER
+
+# Write header for creating scripts section
+printf ':put "=== Creating scripts ==="\n' >> "$RSC_FILE"
+printf '\n' >> "$RSC_FILE"
+
+# Create function for script creation
+printf ':global createScriptIfMissing do={\n' >> "$RSC_FILE"
+printf '    :if ([/system script print count-only where name=$scriptName] = 0) do={\n' >> "$RSC_FILE"
+printf '        :put ("Creating script: " . $scriptName);\n' >> "$RSC_FILE"
+printf '        /system script add name=$scriptName\n' >> "$RSC_FILE"
+printf '    }\n' >> "$RSC_FILE"
+printf '}\n\n' >> "$RSC_FILE"
+
+# Write all add commands first
+for SCRIPT_FILE in scripts/config/*.rsc scripts/check/*.rsc; do
+    if [ ! -f "$SCRIPT_FILE" ]; then
+        continue
+    fi
+    
+    SCRIPT_NAME=$(sed -n '1p' "$SCRIPT_FILE" | sed 's/^# //')
+    POLICY=$(sed -n '3p' "$SCRIPT_FILE" | sed -n 's/^# policy=\(.*\)/\1/p')
+    
+    if [ -z "$POLICY" ]; then
+        echo "Error: Missing policy in $SCRIPT_FILE (line 3 should be '# policy=read,write')"
+        exit 1
+    fi
+    
+    # Call function with script name
+    printf '$createScriptIfMissing scriptName="%s"\n' "$SCRIPT_NAME" >> "$RSC_FILE"
+done
+
+# Empty line between sections
+printf '\n' >> "$RSC_FILE"
+printf ':put ""\n' >> "$RSC_FILE"
+printf ':put "=== Updating scripts ==="\n' >> "$RSC_FILE"
+printf '\n' >> "$RSC_FILE"
+
+# Write all set commands
+for SCRIPT_FILE in scripts/config/*.rsc scripts/check/*.rsc; do
+    if [ ! -f "$SCRIPT_FILE" ]; then
+        continue
+    fi
+    
+    SCRIPT_NAME=$(sed -n '1p' "$SCRIPT_FILE" | sed 's/^# //')
+    COMMENT=$(sed -n '2p' "$SCRIPT_FILE" | sed 's/^# //')
+    POLICY=$(sed -n '3p' "$SCRIPT_FILE" | sed -n 's/^# policy=\(.*\)/\1/p')
+    
+    # Escape comment for RouterOS (escape quotes)
+    ESCAPED_COMMENT=$(echo "$COMMENT" | sed 's/"/\\"/g')
+    
+    # Read script content and escape for RouterOS .rsc format
+    # Loop through file line by line, escape special chars, join with \n
+    SCRIPT_CONTENT=""
+    while IFS= read -r line || [ -n "$line" ]; do
+        # Escape backslashes, dollar signs, and quotes
+        line=$(echo "$line" | sed 's/\\/\\\\/g' | sed 's/\$/\\$/g' | sed 's/"/\\"/g')
+        if [ -z "$SCRIPT_CONTENT" ]; then
+            SCRIPT_CONTENT="$line"
+        else
+            SCRIPT_CONTENT="$SCRIPT_CONTENT\\n$line"
+        fi
+    done < "$SCRIPT_FILE"
+    
+    # Write set command directly to file
+    printf ':put "Updating script: %s"; /system script set "%s" source="' "$SCRIPT_NAME" "$SCRIPT_NAME" >> "$RSC_FILE"
+    echo "$SCRIPT_CONTENT" | sed 's/\\n/\\n\\\n    /g' >> "$RSC_FILE"
+    printf '" comment="%s" policy=%s\n\n' "$ESCAPED_COMMENT" "$POLICY" >> "$RSC_FILE"
+done
+
+# Delete the helper function
+printf '\n:set createScriptIfMissing;\n' >> "$RSC_FILE"
+
+# Print all scripts (excluding source/contents)
+printf ':put ""\n:put "=== All system scripts ==="\n/system script print proplist=name,comment,owner,policy,dont-require-permissions,run-count,last-started,invalid\n' >> "$RSC_FILE"
+
+# Show generated .rsc file and ask for confirmation
+echo ""
+echo "=== Generated import file ($RSC_FILE) ==="
+head -20 "$RSC_FILE"
+echo "..."
+echo ""
+read -p "Upload and import this file? (y/N): " -n 1 -r
+echo
+if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+    echo "Aborted"
+    rm -f "$RSC_FILE"
+    exit 1
+fi
+
+# Upload .rsc file
+echo "Uploading import file..."
+scp $SSH_OPTS "$RSC_FILE" "$ROUTER_USER@$ROUTER_IP":/
+
+# Import on router
+echo "Importing scripts..."
+ssh $SSH_OPTS "$ROUTER_USER@$ROUTER_IP" "/import file-name=$RSC_FILE"
+
+# Cleanup
+rm -f "$RSC_FILE"
+echo "Done!"
diff --git a/html/func-collection/urls/README.md b/html/func-collection/urls/README.md
new file mode 100644 (file)
index 0000000..eadb18e
--- /dev/null
@@ -0,0 +1,13 @@
+Urls for intresting scripts
+===========================
+  
+[⬅️ Go back to section README ](../README.md)  
+  
+* [!Хакер - Господин Микротиков. Автоматизируем сбор и обработку данных с оборудования MikroTik](https://telegra.ph/Haker---Gospodin-Mikrotikov-Avtomatiziruem-sbor-i-obrabotku-dannyh-s-oborudovaniya-MikroTik-09-11)
+* [!GPG manual](https://habr.com/ru/articles/754128/?ysclid=mm964ftl9481230246)   
+  
+  
+--- 
+[⬅️ Go back to section README ](../README.md)  
+[⬆️ Go back to top](#top)  
+  
\ No newline at end of file
diff --git a/html/ipcalc.rsc b/html/ipcalc.rsc
new file mode 100644 (file)
index 0000000..2aad0cb
--- /dev/null
@@ -0,0 +1,57 @@
+#!rsc by Vados
+# RouterOS script: ipcalc
+# Script comment: ip address calculation
+#
+#
+# requires RouterOS, version=7.19
+
+:global IPCalc;
+:global IPCalcReturn;
+
+# print netmask, network, min host, max host and broadcast
+:set IPCalc do={ :onerror Err {
+  :local Input [ :tostr $1 ];
+  :global FormatLine;
+  :global IPCalcReturn;
+  :local Values [ $IPCalcReturn $1 ];
+  :put [ :tocrlf ( \
+    [ $FormatLine "Address" ($Values->"address") ] . "\n" . \
+    [ $FormatLine "Netmask" ($Values->"netmask") ] . "\n" . \
+    [ $FormatLine "Network" ($Values->"network") ] . "\n" . \
+    [ $FormatLine "HostMin" ($Values->"hostmin") ] . "\n" . \
+    [ $FormatLine "HostMax" ($Values->"hostmax") ] . "\n" . \
+    [ $FormatLine "Broadcast" ($Values->"broadcast") ]) ];
+} do={
+  :global ExitOnError; $ExitOnError $0 $Err;
+} }
+
+# calculate and return netmask, network, min host, max host and broadcast
+:set IPCalcReturn do={
+  :local Input [ :tostr $1 ];
+  :global NetMask4;
+  :global NetMask6;
+  :local Address [ :pick $Input 0 [ :find $Input "/" ] ];
+  :local Bits [ :tonum [ :pick $Input ([ :find $Input "/" ] + 1) [ :len $Input ] ] ];
+  :local Mask;
+  :local One;
+  :if ([ :typeof [ :toip $Address ] ] = "ip") do={
+    :set Address [ :toip $Address ];
+    :set Mask [ $NetMask4 $Bits ];
+    :set One 0.0.0.1;
+  } else={
+    :set Address [ :toip6 $Address ];
+    :set Mask [ $NetMask6 $Bits ];
+    :set One ::1;
+  }
+  :local Return ({
+    "address"=$Address;
+    "netmask"=$Mask;
+    "networkaddress"=($Address & $Mask);
+    "networkbits"=$Bits;
+    "network"=(($Address & $Mask) . "/" . $Bits);
+    "hostmin"=(($Address & $Mask) | $One);
+    "hostmax"=(($Address | ~$Mask) ^ $One);
+    "broadcast"=($Address | ~$Mask);
+  });
+  :return $Return;
+}
diff --git a/html/logo/telegram.md b/html/logo/telegram.md
new file mode 100644 (file)
index 0000000..36f7bc4
--- /dev/null
@@ -0,0 +1,274 @@
+Telegram
+========
+
+[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network)
+[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers)
+[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts)
+[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)
+
+[⬅️ Go back to main README](../README.md)
+
+We have [Miss Rose Bot ↗️](https://t.me/MissRose_bot) in our
+[RouterOS-Scripts ↗️](https://t.me/routeros_scripts) Telegram group,
+always kind and ready to help moderate.
+
+Notes
+-----
+
+### README
+
+    /save readme Please read the [main README](https://rsc.eworm.de/) to understand how things work and to get the base installation right.
+
+### Available scripts
+
+#### accesslist-duplicates
+
+    /save accesslist-duplicates Find and remove access list duplicates with [accesslist-duplicates](https://rsc.eworm.de/doc/accesslist-duplicates.md).
+
+#### backup-cloud
+
+    /save backup-cloud Upload backup to Mikrotik cloud with [backup-cloud](https://rsc.eworm.de/doc/backup-cloud.md).
+
+#### backup-email
+
+    /save backup-email Send backup via e-mail with [backup-email](https://rsc.eworm.de/doc/backup-email.md).
+
+#### backup-partition
+
+    /save backup-partition Save configuration to fallback partition with [backup-partition](https://rsc.eworm.de/doc/backup-partition.md).
+
+#### backup-upload
+
+    /save backup-upload Upload backup to server with [backup-upload](https://rsc.eworm.de/doc/backup-upload.md).
+
+#### capsman-download-packages
+
+    /save capsman-download-packages Download packages for CAP upgrade from CAPsMAN with [capsman-download-packages](https://rsc.eworm.de/doc/capsman-download-packages.md).
+
+#### capsman-rolling-upgrade
+
+    /save capsman-rolling-upgrade Run rolling CAP upgrades from CAPsMAN with [capsman-rolling-upgrade](https://rsc.eworm.de/doc/capsman-rolling-upgrade.md).
+
+#### certificate-renew-issued
+
+    /save certificate-renew-issued Renew locally issued certificates with [certificate-renew-issued](https://rsc.eworm.de/doc/certificate-renew-issued.md).
+
+#### check-certificates
+
+    /save check-certificates Renew certificates and notify on expiration with [check-certificates](https://rsc.eworm.de/doc/check-certificates.md).
+
+#### check-health
+
+    /save check-health Notify about health state with [check-health](https://rsc.eworm.de/doc/check-health.md).
+
+#### check-lte-firmware-upgrade
+
+    /save check-lte-firmware-upgrade Notify on LTE firmware upgrade with [check-lte-firmware-upgrade](https://rsc.eworm.de/doc/check-lte-firmware-upgrade.md).
+
+#### check-perpetual-license
+
+    /save check-perpetual-license Check perpetual license on CHR with [check-perpetual-license](https://rsc.eworm.de/doc/check-perpetual-license.md).
+
+#### check-routeros-update
+
+    /save check-routeros-update Notify on RouterOS update with [check-routeros-update](https://rsc.eworm.de/doc/check-routeros-update.md).
+
+#### collect-wireless-mac
+
+    /save collect-wireless-mac Collect MAC addresses in wireless access list with [collect-wireless-mac](https://rsc.eworm.de/doc/collect-wireless-mac.md).
+
+#### daily-psk
+
+    /save daily-psk Use wireless network with [daily-psk](https://rsc.eworm.de/doc/daily-psk.md).
+
+#### dhcp-lease-comment
+
+    /save dhcp-lease-comment Comment DHCP leases with [dhcp-lease-comment](https://rsc.eworm.de/doc/dhcp-lease-comment.md).
+
+#### dhcp-to-dns
+
+    /save dhcp-to-dns Create DNS records for DHCP leases with [dhcp-to-dns](https://rsc.eworm.de/doc/dhcp-to-dns.md).
+
+#### firmware-upgrade-reboot
+
+    /save firmware-upgrade-reboot Automatically upgrade firmware and reboot with [firmware-upgrade-reboot](https://rsc.eworm.de/doc/firmware-upgrade-reboot.md).
+
+#### fw-addr-lists
+
+    /save fw-addr-lists Download, import and update firewall address-lists with [fw-addr-lists](https://rsc.eworm.de/doc/fw-addr-lists.md).
+
+#### global-wait
+
+    /save global-wait Wait for global functions und modules with [global-wait](https://rsc.eworm.de/doc/global-wait.md).
+
+#### gps-track
+
+    /save gps-track Send GPS position to server with [gps-track](https://rsc.eworm.de/doc/gps-track.md).
+
+#### hotspot-to-wpa
+
+    /save hotspot-to-wpa Use WPA network with [hotspot-to-wpa](https://rsc.eworm.de/doc/hotspot-to-wpa.md).
+
+#### ipsec-to-dns
+
+    /save ipsec-to-dns Create DNS records for IPSec peers with [ipsec-to-dns](https://rsc.eworm.de/doc/ipsec-to-dns.md).
+
+#### ipv6-update
+
+    /save ipv6-update Update configuration on IPv6 prefix change with [ipv6-update](https://rsc.eworm.de/doc/ipv6-update.md).
+
+#### ip-addr-bridge
+
+    /save ip-addr-bridge Manage IP addresses with [ip-addr-bridge](https://rsc.eworm.de/doc/ip-addr-bridge.md).
+
+#### lease-script
+
+    /save lease-script Run other scripts on DHCP lease with [lease-script](https://rsc.eworm.de/doc/lease-script.md).
+
+#### leds-mode
+
+    /save leds-mode Manage LEDs dark mode with [leds-mode](https://rsc.eworm.de/doc/leds-mode.md).
+
+#### log-forward
+
+    /save log-forward Forward log messages via notification with [log-forward](https://rsc.eworm.de/doc/log-forward.md).
+
+#### mode-button
+
+    /save mode-button Mode button with [mode-button](https://rsc.eworm.de/doc/mode-button.md).
+
+#### netwatch-dns
+
+    /save netwatch-dns Manage DNS and DoH servers from netwatch with [netwatch-dns](https://rsc.eworm.de/doc/netwatch-dns.md).
+
+#### netwatch-notify
+
+    /save netwatch-notify Notify on host up and down with [netwatch-notify](https://rsc.eworm.de/doc/netwatch-notify.md).
+
+#### ospf-to-leds
+
+    /save ospf-to-leds Visualize OSPF state via LEDs with [ospf-to-leds](https://rsc.eworm.de/doc/ospf-to-leds.md).
+
+#### packages-update
+
+    /save packages-update Manage system update with [packages-update](https://rsc.eworm.de/doc/packages-update.md).
+
+#### ppp-on-up
+
+    /save ppp-on-up Run scripts on ppp connection with [ppp-on-up](https://rsc.eworm.de/doc/ppp-on-up.md).
+
+#### sms-action
+
+    /save sms-action Act on received SMS with [sms-action](https://rsc.eworm.de/doc/sms-action.md).
+
+#### sms-forward
+
+    /save sms-forward Forward received SMS with [sms-forward](https://rsc.eworm.de/doc/sms-forward.md).
+
+#### super-mario-theme
+
+    /save super-mario-theme Play Super Mario theme with [super-mario-theme](https://rsc.eworm.de/doc/super-mario-theme.md).
+
+#### telegram-chat
+
+    /save telegram-chat Chat with [telegram-chat](https://rsc.eworm.de/doc/telegram-chat.md).
+
+#### unattended-lte-firmware-upgrade
+
+    /save unattended-lte-firmware-upgrade Install LTE firmware upgrade with [unattended-lte-firmware-upgrade](https://rsc.eworm.de/doc/unattended-lte-firmware-upgrade.md).
+
+#### update-gre-address
+
+    /save update-gre-address Update GRE configuration with [update-gre-address](https://rsc.eworm.de/doc/update-gre-address.md).
+
+#### update-tunnelbroker
+
+    /save update-tunnelbroker Update tunnelbroker configuration with [update-tunnelbroker](https://rsc.eworm.de/doc/update-tunnelbroker.md).
+
+### Available modules
+
+#### mod/bridge-port-to
+
+    /save mod/bridge-port-to Manage ports in bridge with [mod/bridge-port-to](https://rsc.eworm.de/doc/mod/bridge-port-to.md).
+
+#### mod/bridge-port-vlan
+
+    /save mod/bridge-port-vlan Manage VLANs on bridge ports with [mod/bridge-port-vlan](https://rsc.eworm.de/doc/mod/bridge-port-vlan.md).
+
+#### mod/inspectvar
+
+    /save mod/inspectvar Inspect variables with [mod/inspectvar](https://rsc.eworm.de/doc/mod/inspectvar.md).
+
+#### mod/ipcalc
+
+    /save mod/ipcalc IP address calculation with [mod/ipcalc](https://rsc.eworm.de/doc/mod/ipcalc.md).
+
+#### mod/notification-email
+
+    /save mod/notification-email Send notifications via e-mail with [mod/notification-email](https://rsc.eworm.de/doc/mod/notification-email.md).
+
+#### mod/notification-gotify
+
+    /save mod/notification-gotify Send notifications via Gotify with [mod/notification-gotify](https://rsc.eworm.de/doc/mod/notification-gotify.md).
+
+#### mod/notification-matrix
+
+    /save mod/notification-matrix Send notifications via Matrix with [mod/notification-matrix](https://rsc.eworm.de/doc/mod/notification-matrix.md).
+
+#### mod/notification-ntfy
+
+    /save mod/notification-ntfy Send notifications via Ntfy with [mod/notification-ntfy](https://rsc.eworm.de/doc/mod/notification-ntfy.md).
+
+#### mod/notification-telegram
+
+    /save mod/notification-telegram Send notifications via Telegram with [mod/notification-telegram](https://rsc.eworm.de/doc/mod/notification-telegram.md).
+
+#### mod/scriptrunonce
+
+    /save mod/scriptrunonce Download script and run it once with [mod/scriptrunonce](https://rsc.eworm.de/doc/mod/scriptrunonce.md).
+
+#### mod/ssh-keys-import
+
+    /save mod/ssh-keys-import Import ssh keys for public key authentication with [mod/ssh-keys-import](https://rsc.eworm.de/doc/mod/ssh-keys-import.md).
+
+### Other
+
+#### Installing from branches
+
+    /save branches Living on the edge or testing new features? Learn how to [switch specific scripts or the complete installation to different branches](https://rsc.eworm.de/BRANCHES.md).
+
+#### Certificate name from browser
+
+    /save certificate-name-from-browser Running or accessing a custom service and looking for the CA certificate? Get the [certificate name from browser](https://rsc.eworm.de/CERTIFICATES.md).
+
+#### Debug output and logs
+
+    /save debug Enable [debug output and logs](https://rsc.eworm.de/DEBUG.md) for more information on what happens.
+
+#### Donate
+
+    /save donate This project is developed in private spare time and usage is free of charge for you. If you like the scripts and think this is of value for you or your business [please consider a donation](https://rsc.eworm.de/#donate). Thanks!
+
+#### Fix existing installation
+
+    /save fix-installation [Fix existing installation] Your installation broke and you do not know back and forth? See how to [fix an existing installation](https://rsc.eworm.de/INITIAL-COMMANDS.md#fix-existing-installation).
+
+#### Next!
+
+    /save next Another satisfied user. 😊 Next, please!
+
+#### Off-topic
+
+    /save off-topic Please note this group is not about MikroTik RouterOS in general, but [RouterOS Scripts](https://rsc.eworm.de/). Your request is not about scripting at all, so please discuss somewhere else. See the [MikroTik RouterOS users (english)](https://t.me/RouterOS_users_english) group or official Mikrotik forums (https://forum.mikrotik.com/).
+
+Greeting
+--------
+
+    /setwelcome Hello {mention}, and welcome to {chatname}!
+    Please note this group is not about RouterOS in general, but [RouterOS Scripts](https://rsc.eworm.de/). Also pay attention to [rules](https://t.me/routeros_scripts/4), thanks!
+
+---
+[⬅️ Go back to main README](../README.md)  
+[⬆️ Go back to top](#top)
diff --git a/html/mikro_bak_rsa.pub b/html/mikro_bak_rsa.pub
new file mode 100644 (file)
index 0000000..2f63755
--- /dev/null
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC77aronyggZEHIwk6bcZM32aLGsqqub95bUjO/r0u3ahRN4KN+ayo+TwISJGOTIJov9qBe4igOCEYUVAZ7FqfJVck1iVIWAZrVa4KfMkESWkNCjmwfIoHa29wVyNC/VIsndyLXUYMgiEsCqVDi94034IWSNdaAuhxNNxw+cLr6uCcwuQkr+jB6NYSMsWX+2LnR2pM+fL45P1Or16L1xjV+olTivnoarkbkZiP0LMp8llfkXnCRnTqcIIkZFZNeCty2gQUHiv6+fauXU/JqoLPYBdaZbKMdwZnaCnxfDlt57Mnx7k3gcE5mT+ewXxvWcw2kDbSI313e6AjVflKCNh2B mikro_bak@amster
diff --git a/html/mod/AM-TG-Bot.rsc b/html/mod/AM-TG-Bot.rsc
new file mode 100644 (file)
index 0000000..3477c3c
--- /dev/null
@@ -0,0 +1,189 @@
+#!rsc by Vados
+# RouterOS script: AM-TG-Bot
+# Script comment: Use Telegram to chat with your Router and send commands
+# Copyright (c) 2007-2026 Vados <vados@vados.ru>
+#
+#
+# requires RouterOS, version=7.19
+# requires device-mode, fetch
+:local ExitOK false;
+:onerror Err {
+  :global GlobalConfReady; :global GlobalFuncReady;
+  :retry {:if ($GlobalConfReady != true || $GlobalFuncReady != true) \
+      do={:error ("Global configs or functions not ready.")};}; delay=500ms max=50;
+  :local ScriptName [ :jobname ];
+
+  :global Identity;
+  :global TelegramChatActive;
+  :global TelegramChatGroups;
+  :global TelegramChatId;
+  :global TelegramChatIdsTrusted;
+  :global TelegramChatOffset;
+  :global TelegramChatRunTime;
+  :global TelegramMessageIDs;
+  :global TelegramRandomDelay;
+  :global TelegramTokenId;
+
+  :global CertificateAvailable;
+  :global EitherOr;
+  :global EscapeForRegEx;
+  :global FileExists;
+  :global GetRandom20CharAlNum;
+  :global IfThenElse;
+  :global LogPrint;
+  :global LogPrintVerbose;
+  :global MAX;
+  :global MIN;
+  :global MkDir;
+  :global RandomDelay;
+  :global RmDir;
+  :global ScriptLock;
+  :global SendTelegram2;
+  :global SymbolForNotification;
+  :global ValidateSyntax;
+  :global WaitForFile;
+  
+  :if ([ $ScriptLock $ScriptName ] = false) do={
+    :set ExitOK true;
+    :error false;
+  }
+  
+  :if ([ :len [ /system/scheduler/find where name=$ScriptName ] ] = 0) do={
+    /system/scheduler/add start-time=startup interval=30s name=$ScriptName on-event="/system/script/run $ScriptName;" comment="Telegram Chat Bot";
+  }
+
+  :if ([ :typeof $TelegramChatOffset ] != "array") do={
+    :set TelegramChatOffset { 0; 0; 0 };
+  }
+  :if ([ :typeof $TelegramRandomDelay ] != "num") do={
+    :set TelegramRandomDelay 0;
+  }
+
+  :if ([ $CertificateAvailable "Go Daddy Root Certificate Authority - G2" "fetch" ] = false) do={
+    $LogPrint warning $ScriptName ("Downloading required certificate failed.");
+    :set ExitOK true;
+    :error false;
+  }
+
+  $RandomDelay $TelegramRandomDelay;
+  :local Data false;
+  :for I from=1 to=4 do={
+    :if ($Data = false) do={
+      :onerror Err {
+        :set Data ([ /tool/fetch check-certificate=yes-without-crl \
+          ("https://api.telegram.org/bot" . $TelegramTokenId . "/getUpdates?offset=" . \
+          $TelegramChatOffset->0 . "&allowed_updates=%5B%22message%22%5D") output=user as-value ]->"data");
+        :set TelegramRandomDelay [ $MAX 0 ($TelegramRandomDelay - 1) ];
+      } do={
+        :if ($I < 4) do={
+          $LogPrint debug $ScriptName ("Fetch failed, " . $I . ". try: " . $Err);
+          :set TelegramRandomDelay [ $MIN 15 ($TelegramRandomDelay + 5) ];
+          :delay (($I * $I) . "s");
+        }
+      }
+    }
+  }
+  :if ($Data = false) do={
+    $LogPrint warning $ScriptName ("Failed getting updates.");
+    :set ExitOK true;
+    :error false;
+  }
+
+  :local JSON [ :deserialize from=json value=$Data ];
+  :local UpdateID 0;
+  :local Uptime [ /system/resource/get uptime ];
+  :foreach Update in=($JSON->"result") do={
+    :set UpdateID ($Update->"update_id");
+    $LogPrintVerbose debug $ScriptName ("Update " . $UpdateID . ": " . [ :serialize to=json $Update ]);
+
+    :local Message ($Update->"message");
+    :local IsAnyReply ([ :typeof ($Message->"reply_to_message") ] = "array");
+    :local IsMyReply ($TelegramMessageIDs->[ :tostr ($Message->"reply_to_message"->"message_id") ]);
+    :if (($IsMyReply = 1 || $TelegramChatOffset->0 > 0 || $Uptime > 5m) && $UpdateID >= $TelegramChatOffset->2) do={
+      :local Trusted false;
+      :local Chat ($Message->"chat");
+      :local From ($Message->"from");
+      :local Command ($Message->"text");
+      :local ThreadId [ $IfThenElse ($Message->"is_topic_message") ($Message->"message_thread_id") "" ];
+      :foreach IdsTrusted in=($TelegramChatId, $TelegramChatIdsTrusted) do={
+        :if ($From->"id" = $IdsTrusted || \
+             $From->"username" = $IdsTrusted || \
+             $Chat->"id" = $IdsTrusted) do={
+          :set Trusted true;
+        }
+      }
+      :if ($Trusted = true) do={
+        :local Done false;
+        :if ($Command = "?") do={
+          $LogPrint info $ScriptName ("Sending notice for update " . $UpdateID . ".");
+          $SendTelegram2 ({ origin=$ScriptName; chatid=($Chat->"id"); silent=true; \
+            replyto=($Message->"message_id"); threadid=$ThreadId; \
+            subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \
+            message=([ $IfThenElse ([ :len ($From->"first_name") ] > 0) ("Hello " . ($From->"first_name") . "!\n\n") ] . \
+              "Online" . [ $IfThenElse $TelegramChatActive " (and active!)" ] . ", awaiting your commands!") });
+          :set Done true;
+        }
+        :if ($Done = false && [ :pick $Command 0 1 ] = "!") do={
+          :if ($Command ~ ("^! *(" . [ $EscapeForRegEx $Identity ] . "|@" . $TelegramChatGroups . ")\$")) do={
+            :set TelegramChatActive true;
+          } else={
+            :set TelegramChatActive false;
+          }
+          $LogPrint info $ScriptName ("Now " . [ $IfThenElse $TelegramChatActive "active" "passive" ] . \
+            " from update " . $UpdateID . "!");
+          :set Done true;
+        }
+        :if ($Done = false && ($IsMyReply = 1 || ($IsAnyReply = false && \
+             $TelegramChatActive = true)) && [ :len $Command ] > 0) do={
+          :if ([ $ValidateSyntax $Command ] = true) do={
+            :local State "";
+            :local File ("tmpfs/telegram-chat/" . [ $GetRandom20CharAlNum 6 ]);
+            :if ([ $MkDir "tmpfs/telegram-chat" ] = false) do={
+              $LogPrint error $ScriptName ("Failed creating directory!");
+              :set ExitOK true;
+              :error false;
+            }
+            $LogPrint info $ScriptName ("Running command from update " . $UpdateID . ": " . $Command);
+            :execute script=(":do {\n" . $Command . "\n} on-error={ /file/add name=\"" . $File . ".failed\" };" . \
+              "/file/add name=\"" . $File . ".done\"") file=($File . "\00");
+            :if ([ $WaitForFile ($File . ".done") [ $EitherOr $TelegramChatRunTime 20s ] ] = false) do={
+              :set State ([ $SymbolForNotification "warning-sign" ] . "The command did not finish, still running in background.\n\n");
+            }
+            :if ([ $FileExists ($File . ".failed") ] = true) do={
+              :set State ([ $SymbolForNotification "cross-mark" ] . "The command failed with an error!\n\n");
+            }
+            :local Content ([ /file/read chunk-size=32768 file=$File as-value ]->"data");
+            $SendTelegram2 ({ origin=$ScriptName; chatid=($Chat->"id"); silent=true; \
+              replyto=($Message->"message_id"); threadid=$ThreadId; \
+              subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \
+              message=([ $SymbolForNotification "gear" ] . "Command:\n" . $Command . "\n\n" . \
+                $State . [ $IfThenElse ([ :len $Content ] > 0) \
+                ([ $SymbolForNotification "memo" ] . "Output:\n" . $Content) \
+                ([ $SymbolForNotification "memo" ] . "No output.") ]) });
+            $RmDir "tmpfs/telegram-chat";
+          } else={
+            $LogPrint info $ScriptName ("The command from update " . $UpdateID . " failed syntax validation!");
+            $SendTelegram2 ({ origin=$ScriptName; chatid=($Chat->"id"); silent=false; \
+              replyto=($Message->"message_id"); threadid=$ThreadId; \
+              subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \
+              message=([ $SymbolForNotification "gear" ] . "Command:\n" . $Command . "\n\n" . \
+                [ $SymbolForNotification "cross-mark" ] . "The command failed syntax validation!") });
+          }
+        }
+      } else={
+        :local MessageText ("Received a message from untrusted contact " . \
+          [ $IfThenElse ([ :len ($From->"username") ] = 0) "without username" ("'" . $From->"username" . "'") ] . \
+          " (ID " . $From->"id" . ") in update " . $UpdateID . "!");
+        :if ($Command ~ ("^! *" . [ $EscapeForRegEx $Identity ] . "\$")) do={
+          $LogPrint warning $ScriptName $MessageText;
+          $SendTelegram2 ({ origin=$ScriptName; chatid=($Chat->"id"); silent=false; \
+            replyto=($Message->"message_id"); threadid=$ThreadId; \
+            subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \
+            message=("You are not trusted.") });
+        } else={$LogPrint info $ScriptName $MessageText}
+      }
+    } else={$LogPrint debug $ScriptName ("Already handled update " . $UpdateID . ".")}
+  }
+  :set TelegramChatOffset ([ :pick $TelegramChatOffset 1 3 ], \
+    [ $IfThenElse ($UpdateID >= $TelegramChatOffset->2) ($UpdateID + 1) ($TelegramChatOffset->2) ]);
+} do={:global ExitError; $ExitError $ExitOK [ :jobname ] $Err}
diff --git a/html/mod/AM-TG-Notifications.rsc b/html/mod/AM-TG-Notifications.rsc
new file mode 100644 (file)
index 0000000..dfd30db
--- /dev/null
@@ -0,0 +1,209 @@
+#!rsc by Vados
+# RouterOS script: AM-TG-Notifications
+# Script comment: Script send Notifications in Telegram
+# Copyright (c) 2007-2026 Vados <vados@vados.ru>
+#
+#
+# requires RouterOS, version=7.19
+# requires device-mode, fetch, scheduler
+:local ExitOK false;
+:onerror Err {
+  :global GlobalConfReady; :global GlobalFuncReady;
+  :retry { :if ($GlobalConfReady != true || $GlobalFuncReady != true) \
+      do={ :error ("Global configs or functions not ready."); }; } delay=500ms max=50;
+  :local ScriptName [ :jobname ];
+
+:global FlushTelegramQueue;
+:global GetTelegramChatId;
+:global NotificationFunctions;
+:global PurgeTelegramQueue;
+:global SendTelegram;
+:global SendTelegram2;
+
+# flush telegram queue
+:set FlushTelegramQueue do={ :onerror Err {
+  :global TelegramQueue;
+  :global TelegramMessageIDs;
+  :global CertificateAvailable;
+  :global IsFullyConnected;
+  :global LogPrint;
+  #:if ([ $IsFullyConnected ] = false) do={
+  #  $LogPrint debug $0 ("System is not fully connected, not flushing.");
+  #  :return false;
+  #}
+  :if ([ $CertificateAvailable "Go Daddy Root Certificate Authority - G2" "fetch" ] = false) do={
+    $LogPrint warning $0 ("Downloading required certificate failed.");
+    :return false;
+  }
+
+  :local AllDone true;
+  :local QueueLen [ :len $TelegramQueue ];
+  :if ([ :len [ /system/scheduler/find where name="_FlushTelegramQueue" ] ] > 0 && $QueueLen = 0) do={
+    $LogPrint warning $0 ("Flushing Telegram messages from scheduler, but queue is empty.");
+  }
+  :foreach Id,Message in=$TelegramQueue do={
+    :if ([ :typeof $Message ] = "array" ) do={
+      :onerror Err {
+        :local Data ([ /tool/fetch check-certificate=yes-without-crl output=user http-method=post \
+          ("https://api.telegram.org/bot" . ($Message->"tokenid") . "/sendMessage") \
+          http-data=($Message->"http-data") as-value ]->"data");
+        :set ($TelegramQueue->$Id);
+        :set ($TelegramMessageIDs->[ :tostr ([ :deserialize from=json value=$Data ]->"result"->"message_id") ]) 1;
+      } do={
+        $LogPrint debug $0 ("Sending queued Telegram message failed: " . $Err);
+        :set AllDone false;
+      }
+    }
+  }
+  :if ($AllDone = true && $QueueLen = [ :len $TelegramQueue ]) do={
+    /system/scheduler/remove [ find where name="_FlushTelegramQueue" ];
+    :set TelegramQueue;
+  }
+} do={:global ExitOnError; $ExitOnError $0 $Err}
+}
+# get the chat id
+:set GetTelegramChatId do={ :onerror Err {
+  :global TelegramTokenId;
+#  :global CertificateAvailable;
+  :global LogPrint;
+#  :if ([ $CertificateAvailable "Go Daddy Root Certificate Authority - G2" "fetch" ] = false) do={
+#    $LogPrint warning $0 ("Downloading required certificate failed.");
+#    :return false;
+#  }
+  :local Data;
+  :onerror Err {
+    :set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \
+       ("https://api.telegram.org/bot" . $TelegramTokenId . "/getUpdates?offset=0" . \
+       "&allowed_updates=%5B%22message%22%5D") as-value ]->"data");
+  } do={
+    $LogPrint warning $0 ("Fetching data failed: " . $Err);
+    :return false;
+  }
+  :local JSON [ :deserialize from=json value=$Data ];
+  :local Count [ :len ($JSON->"result") ];
+  :if ($Count = 0) do={
+    $LogPrint info $0 ("No message received.");
+    :return false;
+  }
+  :local Message ($JSON->"result"->($Count - 1)->"message");
+  $LogPrint info $0 ("The chat id is: " . ($Message->"chat"->"id"));
+  :if (($Message->"is_topic_message") = true) do={
+    $LogPrint info $0 ("The thread id is: " . ($Message->"message_thread_id"));
+  }
+} do={:global ExitOnError; $ExitOnError $0 $Err}
+} 
+# send notification via telegram - expects one array argument
+:set ($NotificationFunctions->"telegram") do={
+  :local Notification $1;
+  :global Identity;
+  :global IdentityExtra;
+  :global TelegramChatId;
+  :global TelegramChatIdOverride;
+  :global TelegramMessageIDs;
+  :global TelegramQueue;
+  :global TelegramThreadId;
+  :global TelegramThreadIdOverride;
+  :global TelegramTokenId;
+  :global TelegramTokenIdOverride;
+  :global CertificateAvailable;
+  :global CharacterReplace;
+  :global EitherOr;
+  :global IfThenElse;
+  :global LogPrint;
+  :global ProtocolStrip;
+  :global SymbolForNotification;
+  :global UrlEncode;
+  :local EscapeMD do={
+    :local Text [ :tostr $1 ];
+    :local Mode [ :tostr $2 ];
+    :local Excl [ :tostr $3 ];
+    :global CharacterReplace;
+    :global IfThenElse;
+    :local Chars {
+       "body"={ "\\"; "`" };
+      "plain"={ "_"; "*"; "["; "]"; "("; ")"; "~"; "`"; ">";
+                "#"; "+"; "-"; "="; "|"; "{"; "}"; "."; "!" };
+    }
+    :foreach Char in=($Chars->$Mode) do={
+      :if ([ :typeof [ :find $Excl $Char ] ] = "nil") do={
+        :set Text [ $CharacterReplace $Text $Char ("\\" . $Char) ];
+      }
+    }
+    :if ($Mode = "body") do={:return ("```\n" . $Text . "\n```")}
+    :return $Text;
+  }
+  :local ChatId [ $EitherOr ($Notification->"chatid") \
+    [ $EitherOr ($TelegramChatIdOverride->($Notification->"origin")) $TelegramChatId ] ];
+  :local ThreadId [ $EitherOr ($Notification->"threadid") \
+    [ $EitherOr ($TelegramThreadIdOverride->($Notification->"origin")) \
+    [ $IfThenElse ([ :len ($TelegramChatIdOverride->($Notification->"origin")) ] = 0) $TelegramThreadId ] ] ];
+  :local TokenId [ $EitherOr ($TelegramTokenIdOverride->($Notification->"origin")) $TelegramTokenId ];
+  :if ([ :len $TokenId ] = 0 || [ :len $ChatId ] = 0) do={:return false}
+  :if ([ :typeof $TelegramMessageIDs ] = "nothing") do={:set TelegramMessageIDs ({})}
+  :local Truncated false;
+  :local Text ("*__" . [ $EscapeMD ("[" . $IdentityExtra . $Identity . "] " . \
+    ($Notification->"subject")) "plain" ] . "__*\n\n");
+  :local LenSubject [ :len $Text ];
+  :local LenMessage [ :len ($Notification->"message") ];
+  :local LenLink ([ :len ($Notification->"link") ] * 2);
+  :local LenSum ($LenSubject + $LenMessage + $LenLink);
+  :if ($LenSum > 3968) do={
+    :set Text ($Text . [ $EscapeMD ([ :pick ($Notification->"message") 0 (3840 - $LenSubject - $LenLink) ] . "...") "body" ]);
+    :set Truncated true;
+  } else={:set Text ($Text . [ $EscapeMD ($Notification->"message") "body" ])}
+  :if ($LenLink > 0) do={
+    :set Text ($Text . "\n" . [ $SymbolForNotification "link" ] . \
+      "[" . [ $EscapeMD [ $ProtocolStrip ($Notification->"link") ] "plain" ] . "]" . \
+      "(" . [ $EscapeMD ($Notification->"link") "plain" ] . ")");
+  }
+  :if ($Truncated = true) do={
+    :set Text ($Text . "\n" . [ $SymbolForNotification "scissors" ] . \
+      [ $EscapeMD ("The message was too long and has been truncated, cut off _" . \
+      (($LenSum - [ :len $Text ]) * 100 / $LenSum) . "%_!") "plain" "_" ]);
+  }
+  :local HTTPData ("chat_id=" . $ChatId . "&disable_notification=" . ($Notification->"silent") . \
+      "&reply_to_message_id=" . ($Notification->"replyto") . "&message_thread_id=" . $ThreadId . \
+      "&disable_web_page_preview=true&parse_mode=MarkdownV2");
+  :onerror Err {
+#    :if ([ $CertificateAvailable "Go Daddy Root Certificate Authority - G2" "fetch" ] = false) do={
+#      $LogPrint warning $0 ("Downloading required certificate failed.");
+#      :error false;
+#    }
+#check-certificate=yes-without-crl     
+    :local Data ([ /tool/fetch output=user http-method=post \
+      ("https://api.telegram.org/bot" . $TokenId . "/sendMessage") \
+      http-data=($HTTPData . "&text=" . [ $UrlEncode $Text ]) as-value ]->"data");
+    :set ($TelegramMessageIDs->[ :tostr ([ :deserialize from=json value=$Data ]->"result"->"message_id") ]) 1;
+  } do={
+    $LogPrint info $0 ("Failed sending Telegram notification: " . $Err . " - Queuing...");
+    :if ([ :typeof $TelegramQueue ] = "nothing") do={:set TelegramQueue ({})}
+    :set Text ($Text . "\n" . [ $SymbolForNotification "alarm-clock" ] . \
+      [ $EscapeMD ("This message was queued since _" . [ /system/clock/get date ] . \
+      " " . [ /system/clock/get time ] . "_ and may be obsolete.") "plain" "_" ]);
+    :set ($TelegramQueue->[ :len $TelegramQueue ]) { tokenid=$TokenId;
+      http-data=($HTTPData . "&text=" . [ $UrlEncode $Text ]) };
+    :if ([ :len [ /system/scheduler/find where name="_FlushTelegramQueue" ] ] = 0) do={
+      /system/scheduler/add name="_FlushTelegramQueue" interval=1m start-time=startup \
+        on-event=(":global FlushTelegramQueue; \$FlushTelegramQueue;");
+    }
+  }
+}
+# purge the Telegram queue
+:set PurgeTelegramQueue do={
+  :global TelegramQueue;
+  /system/scheduler/remove [ find where name="_FlushTelegramQueue" ];
+  :set TelegramQueue;
+}
+# send notification via telegram - expects at least two string arguments
+:set SendTelegram do={ :onerror Err {
+  :global SendTelegram2;
+  $SendTelegram2 ({ origin=$0; subject=$1; message=$2; link=$3; silent=$4 });
+} do={:global ExitOnError; $ExitOnError $0 $Err}
+}
+# send notification via telegram - expects one array argument
+:set SendTelegram2 do={
+  :local Notification $1;
+  :global NotificationFunctions;
+  ($NotificationFunctions->"telegram") ("\$NotificationFunctions->\"telegram\"") $Notification;
+}
diff --git a/html/staging_main.diff b/html/staging_main.diff
new file mode 100644 (file)
index 0000000..30070c3
--- /dev/null
@@ -0,0 +1,1504 @@
+Only in main: .gitignore
+Common subdirectories: staging/CERTIFICATES.d and main/CERTIFICATES.d
+diff staging/INITIAL-COMMANDS.md main/INITIAL-COMMANDS.md
+7c7
+< [![required RouterOS version](https://img.shields.io/badge/RouterOS-7.21-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+---
+> [![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+26c26,27
+<       :if (!((($CertSettings->"builtin-trust-store") ~ "fetch" || \
+---
+>       :if (!((($CertSettings->"builtin-trust-anchors") = "trusted" || \
+>               ($CertSettings->"builtin-trust-store") ~ "fetch" || \
+Common subdirectories: staging/README.d and main/README.d
+diff staging/README.md main/README.md
+7c7
+< [![required RouterOS version](https://img.shields.io/badge/RouterOS-7.21-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+---
+> [![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)](https://mikrotik.com/download/changelogs/)
+53c53
+< > ![required RouterOS version](https://img.shields.io/badge/RouterOS-7.21-yellow?style=flat)
+---
+> > ![required RouterOS version](https://img.shields.io/badge/RouterOS-7.19-yellow?style=flat)
+114a115,118
+> 
+> > 💡️ **Hint**: With RouterOS 7.20.x and before the functionality was
+> > different. Set the trust for the builtin trust anchors:  
+> > `/certificate/settings/set builtin-trust-anchors=trusted;`  
+diff staging/backup-cloud.rsc main/backup-cloud.rsc
+11a12
+> :local ExitOK false;
+38c39,40
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+44c46,47
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+55c58,59
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+99c103
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/backup-email.rsc main/backup-email.rsc
+11a12
+> :local ExitOK false;
+43c44,45
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+49c51,52
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+54c57,58
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+60c64,65
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+80c85,86
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+136c142
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/backup-partition.rsc main/backup-partition.rsc
+12a13
+> :local ExitOK false;
+47c48,49
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+53c55,56
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+59c62,63
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+67c71,72
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+76c81,82
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+85c91,92
+<           :exit;
+---
+>           :set ExitOK true;
+>           :error false;
+97c104,105
+<           :exit;
+---
+>           :set ExitOK true;
+>           :error false;
+115c123,124
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+118c127
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/backup-upload.rsc main/backup-upload.rsc
+12a13
+> :local ExitOK false;
+49c50,51
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+54c56,57
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+60c63,64
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+80c84,85
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+172c177
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/capsman-download-packages.capsman.rsc main/capsman-download-packages.capsman.rsc
+13a14
+> :local ExitOK false;
+30c31,32
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+40c42,43
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+47c50,51
+<       :exit;
+---
+>       :set ExitOK true;
+>       :error false;
+88c92
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/capsman-download-packages.template.rsc main/capsman-download-packages.template.rsc
+14a15
+> :local ExitOK false;
+31c32,33
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+42c44,45
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+49c52,53
+<       :exit;
+---
+>       :set ExitOK true;
+>       :error false;
+99c103
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/capsman-download-packages.wifi.rsc main/capsman-download-packages.wifi.rsc
+13a14
+> :local ExitOK false;
+30c31,32
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+40c42,43
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+47c50,51
+<       :exit;
+---
+>       :set ExitOK true;
+>       :error false;
+90c94
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/capsman-rolling-upgrade.capsman.rsc main/capsman-rolling-upgrade.capsman.rsc
+14a15
+> :local ExitOK false;
+25c26,27
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+47c49
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/capsman-rolling-upgrade.template.rsc main/capsman-rolling-upgrade.template.rsc
+15a16
+> :local ExitOK false;
+26c27,28
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+55c57
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/capsman-rolling-upgrade.wifi.rsc main/capsman-rolling-upgrade.wifi.rsc
+14a15
+> :local ExitOK false;
+25c26,27
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+48c50
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/certificate-renew-issued.rsc main/certificate-renew-issued.rsc
+10a11
+> :local ExitOK false;
+24c25,26
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+49c51
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+Common subdirectories: staging/certs and main/certs
+diff staging/check-certificates.rsc main/check-certificates.rsc
+11a12
+> :local ExitOK false;
+154d154
+<       [ $FormatLine "    days" ($CertVal->"days-valid") ] . "\n" . \
+157c157
+<       [ $FormatLine "    time left" [ $IfThenElse (($CertVal->"expired") = true) "expired" [ $FormatExpire ($CertVal->"expires-after") ] ] ]);
+---
+>       [ $FormatLine "Expires in" [ $IfThenElse (($CertVal->"expired") = true) "expired" [ $FormatExpire ($CertVal->"expires-after") ] ] ]);
+161c161,162
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+173c174
+<         :continue;
+---
+>         :error false;
+260c261
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+Common subdirectories: staging/check-health.d and main/check-health.d
+diff staging/check-health.rsc main/check-health.rsc
+10a11
+> :local ExitOK false;
+39c40,41
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+78c80,81
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+106c109
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/check-lte-firmware-upgrade.rsc main/check-lte-firmware-upgrade.rsc
+10a11
+> :local ExitOK false;
+22c23,24
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+104c106
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/check-perpetual-license.rsc main/check-perpetual-license.rsc
+10a11
+> :local ExitOK false;
+27c28,29
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+35c37,38
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+47c50,51
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+59c63,64
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+72c77
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/check-routeros-update.rsc main/check-routeros-update.rsc
+11a12
+> :local ExitOK false;
+49c50,51
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+54c56,57
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+65c68,69
+<       :exit;
+---
+>       :set ExitOK true;
+>       :error false;
+77c81,82
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+82c87,88
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+94c100,101
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+106c113,114
+<       :exit;
+---
+>       :set ExitOK true;
+>       :error true;
+116c124,125
+<       :exit;
+---
+>       :set ExitOK true;
+>       :error true;
+131c140,141
+<         :exit;
+---
+>         :set ExitOK true;
+>         :error true;
+152c162,163
+<         :exit;
+---
+>         :set ExitOK true;
+>         :error true;
+162c173,174
+<           :exit;
+---
+>           :set ExitOK true;
+>           :error true;
+169c181,182
+<         :exit;
+---
+>         :set ExitOK true;
+>         :error true;
+178c191,192
+<       :exit;
+---
+>       :set ExitOK true;
+>       :error true;
+193c207,208
+<       :exit;
+---
+>       :set ExitOK true;
+>       :error true;
+206c221
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/collect-wireless-mac.capsman.rsc main/collect-wireless-mac.capsman.rsc
+13a14
+> :local ExitOK false;
+32c33,34
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+97c99
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/collect-wireless-mac.local.rsc main/collect-wireless-mac.local.rsc
+13a14
+> :local ExitOK false;
+32c33,34
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+98c100
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/collect-wireless-mac.template.rsc main/collect-wireless-mac.template.rsc
+14a15
+> :local ExitOK false;
+33c34,35
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+115c117
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/collect-wireless-mac.wifi.rsc main/collect-wireless-mac.wifi.rsc
+13a14
+> :local ExitOK false;
+32c33,34
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+97c99
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+Common subdirectories: staging/contrib and main/contrib
+diff staging/daily-psk.capsman.rsc main/daily-psk.capsman.rsc
+13a14
+> :local ExitOK false;
+34c35,36
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+93c95
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/daily-psk.local.rsc main/daily-psk.local.rsc
+13a14
+> :local ExitOK false;
+34c35,36
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+92c94
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/daily-psk.template.rsc main/daily-psk.template.rsc
+14a15
+> :local ExitOK false;
+35c36,37
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+108c110
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/daily-psk.wifi.rsc main/daily-psk.wifi.rsc
+13a14
+> :local ExitOK false;
+34c35,36
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+93c95
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/dhcp-lease-comment.capsman.rsc main/dhcp-lease-comment.capsman.rsc
+13a14
+> :local ExitOK false;
+24c25,26
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+40c42
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/dhcp-lease-comment.local.rsc main/dhcp-lease-comment.local.rsc
+13a14
+> :local ExitOK false;
+24c25,26
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+40c42
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/dhcp-lease-comment.template.rsc main/dhcp-lease-comment.template.rsc
+14a15
+> :local ExitOK false;
+25c26,27
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+45c47
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/dhcp-lease-comment.wifi.rsc main/dhcp-lease-comment.wifi.rsc
+13a14
+> :local ExitOK false;
+24c25,26
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+40c42
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/dhcp-to-dns.rsc main/dhcp-to-dns.rsc
+11a12
+> :local ExitOK false;
+30c31,32
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+127c129
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+Common subdirectories: staging/doc and main/doc
+diff staging/firmware-upgrade-reboot.rsc main/firmware-upgrade-reboot.rsc
+10a11
+> :local ExitOK false;
+22c23,24
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+29c31,32
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+33c36,37
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+55c59
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+Common subdirectories: staging/fw-addr-lists.d and main/fw-addr-lists.d
+diff staging/fw-addr-lists.rsc main/fw-addr-lists.rsc
+10a11
+> :local ExitOK false;
+48c49,50
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+114,123c116,129
+< 
+<         :local Branch;
+<         :if ($Address ~ "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}(/[0-9]{1,2})?\$") do={
+<           :local Net $Address;
+<           :local CIDR 32;
+<           :local Slash [ :find $Address "/" ];
+<           :if ([ :typeof $Slash ] = "num") do={
+<             :set Net [ :toip [ :pick $Address 0 $Slash ] ]
+<             :set CIDR [ :pick $Address ($Slash + 1) [ :len $Address ] ];
+<             :set Address [ :tostr (([ :toip $Net ] & [ $NetMask4 $CIDR ]) . [ $IfThenElse ($CIDR < 32) ("/" . $CIDR) ]) ];
+---
+>         :do {
+>           :local Branch;
+>           :if ($Address ~ "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}(/[0-9]{1,2})?\$") do={
+>             :local Net $Address;
+>             :local CIDR 32;
+>             :local Slash [ :find $Address "/" ];
+>             :if ([ :typeof $Slash ] = "num") do={
+>               :set Net [ :toip [ :pick $Address 0 $Slash ] ]
+>               :set CIDR [ :pick $Address ($Slash + 1) [ :len $Address ] ];
+>               :set Address [ :tostr (([ :toip $Net ] & [ $NetMask4 $CIDR ]) . [ $IfThenElse ($CIDR < 32) ("/" . $CIDR) ]) ];
+>             }
+>             :set Branch [ $GetBranch $Address ];
+>             :set ($IPv4Addresses->$Branch->$Address) $TimeOut;
+>             :error true;
+125,135c131,142
+<           :set Branch [ $GetBranch $Address ];
+<           :set ($IPv4Addresses->$Branch->$Address) $TimeOut;
+<           :continue;
+<         }
+<         :if ($Address ~ "^[0-9a-zA-Z]*:[0-9a-zA-Z:\\.]+(/[0-9]{1,3})?\$") do={
+<           :local Net $Address;
+<           :local CIDR 128;
+<           :local Slash [ :find $Address "/" ];
+<           :if ([ :typeof $Slash ] = "num") do={
+<             :set Net [ :toip6 [ :pick $Address 0 $Slash ] ]
+<             :set CIDR [ :pick $Address ($Slash + 1) [ :len $Address ] ];
+---
+>           :if ($Address ~ "^[0-9a-zA-Z]*:[0-9a-zA-Z:\\.]+(/[0-9]{1,3})?\$") do={
+>             :local Net $Address;
+>             :local CIDR 128;
+>             :local Slash [ :find $Address "/" ];
+>             :if ([ :typeof $Slash ] = "num") do={
+>               :set Net [ :toip6 [ :pick $Address 0 $Slash ] ]
+>               :set CIDR [ :pick $Address ($Slash + 1) [ :len $Address ] ];
+>             }
+>             :set Address (([ :toip6 $Net ] & [ $NetMask6 $CIDR ]) . "/" . $CIDR);
+>             :set Branch [ $GetBranch $Address ];
+>             :set ($IPv6Addresses->$Branch->$Address) $TimeOut;
+>             :error true;
+137,147c144,150
+<           :set Address (([ :toip6 $Net ] & [ $NetMask6 $CIDR ]) . "/" . $CIDR);
+<           :set Branch [ $GetBranch $Address ];
+<           :set ($IPv6Addresses->$Branch->$Address) $TimeOut;
+<           :continue;
+<         }
+<         :if ($Address ~ "^[\\.a-zA-Z0-9-]+\\.[a-zA-Z]{2,}\$") do={
+<           :set Branch [ $GetBranch $Address ];
+<           :set ($IPv4Addresses->$Branch->$Address) $TimeOut;
+<           :set ($IPv6Addresses->$Branch->$Address) $TimeOut;
+<           :continue;
+<         }
+---
+>           :if ($Address ~ "^[\\.a-zA-Z0-9-]+\\.[a-zA-Z]{2,}\$") do={
+>             :set Branch [ $GetBranch $Address ];
+>             :set ($IPv4Addresses->$Branch->$Address) $TimeOut;
+>             :set ($IPv6Addresses->$Branch->$Address) $TimeOut;
+>             :error true;
+>           }
+>         } on-error={ }
+232c235
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+Common subdirectories: staging/general and main/general
+Common subdirectories: staging/global-functions.d and main/global-functions.d
+diff staging/global-functions.rsc main/global-functions.rsc
+7c7
+< # requires RouterOS, version=7.21
+---
+> # requires RouterOS, version=7.19
+38a39
+> :global ExitError;
+50a52
+> :global HexToNum;
+131c133,134
+<   :if ((($CertSettings->"builtin-trust-store") ~ $UseFor || \
+---
+>   :if ((($CertSettings->"builtin-trust-anchors") = "trusted" || \
+>         ($CertSettings->"builtin-trust-store") ~ $UseFor || \
+484a488,500
+> # wrapper for $ExitOnError with additional parameter
+> :set ExitError do={
+>   :local ExitOK [ :tostr $1 ];
+>   :local Name   [ :tostr $2 ];
+>   :local Error  [ :tostr $3 ];
+> 
+>   :global ExitOnError;
+> 
+>   :if ($ExitOK = "false") do={
+>     $ExitOnError $Name $Error;
+>   }
+> }
+> 
+722a739,751
+> # convert from hex (string) to num
+> :set HexToNum do={
+>   :local Input [ :tostr $1 ];
+> 
+>   :global HexToNum;
+> 
+>   :if ([ :pick $Input 0 ] = "*") do={
+>     :return [ $HexToNum [ :pick  $Input 1 [ :len $Input ] ] ];
+>   }
+> 
+>   :return [ :tonum ("0x" . $Input) ];
+> }
+> 
+1017c1046,1050
+<   :local CIDR [ :tonum $1 ];
+---
+>   :local CIDR [ :tostr $1 ];
+> 
+>   :global IfThenElse;
+>   :global MAX;
+>   :global MIN;
+1019c1052,1070
+<   :return (((~::) << (128 - $CIDR)) & (~::));
+---
+>   :global NetMask6Cache;
+> 
+>   :if ([ :typeof ($NetMask6Cache->$CIDR) ] = "ip6") do={
+>     :return ($NetMask6Cache->$CIDR);
+>   }
+> 
+>   :if ([ :typeof $NetMask6Cache ] = "nothing") do={
+>     :set NetMask6Cache ({});
+>   }
+> 
+>   :local Mask "";
+>   :for I from=0 to=7 do={
+>     :set Mask ($Mask . \
+>       [ :convert from=num to=hex (0xffff - (0xffff >> [ :tonum [ $MIN [ $MAX ($CIDR - (16 * $I)) 0 ] 16 ] ])) ] . \
+>       [ $IfThenElse ($I < 7) ":" ]);
+>   }
+>   :set Mask [ :toip6 $Mask ];
+>   :set ($NetMask6Cache->$CIDR) $Mask;
+>   :return $Mask;
+1271,1281c1322,1326
+<     :if ($ScriptInfo->"ignore" = true) do={
+<       $LogPrint debug $0 ("Ignoring script '" . $ScriptVal->"name" . "', as requested.");
+<       :continue;
+<     }
+< 
+<     :local CheckSum ($CheckSums->($ScriptVal->"name"));
+<     :if ([ :len ($ScriptInfo->"base-url") ] = 0 && [ :len ($ScriptInfo->"url-suffix") ] = 0 && \
+<          [ :convert transform=md5 to=hex [ :tolf ($ScriptVal->"source") ] ] = $CheckSum) do={
+<       $LogPrint debug $0 ("Checksum for script '" . $ScriptVal->"name" . "' matches, ignoring.");
+<       :continue;
+<     }
+---
+>     :do {
+>       :if ($ScriptInfo->"ignore" = true) do={
+>         $LogPrint debug $0 ("Ignoring script '" . $ScriptVal->"name" . "', as requested.");
+>         :error true;
+>       }
+1283,1285c1328,1332
+<     :if ([ :len ($ScriptInfo->"certificate") ] > 0) do={
+<       :if ([ $CertificateAvailable ($ScriptInfo->"certificate") "fetch" ] = false) do={
+<         $LogPrint warning $0 ("Downloading certificate failed, trying without.");
+---
+>       :local CheckSum ($CheckSums->($ScriptVal->"name"));
+>       :if ([ :len ($ScriptInfo->"base-url") ] = 0 && [ :len ($ScriptInfo->"url-suffix") ] = 0 && \
+>            [ :convert transform=md5 to=hex [ :tolf ($ScriptVal->"source") ] ] = $CheckSum) do={
+>         $LogPrint debug $0 ("Checksum for script '" . $ScriptVal->"name" . "' matches, ignoring.");
+>         :error true;
+1287d1333
+<     }
+1289,1297c1335,1338
+<     :onerror Err {
+<       :local BaseUrl [ $EitherOr ($ScriptInfo->"base-url") $ScriptUpdatesBaseUrl ];
+<       :local UrlSuffix [ $EitherOr ($ScriptInfo->"url-suffix") $ScriptUpdatesUrlSuffix ];
+<       :local Url ($BaseUrl . $ScriptVal->"name" . ".rsc" . $UrlSuffix);
+<       $LogPrint debug $0 ("Fetching script '" . $ScriptVal->"name" . "' from url: " . $Url);
+<       :local Result [ /tool/fetch check-certificate=yes-without-crl \
+<         http-header-field=({ [ $FetchUserAgentStr $0 ] }) $Url output=user as-value ];
+<       :if ($Result->"status" = "finished") do={
+<         :set SourceNew [ :tolf ($Result->"data") ];
+---
+>       :if ([ :len ($ScriptInfo->"certificate") ] > 0) do={
+>         :if ([ $CertificateAvailable ($ScriptInfo->"certificate") "fetch" ] = false) do={
+>           $LogPrint warning $0 ("Downloading certificate failed, trying without.");
+>         }
+1299,1302c1340,1367
+<     } do={
+<       $LogPrint warning $0 ("Failed fetching script '" . $ScriptVal->"name" . "': " . $Err);
+<       :if ($Err != "Fetch failed with status 404") do={
+<         :continue;
+---
+> 
+>       :onerror Err {
+>         :local BaseUrl [ $EitherOr ($ScriptInfo->"base-url") $ScriptUpdatesBaseUrl ];
+>         :local UrlSuffix [ $EitherOr ($ScriptInfo->"url-suffix") $ScriptUpdatesUrlSuffix ];
+>         :local Url ($BaseUrl . $ScriptVal->"name" . ".rsc" . $UrlSuffix);
+>         $LogPrint debug $0 ("Fetching script '" . $ScriptVal->"name" . "' from url: " . $Url);
+>         :local Result [ /tool/fetch check-certificate=yes-without-crl \
+>           http-header-field=({ [ $FetchUserAgentStr $0 ] }) $Url output=user as-value ];
+>         :if ($Result->"status" = "finished") do={
+>           :set SourceNew [ :tolf ($Result->"data") ];
+>         }
+>       } do={
+>         $LogPrint warning $0 ("Failed fetching script '" . $ScriptVal->"name" . "': " . $Err);
+>         :if ($Err != "Fetch failed with status 404") do={
+>           :error false;
+>         }
+> 
+>         :if ($ScriptVal->"source" = "#!rsc by RouterOS\n") do={
+>           $LogPrint warning $0 ("Removing dummy. Typo on installation?");
+>           /system/script/remove $Script;
+>           :error false;
+>         }
+>         :if ([ :len ($ScriptInfo->"base-url") ] = 0 && [ :len ($ScriptInfo->"url-suffix") ] = 0 && \
+>              [ :len $CheckSum ] = 0) do={
+>           $LogPrintOnce warning $0 \
+>               ("Added the script manually? Skip updates with 'ignore=true' in comment.");
+>         }
+>         :error false;
+1305,1308c1370,1372
+<       :if ($ScriptVal->"source" = "#!rsc by RouterOS\n") do={
+<         $LogPrint warning $0 ("Removing dummy. Typo on installation?");
+<         /system/script/remove $Script;
+<         :continue;
+---
+>       :if ([ :len $SourceNew ] = 0) do={
+>         $LogPrint debug $0 ("No update for script '" . $ScriptVal->"name" . "'.");
+>         :error false;
+1310,1313c1374,1378
+<       :if ([ :len ($ScriptInfo->"base-url") ] = 0 && [ :len ($ScriptInfo->"url-suffix") ] = 0 && \
+<            [ :len $CheckSum ] = 0) do={
+<         $LogPrintOnce warning $0 \
+<             ("Added the script manually? Skip updates with 'ignore=true' in comment.");
+---
+> 
+>       :local SourceCRLF [ :tocrlf $SourceNew ];
+>       :if ($SourceNew = $ScriptVal->"source" || $SourceCRLF = $ScriptVal->"source") do={
+>         $LogPrint debug $0 ("Script '" .  $ScriptVal->"name" . "' did not change.");
+>         :error false;
+1315,1316d1379
+<       :continue;
+<     }
+1318,1321c1381,1385
+<     :if ([ :len $SourceNew ] = 0) do={
+<       $LogPrint debug $0 ("No update for script '" . $ScriptVal->"name" . "'.");
+<       :continue;
+<     }
+---
+>       :if ([ :pick $SourceNew 0 18 ] != "#!rsc by RouterOS\n") do={
+>         $LogPrint warning $0 ("Looks like new script '" . $ScriptVal->"name" . \
+>             "' is not valid (missing shebang). Ignoring!");
+>         :error false;
+>       }
+1323,1327c1387,1392
+<     :local SourceCRLF [ :tocrlf $SourceNew ];
+<     :if ($SourceNew = $ScriptVal->"source" || $SourceCRLF = $ScriptVal->"source") do={
+<       $LogPrint debug $0 ("Script '" .  $ScriptVal->"name" . "' did not change.");
+<       :continue;
+<     }
+---
+>       :local RequiredROS ([ $ParseKeyValueStore [ $Grep $SourceNew ("\23 requires RouterOS, ") ] ]->"version");
+>       :if ([ $RequiredRouterOS $0 [ $EitherOr $RequiredROS "0.0" ] false ] = false) do={
+>         $LogPrintOnce warning $0 ("The script '" . $ScriptVal->"name" . "' requires RouterOS " . \
+>             $RequiredROS . ", which is not met by your installation. Ignoring!");
+>         :error false;
+>       }
+1329,1333c1394,1405
+<     :if ([ :pick $SourceNew 0 18 ] != "#!rsc by RouterOS\n") do={
+<       $LogPrint warning $0 ("Looks like new script '" . $ScriptVal->"name" . \
+<           "' is not valid (missing shebang). Ignoring!");
+<       :continue;
+<     }
+---
+>       :local RequiredDM [ $ParseKeyValueStore [ $Grep $SourceNew ("\23 requires device-mode, ") ] ];
+>       :local MissingDM ({});
+>       :foreach Feature,Value in=$RequiredDM do={
+>         :if ([ :typeof ($DeviceMode->$Feature) ] = "bool" && ($DeviceMode->$Feature) = false) do={
+>           :set MissingDM ($MissingDM, $Feature);
+>         }
+>       }
+>       :if ([ :len $MissingDM ] > 0) do={
+>         $LogPrintOnce warning $0 ("The script '" . $ScriptVal->"name" . "' requires disabled " . \
+>             "device-mode features (" . [ :tostr $MissingDM ] . "). Ignoring!");
+>         :error false;
+>       }
+1335,1340c1407,1410
+<     :local RequiredROS ([ $ParseKeyValueStore [ $Grep $SourceNew ("\23 requires RouterOS, ") ] ]->"version");
+<     :if ([ $RequiredRouterOS $0 [ $EitherOr $RequiredROS "0.0" ] false ] = false) do={
+<       $LogPrintOnce warning $0 ("The script '" . $ScriptVal->"name" . "' requires RouterOS " . \
+<           $RequiredROS . ", which is not met by your installation. Ignoring!");
+<       :continue;
+<     }
+---
+>       :if ([ $ValidateSyntax $SourceNew ] = false) do={
+>         $LogPrint warning $0 ("Syntax validation for script '" . $ScriptVal->"name" . "' failed! Ignoring!");
+>         :error false;
+>       }
+1342,1346c1412,1418
+<     :local RequiredDM [ $ParseKeyValueStore [ $Grep $SourceNew ("\23 requires device-mode, ") ] ];
+<     :local MissingDM ({});
+<     :foreach Feature,Value in=$RequiredDM do={
+<       :if ([ :typeof ($DeviceMode->$Feature) ] = "bool" && ($DeviceMode->$Feature) = false) do={
+<         :set MissingDM ($MissingDM, $Feature);
+---
+>       $LogPrint info $0 ("Updating script: " . $ScriptVal->"name");
+>       /system/script/set owner=($ScriptVal->"name") \
+>           source=[ $IfThenElse ($ScriptUpdatesCRLF = true) $SourceCRLF $SourceNew ] $Script;
+>       :if ($ScriptVal->"name" = "global-config" || \
+>            $ScriptVal->"name" = "global-functions" || \
+>            $ScriptVal->"name" ~ ("^(global-functions\\.d|mod)/.")) do={
+>         :set ReloadGlobal true;
+1348,1367c1420
+<     }
+<     :if ([ :len $MissingDM ] > 0) do={
+<       $LogPrintOnce warning $0 ("The script '" . $ScriptVal->"name" . "' requires disabled " . \
+<           "device-mode features (" . [ :tostr $MissingDM ] . "). Ignoring!");
+<       :continue;
+<     }
+< 
+<     :if ([ $ValidateSyntax $SourceNew ] = false) do={
+<       $LogPrint warning $0 ("Syntax validation for script '" . $ScriptVal->"name" . "' failed! Ignoring!");
+<       :continue;
+<     }
+< 
+<     $LogPrint info $0 ("Updating script: " . $ScriptVal->"name");
+<     /system/script/set owner=($ScriptVal->"name") \
+<         source=[ $IfThenElse ($ScriptUpdatesCRLF = true) $SourceCRLF $SourceNew ] $Script;
+<     :if ($ScriptVal->"name" = "global-config" || \
+<          $ScriptVal->"name" = "global-functions" || \
+<          $ScriptVal->"name" ~ ("^(global-functions\\.d|mod)/.")) do={
+<       :set ReloadGlobal true;
+<     }
+---
+>     } on-error={ }
+diff staging/gps-track.rsc main/gps-track.rsc
+11a12
+> :local ExitOK false;
+27c28,29
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+50c52
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/hotspot-to-wpa-cleanup.capsman.rsc main/hotspot-to-wpa-cleanup.capsman.rsc
+14a15
+> :local ExitOK false;
+27c28,29
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+77c79
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/hotspot-to-wpa-cleanup.template.rsc main/hotspot-to-wpa-cleanup.template.rsc
+15a16
+> :local ExitOK false;
+28c29,30
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+84c86
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/hotspot-to-wpa-cleanup.wifi.rsc main/hotspot-to-wpa-cleanup.wifi.rsc
+14a15
+> :local ExitOK false;
+27c28,29
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+77c79
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/hotspot-to-wpa.capsman.rsc main/hotspot-to-wpa.capsman.rsc
+13a14
+> :local ExitOK false;
+29c30,31
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+34c36,37
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+61c64,65
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+100c104
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/hotspot-to-wpa.template.rsc main/hotspot-to-wpa.template.rsc
+14a15
+> :local ExitOK false;
+30c31,32
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+35c37,38
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+68c71,72
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+120c124
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/hotspot-to-wpa.wifi.rsc main/hotspot-to-wpa.wifi.rsc
+13a14
+> :local ExitOK false;
+29c30,31
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+34c36,37
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+61c64,65
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+97c101
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/ipsec-to-dns.rsc main/ipsec-to-dns.rsc
+11a12
+> :local ExitOK false;
+30c31,32
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+81c83
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/ipv6-update.rsc main/ipv6-update.rsc
+10a11
+> :local ExitOK false;
+27c28,29
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+32c34,35
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+37c40,41
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+42c46,47
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+101c106
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/lease-script.rsc main/lease-script.rsc
+10a11
+> :local ExitOK false;
+28c29,30
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+35c37,38
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+40c43,44
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+60c64
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/log-forward.rsc main/log-forward.rsc
+6c6
+< # requires RouterOS, version=7.22beta1
+---
+> # requires RouterOS, version=7.19
+10a11
+> :local ExitOK false;
+25a27
+>   :global HexToNum;
+35,39c37,38
+<     :exit;
+<   }
+< 
+<   :if ([ :typeof $LogForwardLast ] = "nothing") do={
+<     :set LogForwardLast false;
+---
+>     :set ExitOK true;
+>     :error false;
+49c48,49
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+53a54
+>   :local Last [ $IfThenElse ([ :len $LogForwardLast ] > 0) [ $HexToNum $LogForwardLast ] -1 ];
+65c66,67
+<   :local Max ($LogAll->([ :len $LogAll ] - 1));
+---
+>   :local MaxId ($LogAll->([ :len $LogAll ] - 1));
+>   :local MaxNum [ $HexToNum $MaxId ];
+68,71c70,73
+<   :foreach Message in=[ /log/find where .id>$LogForwardLast and .id<=$Max and \
+<       ((!(message="") and !(message~$LogForwardFilterLogForwardingCached) and \
+<         !(topics~$LogForwardFilter) and !(message~$LogForwardFilterMessage)) or \
+<        topics~$LogForwardInclude or message~$LogForwardIncludeMessage) ] do={
+---
+>   :foreach Message in=[ /log/find where (!(message="") and \
+>       !(message~$LogForwardFilterLogForwardingCached) and \
+>       !(topics~$LogForwardFilter) and !(message~$LogForwardFilterMessage)) or \
+>       topics~$LogForwardInclude or message~$LogForwardIncludeMessage ] do={
+75,88c77,95
+<     :local DupCount ($MessageDups->($MessageVal->"message"));
+<     :if ($MessageVal->"topics" ~ "(warning)") do={
+<       :set Warning true;
+<       :set Bullet "large-orange-circle";
+<     }
+<     :if ($MessageVal->"topics" ~ "(emergency|alert|critical|error)") do={
+<       :set Warning true;
+<       :set Bullet "large-red-circle";
+<     }
+<     :if ($DupCount < 3) do={
+<       :set Messages ($Messages . "\n" . [ $SymbolForNotification $Bullet ] . \
+<         $MessageVal->"time" . " " . [ :tostr ($MessageVal->"topics") ] . " " . $MessageVal->"message");
+<     } else={
+<       :set Duplicates true;
+---
+>     :local Current [ $HexToNum ($MessageVal->".id") ];
+>     :if ($Last < $Current && $Current <= $MaxNum) do={
+>       :local DupCount ($MessageDups->($MessageVal->"message"));
+>       :if ($MessageVal->"topics" ~ "(warning)") do={
+>         :set Warning true;
+>         :set Bullet "large-orange-circle";
+>       }
+>       :if ($MessageVal->"topics" ~ "(emergency|alert|critical|error)") do={
+>         :set Warning true;
+>         :set Bullet "large-red-circle";
+>       }
+>       :if ($DupCount < 3) do={
+>         :set Messages ($Messages . "\n" . [ $SymbolForNotification $Bullet ] . \
+>           $MessageVal->"time" . " " . [ :tostr ($MessageVal->"topics") ] . " " . $MessageVal->"message");
+>       } else={
+>         :set Duplicates true;
+>       }
+>       :set ($MessageDups->($MessageVal->"message")) ($DupCount + 1);
+>       :set Count ($Count + 1);
+90,91d96
+<     :set ($MessageDups->($MessageVal->"message")) ($DupCount + 1);
+<     :set Count ($Count + 1);
+109c114
+<   :set LogForwardLast $Max;
+---
+>   :set LogForwardLast $MaxId;
+111c116
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+Common subdirectories: staging/mod and main/mod
+diff staging/netwatch-dns.rsc main/netwatch-dns.rsc
+11a12
+> :local ExitOK false;
+28c29,30
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+34c36,37
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+89c92,93
+<         :exit;
+---
+>         :set ExitOK true;
+>         :error true;
+124d127
+<       :continue;
+127,128c130,141
+<     :if ([ :typeof [ :find $Data "doh-check-OK" ] ] != "num") do={
+<       $LogPrint warning $ScriptName ("Received unexpected response from DoH server: " . \
+---
+>     :if ($Data != false) do={
+>       :if ([ :typeof [ :find $Data "doh-check-OK" ] ] = "num") do={
+>         /ip/dns/set use-doh-server=($DohServer->"doh-url") verify-doh-cert=yes;
+>         :if ([ /certificate/settings/get crl-use ] = true) do={
+>           $LogPrintOnce warning $ScriptName ("Configured to use CRL, that can cause severe issue!");
+>         }
+>         /ip/dns/cache/flush;
+>         $LogPrint info $ScriptName ("Setting DoH server: " . ($DohServer->"doh-url"));
+>         :set ExitOK true;
+>         :error true;
+>       } else={
+>         $LogPrint warning $ScriptName ("Received unexpected response from DoH server: " . \
+130,135c143
+<       :continue;
+<     }
+< 
+<     /ip/dns/set use-doh-server=($DohServer->"doh-url") verify-doh-cert=yes;
+<     :if ([ /certificate/settings/get crl-use ] = true) do={
+<       $LogPrintOnce warning $ScriptName ("Configured to use CRL, that can cause severe issue!");
+---
+>       }
+137,139d144
+<     /ip/dns/cache/flush;
+<     $LogPrint info $ScriptName ("Setting DoH server: " . ($DohServer->"doh-url"));
+<     :exit;
+142c147
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/netwatch-notify.rsc main/netwatch-notify.rsc
+10a11
+> :local ExitOK false;
+82c83,84
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+226c228
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/ospf-to-leds.rsc main/ospf-to-leds.rsc
+10a11
+> :local ExitOK false;
+22c23,24
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+46c48
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/packages-update.rsc main/packages-update.rsc
+11a12
+> :local ExitOK false;
+61c62,63
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+66c68,69
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+73c76,77
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+78c82,83
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+109c114,115
+<           :exit;
+---
+>           :set ExitOK true;
+>           :error false;
+113c119,120
+<         :exit;
+---
+>         :set ExitOK true;
+>         :error false;
+132c139,140
+<       :exit;
+---
+>       :set ExitOK true;
+>       :error false;
+140c148,149
+<       :exit;
+---
+>       :set ExitOK true;
+>       :error false;
+154c163,164
+<       :exit;
+---
+>       :set ExitOK true;
+>       :error true;
+159c169,170
+<       :exit;
+---
+>       :set ExitOK true;
+>       :error true;
+167c178
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/ppp-on-up.rsc main/ppp-on-up.rsc
+10a11
+> :local ExitOK false;
+23c24,25
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+41c43
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/sms-action.rsc main/sms-action.rsc
+10a11
+> :local ExitOK false;
+26c27,28
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+38c40
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/sms-forward.rsc main/sms-forward.rsc
+11a12
+> :local ExitOK false;
+31c32,33
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+36c38,39
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+45c48,49
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error true;
+106c110
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/telegram-chat.rsc main/telegram-chat.rsc
+11a12
+> :local ExitOK false;
+50c51,52
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+64c66,67
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+71,81c74,85
+<     :onerror Err {
+<       :set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \
+<         ("https://api.telegram.org/bot" . $TelegramTokenId . "/getUpdates?offset=" . \
+<         $TelegramChatOffset->0 . "&allowed_updates=%5B%22message%22%5D") as-value ]->"data");
+<       :set TelegramRandomDelay [ $MAX 0 ($TelegramRandomDelay - 1) ];
+<       :break;
+<     } do={
+<       :if ($I < 4) do={
+<         $LogPrint debug $ScriptName ("Fetch failed, " . $I . ". try: " . $Err);
+<         :set TelegramRandomDelay [ $MIN 15 ($TelegramRandomDelay + 5) ];
+<         :delay (($I * $I) . "s");
+---
+>     :if ($Data = false) do={
+>       :onerror Err {
+>         :set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \
+>           ("https://api.telegram.org/bot" . $TelegramTokenId . "/getUpdates?offset=" . \
+>           $TelegramChatOffset->0 . "&allowed_updates=%5B%22message%22%5D") as-value ]->"data");
+>         :set TelegramRandomDelay [ $MAX 0 ($TelegramRandomDelay - 1) ];
+>       } do={
+>         :if ($I < 4) do={
+>           $LogPrint debug $ScriptName ("Fetch failed, " . $I . ". try: " . $Err);
+>           :set TelegramRandomDelay [ $MIN 15 ($TelegramRandomDelay + 5) ];
+>           :delay (($I * $I) . "s");
+>         }
+88c92,93
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+116a122
+>         :local Done false;
+124c130
+<           :continue;
+---
+>           :set Done true;
+126c132
+<         :if ([ :pick $Command 0 1 ] = "!") do={
+---
+>         :if ($Done = false && [ :pick $Command 0 1 ] = "!") do={
+134c140
+<           :continue;
+---
+>           :set Done true;
+136c142
+<         :if (($IsMyReply = 1 || ($IsAnyReply = false && \
+---
+>         :if ($Done = false && ($IsMyReply = 1 || ($IsAnyReply = false && \
+143c149,150
+<               :exit;
+---
+>               :set ExitOK true;
+>               :error false;
+193c200
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/update-gre-address.rsc main/update-gre-address.rsc
+11a12
+> :local ExitOK false;
+23c24,25
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+43c45
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
+diff staging/update-tunnelbroker.rsc main/update-tunnelbroker.rsc
+13a14
+> :local ExitOK false;
+26c27,28
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+31c33,34
+<     :exit;
+---
+>     :set ExitOK true;
+>     :error false;
+54c57,58
+<       :exit;
+---
+>       :set ExitOK true;
+>       :error false;
+69c73
+<   :global ExitOnError; $ExitOnError [ :jobname ] $Err;
+---
+>   :global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
diff --git a/html/test.rsc b/html/test.rsc
new file mode 100644 (file)
index 0000000..f91b6b9
--- /dev/null
@@ -0,0 +1,224 @@
+
+#:local runningVer [:find [/system/resource/get version] "("];
+
+:local runningChannel;
+:local runningVersion;
+:local ParseCustom;
+
+:set ParseCustom do={
+  :local Str [ :tostr $1 ];
+  :local Start [:find [ $Str ] [$2]];
+  :local End [ :tostr $3 ];
+  :local Num [ :tonum $4 ];
+  :local Return;
+
+  :if ($Num = false) do={
+:set Num [0];
+}
+:log warning [$Num];
+:log warning ("'" . [$End . "'"];
+  :local rest [:pick $Str ($Start+$Num) [:len $Str]]; 
+  :if ($End = false) do={
+  :set Return [:pick $Str $Num $Start];
+  } else={
+  :set End [:find $rest $End];
+  :set Return [:pick $rest 0 $End];
+  }
+:return $Return;
+}
+  
+:set runningChannel [$ParseCustom [/system/resource/get version] "(" ")" 1];
+:log warning [$runningChannel];
+:set runningVersion [$ParseCustom [/system/resource/get version] [:tostr " "]];
+:log warning [$runningVersion];
+
+
+
+#  :if ([:len $open] = 0) do={
+#    :log error "$SMP Could not extract installed OS channel from version string: `$runningOsAndChannel`.";
+#    :error "$SMP error, check logs";
+#  }
+#  :local rest [:pick $runningOsAndChannel ($open+1) [:len $runningOsAndChannel]];
+#  :local close [:find $rest ")"];
+#  :local channel [:pick $rest 0 $close];
+#  :return $channel;
+#}
+#
+#:log warning ("Waiting for one minute before continuing to the final step.");
+#:delay 1m;
+
+:local ipAddrDefault "https://ipv4.mikrotik.ovh/";
+:local ipAddrFallback "https://api.ipify.org/";
+:local publicIpAddress "not-detected";
+:global getPubIp;
+:set publicIpAddress [ $getPibIp $ipAddrDefault ];
+
+:set getPubIp do={
+    :local ipAddr $1;
+    :local PubIpAddress "not-detected";
+    :do {
+#mode=https
+      :set PubIpAddress ([/tool fetch https-method="get" mode="https" url=$ipAddrFallback output=user as-value]->"data");
+:delay 5;
+     } on-error={
+     :log error ($error);
+     :return false;
+     }
+      :log warning ("/nAddress = " . $Address);
+      :set PubIpAddress ([:pick $Address 0 15]);
+      :return $PubIpAddress;
+:log warning ("ipAddr is " . $ipAddr);
+}
+:log warning ("ipAddrDefault is " . $ipAddrDefault);
+:log warning ("ipAddrFallback is " . $ipAddrFallback);
+:set publicIpAddress [ $getPubIp $ipAddrDefault ];
+:log warning ("Public Ip Address is " . $publicIpAddress);
+
+:local result true;
+:do {
+tool fetch url="https://1.1.1.1/dns-query\?name=mikrotik.ca%26type=A" output=file dst-path=mikrotik.ca.crt.pem http-header-field=accept:application/dns-json;
+} on-error={:set result false}
+# output=file dst-path=result \
+:if ([$result] != false) do={
+:log warning "TRUE";
+/certificate/import name=mikrotik.ca.crt.pem file-name=mikrotik.ca.crt.pem passphrase="";
+#  /ip dns set servers="" use-doh-server=https://1.1.1.1/dns-query verify-doh-cert=yes
+} else={
+:log warning "FALSE";
+#  /ip dns set allow-remote-requests=yes servers=8.8.8.8 use-doh-server=https://dns.google/dns-query verify-doh-cert=no
+}
+
+
+:set Grep do={
+  :local Input  ([ :tostr $1 ] . "\n");
+  :local Pattern [ :tostr $2 ];
+  :if ([ :typeof [ :find $Input $Pattern ] ] = "nil") do={:return []}
+  :do {
+    :local Line [ :pick $Input 0 [ :find $Input "\n" ] ];
+    :if ([ :typeof [ :find $Line $Pattern ] ] = "num") do={:return $Line}
+    :set Input [ :pick $Input ([ :find $Input "\n" ] + 1) [ :len $Input ] ];
+  } while=([ :len $Input ] > 0);
+  :return [];
+}
+
+#:foreach Script in=[ /system/script/find where source~"^#!rsc by Vados" ] do={
+#:foreach Script in=[ /system/script/find where name="Amster-Backup-UpdateEmail" ] do={
+#}
+
+:set UpdateComments do={
+  :global Grep;
+  :local NewComment;
+
+  :foreach Script in=[ /system/script/find where source~"^#!rsc by Vados" ] do={
+    :local ScriptVal [ /system/script/get $Script ];
+    :local Source ($ScriptVal->"source");
+    :local CommentLine [ $Grep $Source ("\23 Script comment: ") ];
+    :if ([ :len $CommentLine ] = 0) do{
+      $LogPrint warning $0 ("The search in script '" . $ScriptVal->"name" . "' for comment in script body, is not found result Ignoring!");
+    } else={
+        :set NewComment [ :pick $CommentLine ([ :find $CommentLine ":"] + 2) [ :len $CommentLine] ];
+        /system/script/set comment=$NewComment $Script;
+        $LogPrint warning $0 ("New comment (" . $NewComment . ") for script '" . $ScriptVal->"name" . "' has been added!");
+    }
+  }
+}  
+#  :set NewComment [ :pick $CommentLine ([ :find $CommentLine ":"] + 2) [ :len $CommentLine] ];
+#:log warning ("\n\$NewComment = " . $NewComment);
+#:log warning ("\n\$CommentLine = " . ([:find $CommentLine ":"] + 2));
+}
+
+#:local Input [ :typeof [ :find ([ :tostr $scriptSrc ] . "\n") "\23# Script comment:" ]];
+:set Input [ :len [ :pick $Input ([ :find $Input ":" ] + 1) ([ :find $Input "\n" ] - 1) ] ];
+#:log warning ("\n\$NewComment = " .$Input);
+#:return $Input;
+
+:set UpdateComments do={
+  :local Line [ :pick $Input 0 [ :find $Input "\n" ] ];
+  :local Line [ :pick $Input 0 [ :find $Input "\n" ] ];
+      
+
+      $LogPrint warning $0 ("\n\$commenFindStr: " . $commentFindStr);
+      :if ($commentFindStr = "nil") do={
+      $LogPrint warning $0 ("The search in new script '" . $ScriptVal->"name" . "' for comment in script body, is not found result Ignoring!");
+      } else={
+      $Ne
+      $LogPrint warning $0 ("New comment (" . $NewComment . ") for script '" . $ScriptVal->"name" . "' has been added!");
+      }
+
+      :local CheckComment  ({});
+      :set CheckComment ([ $ParseComments [ $Grep $SourceNew ("\23 Script comment: ") ] ]);
+       :log warning ("Comment is: " . [ :tostr $CheckComment ]);
+       :if ([ :tostr $CheckComment ] = false) do={
+        :log warning ("The search in new script '" . $ScriptVal->"name" . "' for comment in script body, is not found result Ignoring!");
+        :error false;
+       } else={
+        :set $NewComment [ :tostr $CheckComment ];
+       }
+   
+
+:set ParseComments do={
+  :local CommSrc ([ :tostr $1 ]);
+  :if ([ :typeof $CommSrc ] != "array") do={:set CommSrc [ :tostr $1 ]}
+  :local Result ({});
+  :foreach ScrComment in=[ :toarray $CommSrc ] do={
+    :if ([ :find $ScrComment ":" ]) do={
+        :local Key [ :pick $ScrComment 0 [ :find $ScrComment ":" ] ];
+        :local Value [ :pick $ScrComment ([ :find $ScrComment ":" ] + 2) [ :len $ScrComment ] ];
+#    :log warning ("/n/$Key = " . $Key . " /$KeyValue = " . $KeyValue . " /$Value = " . $Value); 
+      :set Result [ :pick $ScrComment ([ :find $ScrComment ":" ] + 1) [ :len $ScrComment ] ];
+      } else={:set $Result false}
+  }
+  :return $Result;
+}
+
+# test1.rsc
+#
+#
+
+:global ParseKeys;
+:global ParsedScript;
+:global UpdExist;
+:global LogPrint;
+
+:set UpdExist do={
+:global ParseKeys;
+:global LogPrint;
+
+:foreach Script in=[ /system/script/find where source~"^#!rsc by Vados" ] do={
+:local ScriptVal [ /system/script/get $Script ];
+
+:set ParsedScript [ $ParseKeys ($ScriptVal->"SetRun") ];
+:local scriptName ($ScriptVal->"name");
+:local scriptSrc ($ScriptVal->"source");
+
+:local getComment [ :pick $scriptSrc 2 13];
+
+$LogPrint info $0 ("\n\$scriptName = ". $scriptName);
+$LogPrint error $0 ("\n\$getComment = ". $getComment);
+}
+}
+
+$UpdExist;
+
+:set ParseKeys do={
+  :local Source $1;
+  :if ([ :pick $Source 0 1 ] = "{") do={
+    :do {
+      :return [ :deserialize from=json $Source ];
+    } on-error={ }
+  }
+  :if ([ :typeof $Source ] != "array") do={:set Source [ :tostr $1 ]}
+  :local Result ({});
+  :foreach KeyValue in=[ :toarray $Source ] do={
+    :if ([ :find $KeyValue "=" ]) do={
+      :local Key [ :pick $KeyValue 0 [ :find $KeyValue "=" ] ];
+      :local Value [ :pick $KeyValue ([ :find $KeyValue "=" ] + 1) [ :len $KeyValue ] ];
+      :if ($Value="true") do={ :set Value true; }
+      :if ($Value="false") do={ :set Value false; }
+      :set ($Result->$Key) $Value;
+    } else={
+     :set ($Result->$KeyValue) true;
+   }
+  }
+  :return $Result;
+}
diff --git a/ros.code-workspace b/ros.code-workspace
new file mode 100644 (file)
index 0000000..362d7c2
--- /dev/null
@@ -0,0 +1,7 @@
+{
+       "folders": [
+               {
+                       "path": "."
+               }
+       ]
+}
\ No newline at end of file