0.3.16
- The router now makes its own account and name-lookup requests through the same fallback paths as the apps, so it keeps reaching ForestVPN on networks that interfere with them.
ForestVPN as a container on a MikroTik router running RouterOS 7. It registers as its own device and prints a pairing code.
A RouterOS container, verified on real hardware — a MikroTik cAP ax pulled the image, registered, printed a pairing code, and connected. It holds its own machine key and asks the control plane for an exit the same way the phone does.
Three lines in RouterOS's terminal — a /30 that exists only between the container and the router, nothing to hunt for on your LAN.
Pinned image, not :latest — a router you are not standing in front of should never silently swap what it is running.
It prints to /log before the container has any network reachability at all. Open the ForestVPN app, already signed in with your account number, and enter the code.
The router serves its own status page on the address you just gave it — open it from a phone on the same network and tap a location. That is the one thing a screenless box cannot do for itself.
/interface/veth/add name=veth-fvpn address=172.31.254.2/30 gateway=172.31.254.1
/ip/address/add address=172.31.254.1/30 interface=veth-fvpn
/interface/list/member/add list=LAN interface=veth-fvpn
# A `flash` folder (hAP ac² and other 16 MB boards) means only flash/ survives a reboot: the state
# goes there, the log stays in RAM, and the image is pulled again at every boot.
:global fvRAMROOT ([:len [/file/find where name="flash"]] > 0)
:global fvSTATE "/forestvpn"
:global fvCMD "--agent --nat-lan --iface fvpn0 --config /etc/forestvpn/fvpn0.conf --policy-route --fwmark 51820 --table 100 --http-port 80 --report /etc/forestvpn/result.txt"
:if ($fvRAMROOT) do={
:global fvSTATE
:global fvCMD
:set fvSTATE "flash/forestvpn"
:set fvCMD ($fvCMD . " --log-dir /var/log")
}
/container/mounts/add list=fvpn-state src=$fvSTATE dst=/etc/forestvpn mode=rw
/container/add remote-image=suquant/fvpn:0.3.17 interface=veth-fvpn root-dir=/fvpn \
mountlists=fvpn-state entrypoint=/mikrotik.sh cmd=$fvCMD \
privileged=yes logging=yes start-on-boot=yes dns=172.31.254.1 name=fvpn
:if ($fvRAMROOT) do={
/container/set [find name="fvpn"] start-on-boot=no
/system/script/add name=forestvpn-repull comment="forestvpn" source={
:local c [/container/find where name="fvpn"]
:if ([:len $c] = 1) do={
:local n 0
:while ((![/container/get $c running]) && ($n < 60)) do={
:set n ($n + 1)
:log info ("ForestVPN: pulling the image into the RAM root, attempt " . $n)
:do { /container/repull $c } on-error={}
:local w 0
:local s ""
:while (($w < 60) && ($s = "")) do={
:set w ($w + 1)
:delay 5s
:local st [:pick [/container/print as-value where name="fvpn"] 0]
:if (($st->"stopped") = true) do={ :set s "stopped" }
:if (($st->"download/extract failed") = true) do={ :set s "failed" }
}
:if ($s = "stopped") do={
:do { /container/start $c } on-error={}
:delay 30s
} else={
:log warning "ForestVPN: image pull failed, retrying in 60s"
:delay 60s
}
}
:if (![/container/get $c running]) do={
:log error "ForestVPN: the container is still not running after 60 pulls; see /container/print"
}
}
}
/system/scheduler/add name=forestvpn-repull start-time=startup comment="forestvpn" \
on-event="/system/script/run forestvpn-repull"
}
/container/start [find name="fvpn"]Every line is load-bearing, `cmd=` most of all.
Without `--agent` the entrypoint runs the raw tunneller instead of the account-managed one, looks for a `.conf` nobody wrote, and the container exits with `fvpn0 never appeared`. The mount is the other one: the image is `scratch`, so nothing survives a restart except what is mounted, and losing the machine key's mount means the router comes back as a brand-new device that has silently dropped off your account.
It prints to /log, and the router serves a status page on http://172.31.254.2/.
Open ForestVPN on a phone where you are signed in and enter the code. The link is made from the app, not from the router.
On the status page, tap a location.
Not on MikroTik yet: choosing sites or services per destination from inside ForestVPN. RouterOS makes the routing decision.
The image is suquant/fvpn:0.3.17. The tag is pinned and published on Docker Hub; the install block uses exactly this tag.
Stop and remove the container with /container/stop and /container/remove, then delete its veth and address. The device stays on your account until you remove it in the app.