]> Git [git.vados.ru] :: Repos - Mikrotiks/ros.vados.ru.git/commitdiff
Autocommit in repo: ros.vados.ru, generated by owner: root, in 11:50:04 12.09.2026.
authorvados-dev <192440777+vados-dev@users.noreply.github.com>
Sat, 12 Sep 2026 08:50:05 +0000 (11:50 +0300)
committervados-dev <192440777+vados-dev@users.noreply.github.com>
Sat, 12 Sep 2026 08:50:05 +0000 (11:50 +0300)
html/AM-GlobalFunc.rsc
html/AM-awg3vdsInstall.rsc [new file with mode: 0644]

index 695e25223008822fa9dd58ee976acd1b06b94991..342ec6c45c53c038020c40d874adc56b396f0544 100644 (file)
@@ -17,9 +17,9 @@
 :global GlobalFuncReady false;
 
 # Git commit id & info, expected configuration version
-:global CommitId "main/16713/cbbd0da6";
+:global CommitId "main/16714/4a1cab3a";
 :global CommitTAG "unknown";
-:global CommitInfo "Autocommit in repo: ros.vados.ru, generated by owner: root, in 11:45:04 12.09.2026.";
+:global CommitInfo "Autocommit in repo: ros.vados.ru, generated by owner: root, in 11:50:04 12.09.2026.";
 
 # The variables are received right here.
 :global GlobalConfVersion 140;
diff --git a/html/AM-awg3vdsInstall.rsc b/html/AM-awg3vdsInstall.rsc
new file mode 100644 (file)
index 0000000..2cf0f49
--- /dev/null
@@ -0,0 +1,472 @@
+#!rsc by Vados
+# RouterOS script: AM-awg3vdsInstall
+# Script comment: awg3vds installer
+#
+#
+# requires RouterOS, version=7.19
+# requires device-mode, fetch, scheduler
+# ============================================================
+# AWG Proxy -- MikroTik RouterOS (protocol: v3)
+# 2026-09-12 08:41:48
+# ============================================================
+# 0. Check prerequisites
+:if ([:len [/system/package/find where name="container" disabled=no]] = 0) do={
+  :put "Container package is not installed or disabled. Install it and reboot."
+  :error "Usage: /system/device-mode/update container=yes"
+}
+
+# Check storage device
+{
+  :local targetDisk "usb1"
+  :local diskFree 0
+  :do { :set diskFree [/disk/get [find where mount-point=$targetDisk] free] } on-error={}
+  :if ($diskFree = 0) do={
+    :local baseDisk $targetDisk
+    :local dashPos [:find $targetDisk "-part"]
+    :if ([:typeof $dashPos] != "nil") do={ :set baseDisk [:pick $targetDisk 0 $dashPos] }
+    :if ([:len [/disk/find where slot=$baseDisk]] > 0) do={
+      :put ("Device $baseDisk found but $targetDisk is not available.")
+      :put "Format $baseDisk as ext4? ALL DATA WILL BE ERASED!"
+      :put "Press 'y' to confirm, any other key to cancel:"
+      :local key [/terminal/inkey timeout=30]
+      :if ($key = 121 || $key = 89) do={
+        :put "Formatting $baseDisk as ext4..."
+        /disk/format-drive $baseDisk file-system=ext4 label=$baseDisk
+        :delay 3s
+        :do { :set diskFree [/disk/get [find where mount-point=$targetDisk] free] } on-error={}
+        :if ($diskFree = 0) do={
+          :put ("ERROR: $targetDisk not found after formatting.")
+          :put "Check mount point: /disk/print"
+          :error ("Mount point $targetDisk not found after formatting")
+        }
+        :put ("Formatted OK. Free: " . ($diskFree / 1048576) . " MB")
+      } else={
+        :error "Formatting cancelled"
+      }
+    } else={
+      :put ("ERROR: Storage device $targetDisk not found.")
+      :put "Check available disks: /disk/print"
+      :error ("Storage not found: " . $targetDisk)
+    }
+  } else={
+    :put ("Storage OK: $targetDisk (" . ($diskFree / 1048576) . " MB free)")
+  }
+}
+
+# 1. Uninstall and update scripts
+/system/script/add name=awg3vds-uninstall comment=awg3vds source={
+  :put "Uninstallimg AmneziaWG..."
+  :log info "Uninstallimg AmneziaWG..."
+  :local prevDNS ""
+  :do { :set prevDNS [/container/envs/get [find where list="awg3vds-env" key="AWG_PREV_DNS"] value] } on-error={}
+  :local prevTmpdir ""
+  :do { :set prevTmpdir [/container/envs/get [find where list="awg3vds-env" key="AWG_PREV_TMPDIR"] value] } on-error={}
+  /ip/route/remove [find where comment=awg3vds-tunnel]
+  /ip/route/remove [find where comment=awg3vds-dns]
+  :do { /system/scheduler/remove [find where comment~"awg3vds"] } on-error={}
+  :do { /system/script/remove [find where comment~"awg3vds" and name!="awg3vds-uninstall"] } on-error={}
+  /container/stop [find where interface=veth-awg3vds]
+  :delay 7s
+  /container/remove [find where interface=veth-awg3vds]
+  /container/envs/remove [find where list="awg3vds-env"]
+  :if ($prevDNS != "") do={
+    :do { /ip/dns/set servers="$prevDNS" } on-error={}
+    :put ("DNS restored to: $prevDNS")
+  }
+  /container/config set tmpdir=$prevTmpdir
+  :put ("Container tmpdir restored to: $prevTmpdir")
+  /ip/firewall/nat/remove [find where out-interface="wg-awg3vds"]
+  /ip/firewall/nat/remove [find where src-address="172.19.14.100/30"]
+  /ip/address/remove [find where address="172.19.14.101/30"]
+  :do { /ipv6/firewall/nat/remove [find where comment=awg3vds-v6-nat] } on-error={}
+  :do { /ipv6/firewall/nat/remove [find where comment=awg3vds-dstnat6] } on-error={}
+  :do { /ipv6/firewall/filter/remove [find where comment=awg3vds-v6-out] } on-error={}
+  :do { /ipv6/firewall/filter/remove [find where comment=awg3vds-awg-in6] } on-error={}
+  :do { /ipv6/nd/remove [find where interface=veth-awg3vds] } on-error={}
+  :do { /ipv6/nd/prefix/remove [find where interface=veth-awg3vds] } on-error={}
+  :do { /ipv6/address/remove [find where comment=awg3vds-v6] } on-error={}
+  /interface/veth/remove [find where name="veth-awg3vds"]
+  /interface/wireguard/peers/remove [find where interface="wg-awg3vds"]
+  /ip/address/remove [find where interface="wg-awg3vds"]
+  /interface/wireguard/remove [find where name="wg-awg3vds"]
+  :do { /file/remove [find where name~"awg3vds.+tar"] } on-error={}
+  :do { /file/remove [find where name="usb1/awg3vds"] } on-error={}
+  :do { /file/remove [find where name="usb1/ru_ranges_timeout.rsc"] } on-error={}
+  :do { /file/remove [find where name="usb1/ru_ranges.rsc"] } on-error={}
+  :do { /file/remove [find where name~"usb1/pull"] } on-error={}
+  :put "Uninstall AmneziaWG Proxy complete!"
+  :log info "Uninstall AmneziaWG Proxy complete!"
+#  /system/script/remove [find where name=awg3vds-uninstall]
+}
+/system/script/add name=awg3vds-update comment=awg3vds source={
+  :local cids [/container/find where interface=veth-awg3vds and !stopped]
+  :if ([:len $cids] = 0) do={ :set cids [/container/find where interface=veth-awg3vds and start-on-boot=yes] }
+  :if ([:len $cids] = 0) do={ :set cids [/container/find where interface=veth-awg3vds] }
+  :if ([:len $cids] = 0) do={
+    :put "Container awg3vds not found"
+    :log warning "awg3vds-update: container not found"
+  } else={
+    :local cid [:pick $cids ([:len $cids] - 1)]
+    :local img [/container/get $cid remote-image]
+    :local ver [/system/resource/get version]
+    :local dotPos [:find $ver "."]
+    :local rest [:pick $ver ($dotPos + 1) [:len $ver]]
+    :local endPos [:find $rest "."]
+    :if ([:typeof $endPos] = "nil") do={ :set endPos [:find $rest " "] }
+    :local minor [:tonum [:pick $rest 0 $endPos]]
+    :local wantRunning true
+    :do { :if ([/container/get $cid start-on-boot] != true) do={ :set wantRunning false } } on-error={}
+    :local healthy true
+    :if ([:len [/container/get $cid image-id]] = 0) do={ :set healthy false }
+    :if ($wantRunning) do={
+      :do { :if ([/container/get $cid stopped] = true) do={ :set healthy false } } on-error={}
+    }
+    :if (!$healthy && $wantRunning && [:len [/container/get $cid image-id]] > 0) do={
+      :put "Container is down - trying to start it"
+      :do { /container/start $cid } on-error={}
+      :delay 8s
+      :set healthy true
+      :do { :if ([/container/get $cid stopped] = true) do={ :set healthy false } } on-error={}
+      :if ($healthy) do={
+        :put "Container is back up"
+        :log info "awg3vds: container restarted by the update script"
+      }
+    }
+    :if ($img = "" && $minor >= 22 && !$healthy) do={
+      :local imgTag "latest"
+      :local rbModel ""
+      :do { :set rbModel [[:parse ":return [/system/routerboard/get model]"]] } on-error={}
+      :local bName [/system/resource/get board-name]
+      :if ($rbModel~"E50UG" || $rbModel~"E60iUGS" || $bName~"E50UG" || $bName~"E60iUGS" || $bName~"^hEX") do={
+        :set imgTag "latest-armv5"
+      }
+      :set img ("ghcr.io/timbrs/awg-proxy:" . $imgTag)
+      :put ("Container is down and was installed from a file - pointing it at " . $img)
+      :log warning "awg3vds-update: broken file install, switching to the registry"
+      :do { /container/set $cid remote-image=$img } on-error={}
+    }
+    :if ($img = "") do={
+      :put "Installed from a local image file - /container/repull only updates registry installs."
+      :put "Reinstall with the configurator on RouterOS 7.22+ to switch to one-command updates."
+    } else={
+      :if ($minor < 22) do={
+        :put ("/container/repull needs RouterOS 7.22+, this router runs " . $ver)
+        :if (!$healthy) do={ :log error ("awg3vds-update: container is down, and " . $ver . " has no /container/repull") }
+      } else={
+        :local rootDir [/container/get $cid root-dir]
+        :if ([:pick $rootDir 0 1] = "/") do={ :set rootDir [:pick $rootDir 1 [:len $rootDir]] }
+        :local rootDirOrig $rootDir
+        :local slashPos [:find $rootDir "/"]
+        :local disk $rootDir
+        :if ([:typeof $slashPos] != "nil") do={ :set disk [:pick $rootDir 0 $slashPos] }
+        :local verPath "awg-update-check.json"
+        :if ($disk != "disk1") do={
+          :do { /file/make-directory ($disk . "/pull") } on-error={}
+          /container/config set tmpdir=($disk . "/pull")
+          :set verPath ($disk . "/pull/awg-update-check.json")
+        }
+        :local newVer ""
+        :do {
+          /tool/fetch url="https://api.github.com/repos/timbrs/amneziawg-mikrotik-c/releases/latest" dst-path=$verPath http-max-redirect-count=10
+          :local relJson [/file/get [find where name=$verPath] contents]
+          :local tagPos [:find $relJson "\"tag_name\":\""]
+          :if ([:typeof $tagPos] != "nil") do={
+            :local tagRest [:pick $relJson ($tagPos + 12) [:len $relJson]]
+            :set newVer [:pick $tagRest 0 [:find $tagRest "\""]]
+          }
+          :do { /file/remove [find where name=$verPath] } on-error={}
+        } on-error={ :put "Could not read the published version" }
+        :local curVer ""
+        :do { :set curVer [/container/envs/get [find where list="awg3vds-env" key="AWG_IMAGE_VER"] value] } on-error={}
+        :local doPull true
+        :if ($healthy) do={
+          :if ($newVer = "") do={
+            :set doPull false
+            :put "Published version unknown - leaving the healthy container alone"
+          }
+          :if ($newVer != "" && $newVer = $curVer) do={
+            :set doPull false
+            :put ("Already on " . $curVer . " - nothing to pull, tunnel untouched")
+          }
+        }
+        :if ($doPull) do={
+          :if (!$healthy) do={
+            :put "Container is not healthy - pulling the image again"
+            :log warning "awg3vds-update: container unhealthy, forcing a pull"
+          }
+          :put ("Updating " . $img . " ...")
+          :log info "awg3vds: repull started"
+          :do { /file/remove [find where name~($disk . "/awg3vds.backup")] } on-error={}
+          :do { [:parse "/container/set $cid ignore-remote-image-change=yes"] } on-error={}
+          /container/repull $cid
+          :local waited 0
+          :while ([:len [/container/get $cid image-id]] = 0 && $waited < 180) do={
+            :delay 3s
+            :set waited ($waited + 3)
+          }
+          :if ([:len [/container/get $cid image-id]] = 0) do={
+            :put "Pull failed - retrying once in 15s"
+            :delay 15s
+            /container/repull $cid
+            :set waited 0
+            :while ([:len [/container/get $cid image-id]] = 0 && $waited < 180) do={
+              :delay 3s
+              :set waited ($waited + 3)
+            }
+          }
+          :local backUp false
+          :if ([:len [/container/get $cid image-id]] > 0) do={
+            :set backUp true
+            :if ($wantRunning) do={
+              :delay 3s
+              :do { /container/start $cid } on-error={}
+              :delay 8s
+              :set backUp false
+              :do { :if ([/container/get $cid stopped] != true) do={ :set backUp true } } on-error={}
+            }
+          }
+          :if (!$backUp) do={
+            :put "Container did not come back up - rebuilding it from the registry"
+            :log warning "awg3vds-update: container did not come back, rebuilding"
+            :do { /container/stop $cid } on-error={}
+            :delay 5s
+            :do { /container/remove $cid } on-error={}
+            :delay 3s
+            :do { /file/remove [find where name~($disk . "/awg3vds.backup")] } on-error={}
+            :do { /container/add remote-image=$img interface=veth-awg3vds envlist=awg3vds-env hostname=awg3vds root-dir=$rootDirOrig logging=no start-on-boot=yes comment=awg3vds } on-error={}
+            :set waited 0
+            :local newCids [/container/find where interface=veth-awg3vds and start-on-boot=yes]
+            :if ([:len $newCids] = 0) do={ :set newCids [/container/find where interface=veth-awg3vds] }
+            :while ([:len $newCids] > 0 && [:len [/container/get [:pick $newCids ([:len $newCids] - 1)] image-id]] = 0 && $waited < 180) do={
+              :delay 3s
+              :set waited ($waited + 3)
+            }
+            :if ([:len $newCids] > 0 && [:len [/container/get [:pick $newCids ([:len $newCids] - 1)] image-id]] > 0) do={
+              :do { /container/set [:pick $newCids ([:len $newCids] - 1)] shm-size=4M } on-error={}
+              :do { [:parse "/container/set [find where interface=veth-awg3vds] ignore-remote-image-change=yes"] } on-error={}
+              :do { [:parse "/container/set [find where interface=veth-awg3vds] restart-policy=on-failure restart-interval=30s restart-max-count=10"] } on-error={}
+              :do { /container/start [:pick $newCids ([:len $newCids] - 1)] } on-error={}
+              :delay 8s
+              :do { :if ([/container/get [:pick $newCids ([:len $newCids] - 1)] stopped] != true) do={ :set backUp true } } on-error={}
+            }
+            :if ($backUp) do={
+              :put "Container rebuilt"
+              :log info "awg3vds: container rebuilt after update"
+            } else={
+              :put "Rebuild failed - the tunnel is down. Netwatch keeps the router online meanwhile."
+              :put "This script runs again on the next schedule and will retry."
+              :log error "awg3vds-update: rebuild failed, tunnel is down"
+            }
+          }
+          :if ($backUp && $newVer != "") do={
+            :do { /container/envs/remove [find where list="awg3vds-env" key="AWG_IMAGE_VER"] } on-error={}
+            :do { /container/envs/add list=awg3vds-env key=AWG_IMAGE_VER value=$newVer } on-error={}
+          }
+          :do { /file/remove [find where name~($disk . "/awg3vds.backup")] } on-error={}
+          :if ($backUp) do={
+            :put ("Update done: " . $img)
+            :log info "awg3vds: update done"
+          }
+        }
+      }
+    }
+  }
+}
+# Daily update check. Skipped if scheduler is not allowed by device-mode
+{
+  :local schedOk true
+  :do { :set schedOk [/system/device-mode/get scheduler] } on-error={}
+  :if ($schedOk) do={
+    /system/scheduler/add name=awg3vds-update on-event="/system/script/run awg3vds-update" start-time=04:30:00 interval=1d comment=awg3vds
+  } else={
+    :put "WARNING: scheduler not allowed by device-mode, no daily update check."
+    :put "Update by hand: /system/script/run awg3vds-update"
+    :put "To allow it: /system/device-mode/update scheduler=yes"
+  }
+}
+
+# 2. Network infrastructure
+/interface/veth/add name=veth-awg3vds address=172.19.14.102/30 gateway=172.19.14.101
+/ip/address/add address=172.19.14.101/30 interface=veth-awg3vds
+/ip/firewall/nat/add chain=srcnat action=masquerade src-address=172.19.14.100/30
+
+# 3. WireGuard interface (MikroTik derives the public key automatically)
+/interface/wireguard/add name=wg-awg3vds private-key="AEU+diR7SCIEOtwOWSWR3pEpkw6v0Qro2LRF0SnPpnw=" listen-port=41430 mtu=1320 disabled=yes
+/interface/wireguard/peers/add interface=wg-awg3vds public-key="MMWjD/TPNpupCvhXLCAw0xHXV4N6jgtQkg9Pl6pcQhU=" preshared-key="r9B13bB7gUTqde6zgBDfffFIUN6puP2gOWdK5zVS3nw=" endpoint-address=172.19.14.102 endpoint-port=51820 allowed-address=10.31.31.0/24,0.0.0.0/0 persistent-keepalive=14
+/ip/address/add address=10.31.31.2/24 interface=wg-awg3vds
+
+# 4. Container environment variables
+/container/envs/add list=awg3vds-env key=AWG_LISTEN value=":51820"
+/container/envs/add list=awg3vds-env key=AWG_REMOTE value="89.110.101.17:31031"
+/container/envs/add list=awg3vds-env key=AWG_JC value="5"
+/container/envs/add list=awg3vds-env key=AWG_JMIN value="510"
+/container/envs/add list=awg3vds-env key=AWG_JMAX value="952"
+/container/envs/add list=awg3vds-env key=AWG_S1 value="138"
+/container/envs/add list=awg3vds-env key=AWG_S2 value="105"
+/container/envs/add list=awg3vds-env key=AWG_H1 value="457615290-457646150"
+/container/envs/add list=awg3vds-env key=AWG_H2 value="1307355386-1307363580"
+/container/envs/add list=awg3vds-env key=AWG_H3 value="2441734303-2441781787"
+/container/envs/add list=awg3vds-env key=AWG_H4 value="3719180788-3719195844"
+/container/envs/add list=awg3vds-env key=AWG_SERVER_PUB value="MMWjD/TPNpupCvhXLCAw0xHXV4N6jgtQkg9Pl6pcQhU="
+/container/envs/add list=awg3vds-env key=AWG_CLIENT_PUB value=[/interface/wireguard/get [find name=wg-awg3vds] public-key]
+/container/envs/add list=awg3vds-env key=AWG_S3 value="51"
+/container/envs/add list=awg3vds-env key=AWG_S4 value="22"
+/container/envs/add list=awg3vds-env key=AWG_I1 value="<b 0x281f01000001000000000000><rc 7><rd 2><r 109>"
+/container/envs/add list=awg3vds-env key=AWG_I2 value="<rc 13><b 0xf0773b61e8e9e015129b><t><r 63><rd 6>"
+/container/envs/add list=awg3vds-env key=AWG_I3 value="<rc 6><b 0x021b4d><t><r 47><rd 8>"
+/container/envs/add list=awg3vds-env key=AWG_I4 value="<rc 15><b 0x0329d6><t><r 52><rd 8>"
+/container/envs/add list=awg3vds-env key=AWG_I5 value="<rc 13><b 0x220483a81a8cd7e37e66><t><r 22><rd 4>"
+/container/envs/add list=awg3vds-env key=AWG_HEADER_PROTECTION_KEY value="FkGxbrW6ds3YPWNl48OWFEccrLC4tF94z9MnoKUZUsM="
+/container/envs/add list=awg3vds-env key=AWG_CONTENT_PADDING value="54-119"
+/container/envs/add list=awg3vds-env key=AWG_REKEY_AFTER value="102-130"
+/container/envs/add list=awg3vds-env key=AWG_REKEY_TIMEOUT value="6-8"
+/container/envs/add list=awg3vds-env key=AWG_REJECT_AFTER value="174-195"
+/container/envs/add list=awg3vds-env key=AWG_KEEPALIVE_TIMEOUT value="14-21"
+/container/envs/add list=awg3vds-env key=AWG_MAX_HANDSHAKE_ATTEMPTS value="12-14"
+/container/envs/add list=awg3vds-env key=AWG_NO_DF value="1"
+# Perf: give each forwarding thread its own core (4+ core routers)
+{ :if ([/system/resource/get cpu-count] >= 4) do={ /container/envs/add list=awg3vds-env key=AWG_CPU_C2S value="1"; /container/envs/add list=awg3vds-env key=AWG_CPU_S2C value="2" } }
+# NOTE on MTU: the value above assumes this tunnel is the last hop.
+#   If the server forwards your traffic into another tunnel, subtract
+#   that tunnel's overhead too, or every packet leaves in two fragments.
+
+# 5. DNS
+{ :local rawDNS [/ip/dns/get servers]; :local prevDNS ""; :foreach s in=[:toarray $rawDNS] do={ :if ([:len $prevDNS] > 0) do={ :set prevDNS "$prevDNS,$s" } else={ :set prevDNS $s } }; /container/envs/add list=awg3vds-env key=AWG_PREV_DNS value="$prevDNS" }
+# NOTE: replaces existing DNS configuration
+/ip/dns/set servers=89.110.101.17,8.8.4.4
+# DNS 89.110.101.17 идёт общим маршрутом (в активный туннель / default),
+# без привязки к wg-awg3vds: иначе падение туннеля уносит с собой резолв.
+# DNS 8.8.4.4 идёт общим маршрутом (в активный туннель / default),
+# без привязки к wg-awg3vds: иначе падение туннеля уносит с собой резолв.
+
+# 6. Download, create and start container
+:local prevTmpdir [/container/config/get tmpdir]
+/container/envs/add list=awg3vds-env key=AWG_PREV_TMPDIR value=$prevTmpdir
+/container/config set tmpdir=usb1/pull memory-high=200M
+{
+  :local arch [/system/resource/get architecture-name]
+  :local ver [/system/resource/get version]
+  :local dotPos [:find $ver "."]
+  :local rest [:pick $ver ($dotPos + 1) [:len $ver]]
+  :local endPos [:find $rest "."]
+  :if ([:typeof $endPos] = "nil") do={ :set endPos [:find $rest " "] }
+  :local minor [:tonum [:pick $rest 0 $endPos]]
+  :local suffix ""
+  :if ($minor <= 20) do={ :set suffix "-7.20-Docker" }
+  :local file ""
+  :local imgTag "latest"
+  :if ($arch = "arm64") do={ :set file ("awg-proxy-arm64" . $suffix . ".tar.gz") }
+  :if ($arch = "arm") do={
+    :set file ("awg-proxy-arm" . $suffix . ".tar.gz")
+    :local rbModel ""
+    :do { :set rbModel [[:parse ":return [/system/routerboard/get model]"]] } on-error={}
+    :local bName [/system/resource/get board-name]
+    :if ($rbModel~"E50UG" || $rbModel~"E60iUGS" || $bName~"E50UG" || $bName~"E60iUGS" || $bName~"^hEX") do={
+      :set file ("awg-proxy-armv5" . $suffix . ".tar.gz")
+      :set imgTag "latest-armv5"
+    }
+  }
+  :if ($arch ~ "x86") do={ :set file ("awg-proxy-amd64" . $suffix . ".tar.gz") }
+  :if ($file = "") do={ :error "Unsupported architecture: $arch" }
+  :local url "https://github.com/timbrs/amneziawg-mikrotik-c/releases/latest/download/$file"
+  :local filePath ("usb1/pull/" . $file)
+  :local altPath ("usb1/" . $file)
+  :local rootDir "usb1/awg3vds"
+  :do { /file/make-directory usb1/pull } on-error={}
+  :if ([:len [/file/find where name=$filePath]] = 0 && [:len [/file/find where name=$altPath]] > 0) do={
+    :set filePath $altPath
+  }
+  :local added false
+  :local fetched false
+  :if ([:len [/file/find where name=$filePath]] > 0) do={
+    :put ("Using image already on the router: " . $filePath)
+    /container/add file=$filePath interface=veth-awg3vds envlist=awg3vds-env hostname=awg3vds root-dir=$rootDir logging=no start-on-boot=yes comment=awg3vds
+    :set added true
+  }
+  :if (!$added && $minor >= 22) do={
+    :local img ("ghcr.io/timbrs/awg-proxy:" . $imgTag)
+    :put ("Pulling " . $img)
+    /container/add remote-image=$img interface=veth-awg3vds envlist=awg3vds-env hostname=awg3vds root-dir=$rootDir logging=no start-on-boot=yes comment=awg3vds
+    :local cids [/container/find where interface=veth-awg3vds]
+    :local waited 0
+    :while ([:len $cids] > 0 && [:len [/container/get [:pick $cids 0] image-id]] = 0 && $waited < 120) do={
+      :delay 2s
+      :set waited ($waited + 2)
+    }
+    :if ([:len $cids] > 0 && [:len [/container/get [:pick $cids 0] image-id]] > 0) do={
+      :set added true
+      :put ("Pulled from registry in " . $waited . "s")
+      :do {
+        :local verPath ($filePath . ".rel")
+        /tool/fetch url="https://api.github.com/repos/timbrs/amneziawg-mikrotik-c/releases/latest" dst-path=$verPath http-max-redirect-count=10
+        :local relJson [/file/get [find where name=$verPath] contents]
+        :local tagPos [:find $relJson "\"tag_name\":\""]
+        :local tagRest [:pick $relJson ($tagPos + 12) [:len $relJson]]
+        /container/envs/add list=awg3vds-env key=AWG_IMAGE_VER value=[:pick $tagRest 0 [:find $tagRest "\""]]
+        /file/remove [find where name=$verPath]
+      } on-error={}
+    } else={
+      :put "Registry unavailable, falling back to the release archive"
+      :do { /container/remove $cids } on-error={}
+      :delay 2s
+    }
+  }
+  :if (!$added) do={
+    :local freeStorage 0
+    :do { :set freeStorage [/disk/get [find where mount-point="usb1"] free] } on-error={}
+    :if ($freeStorage < 262144) do={
+      :put ("WARNING: Low disk space (" . ($freeStorage / 1024) . "KB free). Need at least 256KB.")
+      :put "See: https://github.com/timbrs/amneziawg-mikrotik-c#insufficient-disk-space"
+      :error "Insufficient disk space"
+    }
+    :put ("Fetching: $url -> " . $filePath)
+    /tool/fetch url=$url dst-path=$filePath http-max-redirect-count=10
+    /container/add file=$filePath interface=veth-awg3vds envlist=awg3vds-env hostname=awg3vds root-dir=$rootDir logging=no start-on-boot=yes comment=awg3vds
+    :set fetched true
+  }
+  :if ($minor > 20) do={ [:parse "/container/set [find where interface=veth-awg3vds] shm-size=4M"] }
+  :do { [:parse "/container/set [find where interface=veth-awg3vds] ignore-remote-image-change=yes"] } on-error={}
+  :do { [:parse "/container/set [find where interface=veth-awg3vds] restart-policy=on-failure restart-interval=30s restart-max-count=10"] } on-error={}
+  :if ($fetched) do={ /file/remove $filePath }
+  :local freeMem [/system/resource/get free-memory]
+  :if ($freeMem < 16777216) do={
+    :put ("WARNING: Low memory (" . ($freeMem / 1048576) . "MB free). 16MB+ recommended.")
+  }
+  /container/start [find where interface=veth-awg3vds]
+  :do { /file/remove [find where name="console-dump.txt"] } on-error={}
+  :put "Waiting for container to start..."
+  :delay 5s
+  /interface/wireguard/enable wg-awg3vds
+  :put "WireGuard interface enabled"
+  :put "Installation complete!"
+  :put "Ping test: 89.110.101.17 via AmneziaWG tunnel. repeat 10 times"
+  /ping 89.110.101.17 count=10 interval=1
+  :put ""
+  :put "======= NEXT STEPS ======="
+  :put ""
+  :put "Route specific traffic through the AmneziaWG tunnel:"
+  :put ""
+  :put "  Route a single host:"
+  :put "    /ip/route/add dst-address=8.8.8.8/32 gateway=wg-awg3vds comment=awg3vds-tunnel"
+  :put ""
+  :put "  Route a subnet:"
+  :put "    /ip/route/add dst-address=100.0.0.0/8 gateway=wg-awg3vds comment=awg3vds-tunnel"
+  :put ""
+  :put "  Remove a route:"
+  :put "    /ip/route/remove [find where comment=awg3vds-tunnel dst-address=8.8.8.8/32]"
+  :put ""
+  :put "  Show all tunnel routes:"
+  :put "    /ip/route/print where comment=awg3vds-tunnel"
+  :put ""
+  :put "  Update the container:"
+  :put "    /system/script/run awg3vds-update"
+  :put ""
+  :put "  Uninstall:"
+  :put "    /system/script/run awg3vds-uninstall"
+  :put "========================="
+}
+
+# 7. Verification
+# /container/print
+# /interface/wireguard/print
+# /interface/wireguard/peers/print