role: user

type: text

text:
<user_query>
сходи на роутер komehaht@192.168.50.1
у меня там есть 2 клиента вайргард (ноутбук и телефон). А на самом роутере кроме сервера ВГ еще есть jffs скрипты для обхода блокировок через польский ВПС. Так вот то с ноута, то с телефона, то дома просто с вайфая часто перестает работать обход. Почини или если не получится, можно полностью переделать архиектуру. Моя задача - обход блокировок. Подробности каких именно - в скриптах
</user_query>


role: assistant

text: Понял задачу: нужно восстановить стабильный обход блокировок через ваш роутер (WireGuard + jffs-скрипты через польский VPS). Сначала проверю, могу ли я вообще подключиться к роутеру по SSH с текущей среды, чтобы выполнить диагностику напрямую.

Shell

command: ssh -o BatchMode=yes -o ConnectTimeout=6 "komehaht@192.168.50.1" "uname -a"

description: Check SSH access to router non-interactively


text: Подключение по ключу не прошло. Сейчас посмотрю, есть ли у вас в SSH-конфиге отдельный хост/ключ для `192.168.50.1`, чтобы зайти корректно и уже чинить на месте.


text: I'm noticing that there are no config keys existing, which is a bit confusing. Maybe I could try using the key id_ed25519 or perhaps the server is expecting a different key, like id_ed25519_devops_ansible. It feels like I'm going back and forth trying to figure this out! I just want to make sure I’m using the right identity to solve this. Let's see if that works!

command: ssh -i "/home/ruslanpi/.ssh/id_ed25519_devops_ansible" -o BatchMode=yes -o ConnectTimeout=6 "komehaht@192.168.50.1" "uname -a"

description: Try router SSH with alternate private key


command: ssh -i "/home/ruslanpi/.ssh/id_ed25519" -o BatchMode=yes -o ConnectTimeout=6 "komehaht@192.168.50.1" "uname -a"

description: Try router SSH with default ed25519 key


text: Прямого входа пока нет (оба найденных ключа не подходят). Чтобы всё равно продвинуться, посмотрю в вашем репозитории текущие скрипты/конфиги обхода и подготовлю рабочую схему с фиксом или полной переделкой.


text:
**Testing SSH authentication methods**

I need to attempt SSH with a password, but it seems complex. I’m considering using the command `ssh -o PreferredAuthentications=password ...` to check if it might work, though the server might be set to accept only public keys. I could try keyboard-interactive authentication as well. I’ll quickly test it with `NumberOfPasswordPrompts=0` to see the offered methods. Using the `-vv` flag might reveal the authentication methods available, so I’ll summarize my findings after that.

command: ssh -vv -o ConnectTimeout=6 -o BatchMode=yes "komehaht@192.168.50.1" "true"

description: Inspect SSH authentication methods with verbose logs


text:
Не смог зайти на роутер напрямую: SSH отвечает, но принимает **только `publickey`**, а доступных у меня ключей не принимает (`Permission denied (publickey)`).

Чтобы я реально “починил” удаленно, нужен один из вариантов доступа:
- добавить мой публичный ключ в `/root/.ssh/authorized_keys` на роутере;
- или дать корректный ключ/пользователя, который уже имеет доступ.

Пока без входа на роутер, даю рабочий план, который обычно убирает такие “то работает, то нет” у схемы `WG server + jffs bypass`.

## Что почти наверняка ломается

- правила в `iptables/ip rule` теряются после `WAN renew/reconnect`;
- jffs-скрипты стартуют в неверном порядке (`nat-start`/`firewall-start`/`wan-event`);
- DNS и маршрутизация рассинхронизируются (домен резолвится не туда, ipset устаревает);
- один и тот же набор правил используется и для WG-клиентов (телефон/ноут), и для домашнего Wi‑Fi без разделения меток.

## Надежная архитектура (рекомендую)

- `wg0` — ваш текущий **сервер** на роутере (для телефона/ноута).
- `wg1` — отдельный **клиентский** туннель роутера к польскому VPS.
- Обход только через Policy Based Routing:
  - маркировать трафик к “блокируемым” назначениям (`fwmark 0x66`);
  - `ip rule add fwmark 0x66 table 166`;
  - `table 166` default route через `wg1`.
- DNS:
  - `dnsmasq + ipset/nftset` для доменов блокировок;
  - запретить утечки DNS в обход туннеля.
- Watchdog:
  - каждые 1-2 минуты проверка доступности через `wg1`;
  - при сбое: `wg-quick down/up wg1` + переустановка policy-правил.

## Что нужно от тебя сейчас (чтобы я сделал точный фикс)

Пришли выводы этих команд с роутера (через SSH/веб-терминал):

```bash
uname -a
nvram get firmware_version
wg show
ip -4 rule
ip -4 route show table main
ip -4 route show table 166
iptables-save | sed -n '1,220p'
ls -l /jffs/scripts
sed -n '1,220p' /jffs/scripts/services-start
sed -n '1,220p' /jffs/scripts/firewall-start
sed -n '1,220p' /jffs/scripts/nat-start
sed -n '1,220p' /jffs/scripts/wan-event
cru l
```

После этого я дам готовые финальные версии скриптов (`services-start`, `firewall-start`, watchdog), чтобы обход стабильно работал и у телефона, и у ноута, и у устройств в домашнем Wi‑Fi.


text:
<user_query>
@/home/ruslanpi/.cursor/projects/home-ruslanpi-Documents-repos-lmru-devops-argocd/terminals/2.txt:17-22 ну вот я в терминале вошел
</user_query>


text:
Отлично, вижу — вход на роутер есть (`komehdaht`). Сейчас подключусь тем же пользователем и сразу соберу диагностику WG/маршрутизации/`jffs`-скриптов, затем внесу фикс на месте.

**Connecting via SSH**

I need to connect to the router through the terminal. It looks like I should use the SSH command with the corrected username, komehdaht. I’m considering using an interactive mode, and since the user might be logged in with passwordless access, that could simplify things. I’ll check the requirements and ensure the correct terminals folder is in place before running any long commands. Let’s go ahead and execute the SSH command with the right user!

command: ssh -o ConnectTimeout=8 "komehdaht@192.168.50.1" "uname -a && nvram get firmware_version && echo '---WG---' && wg show && echo '---RULES---' && ip -4 rule && echo '---ROUTE MAIN---' && ip -4 route show table main && echo '---ROUTE 166---' && ip -4 route show table 166 && echo '---JFFS---' && ls -l /jffs/scripts && echo '---SERVICES---' && sed -n '1,220p' /jffs/scripts/services-start && echo '---FIREWALL---' && sed -n '1,260p' /jffs/scripts/firewall-start && echo '---NAT---' && sed -n '1,260p' /jffs/scripts/nat-start && echo '---WAN-EVENT---' && sed -n '1,260p' /jffs/scripts/wan-event && echo '---CRU---' && cru l"

description: Collect router diagnostics for wireguard and scripts


text:
Нашёл ключевую проблему: правила роутинга раздуты тысячами `ip rule to <IP>` и нет `nat-start`, из-за чего после событий WAN схема становится хрупкой. Сейчас дочитаю ваши `wg_*`/`update_ai_dns` скрипты и соберу устойчивый вариант с самовосстановлением.

**Fixing scripts and configurations**

I need to continue gathering the rest of the truncated scripts, particularly for iptables and watcher scripts. It seems the command failed because "sed nat-start" is missing, which caused an exit error, possibly before the wan-event/cru triggered. I need to fetch specific files and key script content. Using SSH with cat and sed might work, plus I might need to fix the architecture directly on the router. I'll also check the wg scripts and ensure the existence of cru and wan-event.

command: ssh "komehdaht@192.168.50.1" "echo '---CRU---' && cru l && echo '---WAN-EVENT EXISTS---' && ls -l /jffs/scripts/wan-event || true && echo '---DNSMASQ.POSTCONF---' && sed -n '1,220p' /jffs/scripts/dnsmasq.postconf && echo '---WG_DNSMASQ_WATCHER---' && sed -n '1,320p' /jffs/scripts/wg_dnsmasq_watcher.sh && echo '---WG_DOMAIN_ROUTE---' && sed -n '1,320p' /jffs/scripts/wg_domain_route.sh && echo '---UPDATE_AI_DNS---' && sed -n '1,320p' /jffs/scripts/update_ai_dns.sh && echo '---RTRMON HEAD---' && sed -n '1,180p' /jffs/scripts/rtrmon.sh"

description: Read router scripts for routing and DNS logic


text:
**Evaluating network architecture**

It seems the current setup doesn’t clear stale entries, which might cause some bloat. The larger issue is that after a WAN reconnect, the route table for wgc1 may not restore properly, leading to potential route breaks. A more robust architecture could involve using ipset and mangle marking with a simpler rule table instead of having thousands of destination rules. I also want to preserve existing rules for geohide proxies, and I should look into simplifying the scripts for better efficiency.
**Updating iptables rules**

I have a series of iptables commands for managing marks and routing. First, I’ll delete and create the necessary chains for WG_AI_BYPASS and set up the mark. I also want to handle router-originated traffic by adding OUTPUT chain rules. Regarding restoring marks for replies from connmark, I think it may not be needed for destination-based rules. To clean up, I’ll remove old per-IP rules in the range of 17100-18999 and update the wg_dnsmasq_watcher to prevent adding those rules again. Repurposing could also help with health checks and route resets.
**Simplifying DNS management**

I think it might be best to remove the complexity of the watcher and use update_ai_dns for maintaining dnsmasq ipset entries instead. With dnsmasq updating ipset dynamically, I won’t need a watcher. I need to ensure that the ipset exists since dnsmasq can create it automatically if the kernel supports it, but pre-creation might be necessary. At firewall start, I’ll set up the necessary ipset. I also want to replace the cron job with scheduled updates to restart firewall rules and add a new script to check the health of the wgc1 peer handshakes.
**Managing service checks**

I’m considering using `service restart_wgc1` on Merlin control, but maybe it's safer to run `wg show wgc1` to parse the latest handshake duration. If I see something like "(sec:86)", that's easy to parse. I also need to ensure that the ip rule with fwmark exists and that the iptables chain is present; if they’re missing, I'll call firewall-start. Optionally, I might flush the route cache. There’s already a `check_wgc_ep` cron job running every minute, which is good, and I could introduce a health script every 2 minutes. I’ll need to create this remotely using SSH while backing up originals first. Plus, I should inspect the `/jffs/configs/dnsmasq.conf.add` file to ensure everything looks right.

command: ssh "komehdaht@192.168.50.1" "echo '---DNSMASQ.CONF.ADD---' && sed -n '1,260p' /jffs/configs/dnsmasq.conf.add"

description: Inspect dnsmasq additional config content


command: ssh "komehdaht@192.168.50.1" "echo '---SERVICE-EVENT---' && sed -n '1,220p' /jffs/scripts/service-event && echo '---OPENVPN-EVENT---' && sed -n '1,220p' /jffs/scripts/openvpn-event && echo '---POST-MOUNT---' && sed -n '1,220p' /jffs/scripts/post-mount"

description: Inspect existing event scripts for integration


text:
Сейчас внесу правки прямо на роутере: уберу нестабильный механизм с тысячами `ip rule to IP`, переведу обход на `ipset + fwmark + один ip rule`, добавлю health-check и обработку WAN-событий, чтобы схема сама восстанавливалась.

**Crafting firewall script**

I’m thinking about creating a wan-event script in Merlin to reapply the firewall settings during connected or disconnected events. It sounds simple enough, but I’ll need to perform edits on the router using SSH, which means utilizing something like `cat <<'EOF'` and managing permissions with `chmod`. Including backups with something like `cp -a` is also smart. I suppose I should begin drafting that new `firewall-start` script now!
**Setting up routing and rules**

I’m defining a WireGuard table and setting up rules for it. I need to create an IP set and clean up old IP rules, possibly removing outdated entries in the preference range of 17101-18999. I also want to maintain specific preferences like 17050 and 17051.

For peers, I'll ensure the routing is correct for 10.6.0.2/3, adjusting preferences accordingly. I'll clean any existing `fwmark` rules before adding new ones while being careful to use `rg` instead of `grep` for file operations.
**Setting up iptables rules**

I’m working on iptables commands for the router. I want to create a new chain called `WG_AI_BYPASS_RULES` and set that up. I’ll first need to ensure the chain is created and then flush it.

I plan to add rules that match the `WG_AI_BYPASS` set and mark traffic with `0x66`. I also need to manage hooks in the `PREROUTING` and `OUTPUT` chains to avoid duplicate entries. Lastly, I’m considering restoring marks in `PREROUTING` before the chain, just to be thorough!
**Updating firewall and scripts**

I’m realizing that I don't need to mark the destination for locally generated connections. It's important, though, to maintain my geohide IP rules with preferences 17050 and 17051 after deleting any identical ones. I plan to loop through and delete those before adding them back again.

I need to call the `x3mRouting` firewall script, ensuring it’s appropriately set up. After that, I’ll ensure scheduled tasks for updating DNS and health checks are in place. The new `wg_bypass_health.sh` script should check if the interface exists and looks for any stale transmissions before deciding on a restart.
**Updating handshake and firewall scripts**

I’m parsing the output from `wg show wgc1 latest-handshakes`, which simplifies things. I want to compute the maximum age of the handshakes using `awk` and handle cases where there are no peers effectively. Restarting the service might be necessary if the handshake is stale for too long—potentially using `service restart_wgc1`.

My script needs to check handshake age and ensure firewall rules are set correctly, alongside ensuring `dnsmasq` has the proper IP set markers. I’m thinking about modifying the update script to avoid duplicate markings as well, and I’ll ensure to check the syntax while applying changes through SSH.

command:
ssh "komehdaht@192.168.50.1" "set -e; TS=$(date +%Y%m%d%H%M%S); cp -a /jffs/scripts/firewall-start /jffs/scripts/firewall-start.bak.$TS; cp -a /jffs/scripts/services-start /jffs/scripts/services-start.bak.$TS; cp -a /jffs/scripts/update_ai_dns.sh /jffs/scripts/update_ai_dns.sh.bak.$TS; cat > /jffs/scripts/firewall-start << 'EOF'
#!/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH

WG_TABLE="wgc1"
WG_SET="WG_AI_BYPASS"
WG_MARK_HEX="0x66"

# Keep x3mRouting integration if installed
if [ -x /jffs/addons/x3mRouting/x3mRouting_firewall_start.sh ]; then
  sh /jffs/addons/x3mRouting/x3mRouting_firewall_start.sh
fi

# WG server peers (phone/laptop) must stay reachable directly
ip route replace 10.6.0.2 dev wgs1
ip route replace 10.6.0.3 dev wgs1
while ip rule del pref 90 2>/dev/null; do :; done
while ip rule del pref 91 2>/dev/null; do :; done
ip rule add pref 90 from all to 10.6.0.2 lookup main 2>/dev/null || true
ip rule add pref 91 from all to 10.6.0.3 lookup main 2>/dev/null || true

# GeoHideDNS SNI-proxy IPs must go through WG
while ip rule del pref 17050 2>/dev/null; do :; done
while ip rule del pref 17051 2>/dev/null; do :; done
ip rule add pref 17050 to 45.155.204.190 lookup "$WG_TABLE" 2>/dev/null || true
ip rule add pref 17051 to 95.182.120.241 lookup "$WG_TABLE" 2>/dev/null || true

# Remove legacy per-IP rule spam in our old pref range
while ip rule del pref 17100 2>/dev/null; do :; done
ip rule show | awk '
  {
    pref=$1
    sub(":", "", pref)
    if (pref+0 >= 17101 && pref+0 <= 18999) print pref
  }
' | while read -r pref; do
  [ -n "$pref" ] && ip rule del pref "$pref" 2>/dev/null || true
done

# Single stable policy rule: marked traffic -> WG table
ip rule add pref 17100 fwmark "$WG_MARK_HEX" lookup "$WG_TABLE" 2>/dev/null || true

# Ensure destination ipset exists for dnsmasq ipset=/... rules
ipset create "$WG_SET" hash:ip family inet hashsize 4096 maxelem 262144 -exist

# Build mangle chain once; mark packets whose dst in WG_AI_BYPASS
iptables -t mangle -N WG_AI_BYPASS_RULES 2>/dev/null || true
iptables -t mangle -F WG_AI_BYPASS_RULES
iptables -t mangle -A WG_AI_BYPASS_RULES -m set --match-set "$WG_SET" dst -j MARK --set-mark "$WG_MARK_HEX"
iptables -t mangle -A WG_AI_BYPASS_RULES -m mark --mark "$WG_MARK_HEX" -j CONNMARK --save-mark

while iptables -t mangle -D PREROUTING -j WG_AI_BYPASS_RULES 2>/dev/null; do :; done
while iptables -t mangle -D OUTPUT -j WG_AI_BYPASS_RULES 2>/dev/null; do :; done
iptables -t mangle -A PREROUTING -j WG_AI_BYPASS_RULES
iptables -t mangle -A OUTPUT -j WG_AI_BYPASS_RULES

ip route flush cache
EOF
chmod 755 /jffs/scripts/firewall-start; cat > /jffs/scripts/services-start << 'EOF'
#!/bin/sh

# Keep only required jobs
cru d UpdateAIDNS 2>/dev/null
cru d WGReseed 2>/dev/null
cru d WGBypassHealth 2>/dev/null

# AI DNS list updater (daily at 04:00)
cru a UpdateAIDNS "0 4 * * * /jffs/scripts/update_ai_dns.sh"

# Periodic self-heal for bypass policy/tunnel
cru a WGBypassHealth "*/5 * * * * /jffs/scripts/wg_bypass_health.sh"

# Run once at startup in background
/jffs/scripts/update_ai_dns.sh >/dev/null 2>&1 &
/jffs/scripts/wg_bypass_health.sh >/dev/null 2>&1 &
EOF
chmod 755 /jffs/scripts/services-start; cat > /jffs/scripts/wg_bypass_health.sh << 'EOF'
#!/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
TAG="WG_BYPASS_HEALTH"
MAX_STALE_SEC=300

log() {
  logger -t "$TAG" "$*"
}

# Ensure routing/marking rules are present
[ -x /jffs/scripts/firewall-start ] && /jffs/scripts/firewall-start >/dev/null 2>&1

# Parse latest handshake age from Merlin wg output: "(sec:NN)"
age=$(wg show wgc1 2>/dev/null | awk -F'sec:' '/latest handshake:/ {split($2, a, ")"); print a[1]; exit}')

if [ -n "$age" ]; then
  case "$age" in
    ''|*[!0-9]*) age="" ;;
  esac
fi

if [ -n "$age" ] && [ "$age" -gt "$MAX_STALE_SEC" ]; then
  log "wgc1 stale handshake ${age}s, restarting client"
  service restart_wgc1 >/dev/null 2>&1
  sleep 8
  [ -x /jffs/scripts/firewall-start ] && /jffs/scripts/firewall-start >/dev/null 2>&1
fi
EOF
chmod 755 /jffs/scripts/wg_bypass_health.sh; cat > /jffs/scripts/wan-event << 'EOF'
#!/bin/sh

# Re-apply bypass rules after WAN state changes
if [ "$2" = "connected" ] || [ "$2" = "up" ] || [ "$2" = "start" ]; then
  /jffs/scripts/wg_bypass_health.sh >/dev/null 2>&1 &
fi
EOF
chmod 755 /jffs/scripts/wan-event; perl -0777 -i -pe 's/if \[ -x \/jffs\/scripts\/wg_dnsmasq_watcher\.sh \]; then\n  \/jffs\/scripts\/wg_dnsmasq_watcher\.sh reseed >\/dev\/null 2>&1\nfi/if [ -x \/jffs\/scripts\/firewall-start ]; then\n  \/jffs\/scripts\/firewall-start >\/dev\/null 2>\&1\nfi/s' /jffs/scripts/update_ai_dns.sh; sh -n /jffs/scripts/firewall-start; sh -n /jffs/scripts/services-start; sh -n /jffs/scripts/wg_bypass_health.sh; sh -n /jffs/scripts/wan-event"

description: Install stable WG bypass scripts and health checks


command:
setopt nonomatch; ssh "komehdaht@192.168.50.1" "set -e; TS=$(date +%Y%m%d%H%M%S); cp -a /jffs/scripts/firewall-start /jffs/scripts/firewall-start.bak.$TS; cp -a /jffs/scripts/services-start /jffs/scripts/services-start.bak.$TS; cp -a /jffs/scripts/update_ai_dns.sh /jffs/scripts/update_ai_dns.sh.bak.$TS; cat > /jffs/scripts/firewall-start << 'EOF'
#!/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH

WG_TABLE=\"wgc1\"
WG_SET=\"WG_AI_BYPASS\"
WG_MARK_HEX=\"0x66\"

# Keep x3mRouting integration if installed
if [ -x /jffs/addons/x3mRouting/x3mRouting_firewall_start.sh ]; then
  sh /jffs/addons/x3mRouting/x3mRouting_firewall_start.sh
fi

# WG server peers (phone/laptop) must stay reachable directly
ip route replace 10.6.0.2 dev wgs1
ip route replace 10.6.0.3 dev wgs1
while ip rule del pref 90 2>/dev/null; do :; done
while ip rule del pref 91 2>/dev/null; do :; done
ip rule add pref 90 from all to 10.6.0.2 lookup main 2>/dev/null || true
ip rule add pref 91 from all to 10.6.0.3 lookup main 2>/dev/null || true

# GeoHideDNS SNI-proxy IPs must go through WG
while ip rule del pref 17050 2>/dev/null; do :; done
while ip rule del pref 17051 2>/dev/null; do :; done
ip rule add pref 17050 to 45.155.204.190 lookup \"$WG_TABLE\" 2>/dev/null || true
ip rule add pref 17051 to 95.182.120.241 lookup \"$WG_TABLE\" 2>/dev/null || true

# Remove legacy per-IP rule spam in our old pref range
while ip rule del pref 17100 2>/dev/null; do :; done
ip rule show | awk '
  {
    pref=$1
    sub(":", "", pref)
    if (pref+0 >= 17101 && pref+0 <= 18999) print pref
  }
' | while read -r pref; do
  [ -n \"$pref\" ] && ip rule del pref \"$pref\" 2>/dev/null || true
done

# Single stable policy rule: marked traffic -> WG table
ip rule add pref 17100 fwmark \"$WG_MARK_HEX\" lookup \"$WG_TABLE\" 2>/dev/null || true

# Ensure destination ipset exists for dnsmasq ipset=/... rules
ipset create \"$WG_SET\" hash:ip family inet hashsize 4096 maxelem 262144 -exist

# Build mangle chain once; mark packets whose dst in WG_AI_BYPASS
iptables -t mangle -N WG_AI_BYPASS_RULES 2>/dev/null || true
iptables -t mangle -F WG_AI_BYPASS_RULES
iptables -t mangle -A WG_AI_BYPASS_RULES -m set --match-set \"$WG_SET\" dst -j MARK --set-mark \"$WG_MARK_HEX\"
iptables -t mangle -A WG_AI_BYPASS_RULES -m mark --mark \"$WG_MARK_HEX\" -j CONNMARK --save-mark

while iptables -t mangle -D PREROUTING -j WG_AI_BYPASS_RULES 2>/dev/null; do :; done
while iptables -t mangle -D OUTPUT -j WG_AI_BYPASS_RULES 2>/dev/null; do :; done
iptables -t mangle -A PREROUTING -j WG_AI_BYPASS_RULES
iptables -t mangle -A OUTPUT -j WG_AI_BYPASS_RULES

ip route flush cache
EOF
chmod 755 /jffs/scripts/firewall-start; cat > /jffs/scripts/services-start << 'EOF'
#!/bin/sh

# Keep only required jobs
cru d UpdateAIDNS 2>/dev/null
cru d WGReseed 2>/dev/null
cru d WGBypassHealth 2>/dev/null

# AI DNS list updater (daily at 04:00)
cru a UpdateAIDNS \"0 4 * * * /jffs/scripts/update_ai_dns.sh\"

# Periodic self-heal for bypass policy/tunnel
cru a WGBypassHealth \"*/5 * * * * /jffs/scripts/wg_bypass_health.sh\"

# Run once at startup in background
/jffs/scripts/update_ai_dns.sh >/dev/null 2>&1 &
/jffs/scripts/wg_bypass_health.sh >/dev/null 2>&1 &
EOF
chmod 755 /jffs/scripts/services-start; cat > /jffs/scripts/wg_bypass_health.sh << 'EOF'
#!/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
TAG=\"WG_BYPASS_HEALTH\"
MAX_STALE_SEC=300

log() {
  logger -t \"$TAG\" \"$*\"
}

# Ensure routing/marking rules are present
[ -x /jffs/scripts/firewall-start ] && /jffs/scripts/firewall-start >/dev/null 2>&1

# Parse latest handshake age from Merlin wg output: \"(sec:NN)\"
age=$(wg show wgc1 2>/dev/null | awk -F'sec:' '/latest handshake:/ {split($2, a, \")\"); print a[1]; exit}')

if [ -n \"$age\" ]; then
  case \"$age\" in
    ''|*[!0-9]*) age=\"\" ;;
  esac
fi

if [ -n \"$age\" ] && [ \"$age\" -gt \"$MAX_STALE_SEC\" ]; then
  log \"wgc1 stale handshake ${age}s, restarting client\"
  service restart_wgc1 >/dev/null 2>&1
  sleep 8
  [ -x /jffs/scripts/firewall-start ] && /jffs/scripts/firewall-start >/dev/null 2>&1
fi
EOF
chmod 755 /jffs/scripts/wg_bypass_health.sh; cat > /jffs/scripts/wan-event << 'EOF'
#!/bin/sh

# Re-apply bypass rules after WAN state changes
if [ \"$2\" = \"connected\" ] || [ \"$2\" = \"up\" ] || [ \"$2\" = \"start\" ]; then
  /jffs/scripts/wg_bypass_health.sh >/dev/null 2>&1 &
fi
EOF
chmod 755 /jffs/scripts/wan-event; perl -0777 -i -pe 's/if \[ -x \/jffs\/scripts\/wg_dnsmasq_watcher\.sh \]; then\n  \/jffs\/scripts\/wg_dnsmasq_watcher\.sh reseed >\/dev\/null 2>&1\nfi/if [ -x \/jffs\/scripts\/firewall-start ]; then\n  \/jffs\/scripts\/firewall-start >\/dev\/null 2>\&1\nfi/s' /jffs/scripts/update_ai_dns.sh; sh -n /jffs/scripts/firewall-start; sh -n /jffs/scripts/services-start; sh -n /jffs/scripts/wg_bypass_health.sh; sh -n /jffs/scripts/wan-event"

description: Install rewritten bypass scripts with proper escaping


text:
**Inspecting shell commands**

I’m considering using a command like `cat <<'EOF'` piped with `ssh` to redirect output into a file. To minimize quoting errors, I think it's smart to handle each file step by step. I also want to check the current scripts, as there might be some issues. It's important to inspect those files quickly to avoid further complications with the setup. Let's tackle this systematically!

command: ssh "komehdaht@192.168.50.1" "echo '---firewall-start---'; sed -n '1,220p' /jffs/scripts/firewall-start; echo '---services-start---'; sed -n '1,220p' /jffs/scripts/services-start; echo '---wg_bypass_health---'; sed -n '1,220p' /jffs/scripts/wg_bypass_health.sh 2>/dev/null || true; echo '---wan-event---'; sed -n '1,120p' /jffs/scripts/wan-event 2>/dev/null || true"

description: Inspect script states after failed update


text: Поймал проблему с экранированием при записи скриптов. Сейчас перезапишу файлы по одному безопасным способом, затем проверю синтаксис и сразу применю без перезагрузки.

command:
setopt nonomatch; ssh "komehdaht@192.168.50.1" 'TS=$(date +%Y%m%d%H%M%S); cp -a /jffs/scripts/firewall-start /jffs/scripts/firewall-start.bak.$TS; cat > /jffs/scripts/firewall-start <<'"'"'EOF'"'"'
#!/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
WG_TABLE="wgc1"
WG_SET="WG_AI_BYPASS"
WG_MARK_HEX="0x66"

# Keep x3mRouting integration if installed
if [ -x /jffs/addons/x3mRouting/x3mRouting_firewall_start.sh ]; then
  sh /jffs/addons/x3mRouting/x3mRouting_firewall_start.sh
fi

# WG server peers (phone/laptop) must stay reachable directly
ip route replace 10.6.0.2 dev wgs1
ip route replace 10.6.0.3 dev wgs1
while ip rule del pref 90 2>/dev/null; do :; done
while ip rule del pref 91 2>/dev/null; do :; done
ip rule add pref 90 from all to 10.6.0.2 lookup main 2>/dev/null || true
ip rule add pref 91 from all to 10.6.0.3 lookup main 2>/dev/null || true

# GeoHideDNS SNI-proxy IPs must go through WG
while ip rule del pref 17050 2>/dev/null; do :; done
while ip rule del pref 17051 2>/dev/null; do :; done
ip rule add pref 17050 to 45.155.204.190 lookup "$WG_TABLE" 2>/dev/null || true
ip rule add pref 17051 to 95.182.120.241 lookup "$WG_TABLE" 2>/dev/null || true

# Remove legacy per-IP rule spam in old pref range
while ip rule del pref 17100 2>/dev/null; do :; done
ip rule show | awk '
  {
    pref=$1
    sub(":", "", pref)
    if (pref+0 >= 17101 && pref+0 <= 18999) print pref
  }
' | while read -r pref; do
  [ -n "$pref" ] && ip rule del pref "$pref" 2>/dev/null || true
done

# Single stable policy rule: marked traffic -> WG table
ip rule add pref 17100 fwmark "$WG_MARK_HEX" lookup "$WG_TABLE" 2>/dev/null || true

# Ensure destination ipset exists for dnsmasq ipset=/... rules
ipset create "$WG_SET" hash:ip family inet hashsize 4096 maxelem 262144 -exist

# Build mangle chain once; mark packets whose dst in WG_AI_BYPASS
iptables -t mangle -N WG_AI_BYPASS_RULES 2>/dev/null || true
iptables -t mangle -F WG_AI_BYPASS_RULES
iptables -t mangle -A WG_AI_BYPASS_RULES -m set --match-set "$WG_SET" dst -j MARK --set-mark "$WG_MARK_HEX"
iptables -t mangle -A WG_AI_BYPASS_RULES -m mark --mark "$WG_MARK_HEX" -j CONNMARK --save-mark

while iptables -t mangle -D PREROUTING -j WG_AI_BYPASS_RULES 2>/dev/null; do :; done
while iptables -t mangle -D OUTPUT -j WG_AI_BYPASS_RULES 2>/dev/null; do :; done
iptables -t mangle -A PREROUTING -j WG_AI_BYPASS_RULES
iptables -t mangle -A OUTPUT -j WG_AI_BYPASS_RULES

ip route flush cache
EOF
chmod 755 /jffs/scripts/firewall-start; sh -n /jffs/scripts/firewall-start'

description: Write new firewall-start script safely


text:
**Planning file transfers**

I'm trying to figure out the easiest way to handle quotes and escape characters, especially with those pesky dollar signs and backticks. It seems like using double quotes on the outside might help. I could potentially use `scp` to transfer files created with a tool like ApplyPatch. It sounds manageable if I create temporary script files in a workspace directory. I'll prioritize ensuring the parent directory exists before I make those files. It seems like a solid plan!

ApplyPatch

input:
*** Begin Patch
*** Add File: /home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_firewall-start.sh
+#!/bin/sh
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
+WG_TABLE="wgc1"
+WG_SET="WG_AI_BYPASS"
+WG_MARK_HEX="0x66"
+
+# Keep x3mRouting integration if installed
+if [ -x /jffs/addons/x3mRouting/x3mRouting_firewall_start.sh ]; then
+  sh /jffs/addons/x3mRouting/x3mRouting_firewall_start.sh
+fi
+
+# WG server peers (phone/laptop) must stay reachable directly
+ip route replace 10.6.0.2 dev wgs1
+ip route replace 10.6.0.3 dev wgs1
+while ip rule del pref 90 2>/dev/null; do :; done
+while ip rule del pref 91 2>/dev/null; do :; done
+ip rule add pref 90 from all to 10.6.0.2 lookup main 2>/dev/null || true
+ip rule add pref 91 from all to 10.6.0.3 lookup main 2>/dev/null || true
+
+# GeoHideDNS SNI-proxy IPs must go through WG
+while ip rule del pref 17050 2>/dev/null; do :; done
+while ip rule del pref 17051 2>/dev/null; do :; done
+ip rule add pref 17050 to 45.155.204.190 lookup "$WG_TABLE" 2>/dev/null || true
+ip rule add pref 17051 to 95.182.120.241 lookup "$WG_TABLE" 2>/dev/null || true
+
+# Remove legacy per-IP rule spam in old pref range
+while ip rule del pref 17100 2>/dev/null; do :; done
+ip rule show | awk "
+  {
+    pref=\$1
+    sub(\":\", \"\", pref)
+    if (pref+0 >= 17101 && pref+0 <= 18999) print pref
+  }
+" | while read -r pref; do
+  [ -n "$pref" ] && ip rule del pref "$pref" 2>/dev/null || true
+done
+
+# Single stable policy rule: marked traffic -> WG table
+ip rule add pref 17100 fwmark "$WG_MARK_HEX" lookup "$WG_TABLE" 2>/dev/null || true
+
+# Ensure destination ipset exists for dnsmasq ipset=/... rules
+ipset create "$WG_SET" hash:ip family inet hashsize 4096 maxelem 262144 -exist
+
+# Build mangle chain once; mark packets whose dst in WG_AI_BYPASS
+iptables -t mangle -N WG_AI_BYPASS_RULES 2>/dev/null || true
+iptables -t mangle -F WG_AI_BYPASS_RULES
+iptables -t mangle -A WG_AI_BYPASS_RULES -m set --match-set "$WG_SET" dst -j MARK --set-mark "$WG_MARK_HEX"
+iptables -t mangle -A WG_AI_BYPASS_RULES -m mark --mark "$WG_MARK_HEX" -j CONNMARK --save-mark
+
+while iptables -t mangle -D PREROUTING -j WG_AI_BYPASS_RULES 2>/dev/null; do :; done
+while iptables -t mangle -D OUTPUT -j WG_AI_BYPASS_RULES 2>/dev/null; do :; done
+iptables -t mangle -A PREROUTING -j WG_AI_BYPASS_RULES
+iptables -t mangle -A OUTPUT -j WG_AI_BYPASS_RULES
+
+ip route flush cache
*** End Patch


input:
*** Begin Patch
*** Add File: /home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_services-start.sh
+#!/bin/sh
+
+# Keep only required jobs
+cru d UpdateAIDNS 2>/dev/null
+cru d WGReseed 2>/dev/null
+cru d WGBypassHealth 2>/dev/null
+
+# AI DNS list updater (daily at 04:00)
+cru a UpdateAIDNS "0 4 * * * /jffs/scripts/update_ai_dns.sh"
+
+# Periodic self-heal for bypass policy/tunnel
+cru a WGBypassHealth "*/5 * * * * /jffs/scripts/wg_bypass_health.sh"
+
+# Run once at startup in background
+/jffs/scripts/update_ai_dns.sh >/dev/null 2>&1 &
+/jffs/scripts/wg_bypass_health.sh >/dev/null 2>&1 &
*** End Patch


input:
*** Begin Patch
*** Add File: /home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_wg_bypass_health.sh
+#!/bin/sh
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
+TAG="WG_BYPASS_HEALTH"
+MAX_STALE_SEC=300
+
+log() {
+  logger -t "$TAG" "$*"
+}
+
+# Ensure routing/marking rules are present
+[ -x /jffs/scripts/firewall-start ] && /jffs/scripts/firewall-start >/dev/null 2>&1
+
+# Parse latest handshake age from Merlin wg output: "(sec:NN)"
+age=$(wg show wgc1 2>/dev/null | awk -F"sec:" "/latest handshake:/ {split(\$2, a, \")\"); print a[1]; exit}")
+
+if [ -n "$age" ]; then
+  case "$age" in
+    ''|*[!0-9]*) age="" ;;
+  esac
+fi
+
+if [ -n "$age" ] && [ "$age" -gt "$MAX_STALE_SEC" ]; then
+  log "wgc1 stale handshake ${age}s, restarting client"
+  service restart_wgc1 >/dev/null 2>&1
+  sleep 8
+  [ -x /jffs/scripts/firewall-start ] && /jffs/scripts/firewall-start >/dev/null 2>&1
+fi
*** End Patch


input:
*** Begin Patch
*** Add File: /home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_wan-event.sh
+#!/bin/sh
+
+# Re-apply bypass rules after WAN state changes
+if [ "$2" = "connected" ] || [ "$2" = "up" ] || [ "$2" = "start" ]; then
+  /jffs/scripts/wg_bypass_health.sh >/dev/null 2>&1 &
+fi
*** End Patch


Shell

command: scp "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_firewall-start.sh" "komehdaht@192.168.50.1:/tmp/firewall-start.new" && scp "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_services-start.sh" "komehdaht@192.168.50.1:/tmp/services-start.new" && scp "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_wg_bypass_health.sh" "komehdaht@192.168.50.1:/tmp/wg_bypass_health.sh.new" && scp "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_wan-event.sh" "komehdaht@192.168.50.1:/tmp/wan-event.new"

description: Upload rewritten router scripts to temporary paths


text:
**Using SSH for file transfer**

I need to use SSH with a command substitution to send local file content to a remote server. It looks like I'll run `ssh user@host "cat > /tmp/file" < localfile`. That's pretty straightforward! I'll be using shell command with input redirection. The plan is to execute this in a sequential manner, doing it step by step to ensure everything goes smoothly. I've got a clear path forward here!

command: ssh "komehdaht@192.168.50.1" "cat > /tmp/firewall-start.new" < "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_firewall-start.sh" && ssh "komehdaht@192.168.50.1" "cat > /tmp/services-start.new" < "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_services-start.sh" && ssh "komehdaht@192.168.50.1" "cat > /tmp/wg_bypass_health.sh.new" < "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_wg_bypass_health.sh" && ssh "komehdaht@192.168.50.1" "cat > /tmp/wan-event.new" < "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_wan-event.sh"

description: Upload script files over SSH stdin redirection


text:
**Installing remote backups**

I’m considering installing remote backups and moving files. I think I need to check the syntax and possibly update the AI DNS patch. It might be easier to use an awk script with ed, but maybe a Perl one-liner on the router would work better. I’ll directly execute the remote command without local escaping since it might simplify things, using single quotes for the SSH command and double quotes inside. Let’s start with the install command first.

command: ssh "komehdaht@192.168.50.1" 'set -e; TS=$(date +%Y%m%d%H%M%S); cp -a /jffs/scripts/firewall-start /jffs/scripts/firewall-start.bak.$TS; cp -a /jffs/scripts/services-start /jffs/scripts/services-start.bak.$TS; cp -a /jffs/scripts/update_ai_dns.sh /jffs/scripts/update_ai_dns.sh.bak.$TS; [ -f /jffs/scripts/wan-event ] && cp -a /jffs/scripts/wan-event /jffs/scripts/wan-event.bak.$TS || true; mv /tmp/firewall-start.new /jffs/scripts/firewall-start; mv /tmp/services-start.new /jffs/scripts/services-start; mv /tmp/wg_bypass_health.sh.new /jffs/scripts/wg_bypass_health.sh; mv /tmp/wan-event.new /jffs/scripts/wan-event; chmod 755 /jffs/scripts/firewall-start /jffs/scripts/services-start /jffs/scripts/wg_bypass_health.sh /jffs/scripts/wan-event; sh -n /jffs/scripts/firewall-start; sh -n /jffs/scripts/services-start; sh -n /jffs/scripts/wg_bypass_health.sh; sh -n /jffs/scripts/wan-event'

description: Install uploaded scripts on router with backups


command: ssh "komehdaht@192.168.50.1" 'perl -0777 -i -pe "s@if \[ -x /jffs/scripts/wg_dnsmasq_watcher\\.sh \]; then\n  /jffs/scripts/wg_dnsmasq_watcher\\.sh reseed >/dev/null 2>&1\nfi@if [ -x /jffs/scripts/firewall-start ]; then\n  /jffs/scripts/firewall-start >/dev/null 2>&1\nfi@s" /jffs/scripts/update_ai_dns.sh; sh -n /jffs/scripts/update_ai_dns.sh'

description: Replace watcher reseed call with firewall refresh


command: ssh "komehdaht@192.168.50.1" "sed -n '1,500p' /jffs/scripts/update_ai_dns.sh" > "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_update_ai_dns.sh"

description: Download update_ai_dns script into local temp file


text: I need to apply a patch to a local temporary file, so my first step is to read the file. It seems straightforward, but I want to make sure I'm doing this right. I think checking the content of the file before applying the patch is essential to avoid any issues. Once I read the file, I'll be ready to go ahead with applying the patch. Let’s just take it one step at a time!


ApplyPatch

input:
*** Begin Patch
*** Update File: /home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_update_ai_dns.sh
@@
-if [ -x /jffs/scripts/wg_dnsmasq_watcher.sh ]; then
-  /jffs/scripts/wg_dnsmasq_watcher.sh reseed >/dev/null 2>&1
+if [ -x /jffs/scripts/firewall-start ]; then
+  /jffs/scripts/firewall-start >/dev/null 2>&1
 fi
*** End Patch


Shell

command: ssh "komehdaht@192.168.50.1" "cat > /tmp/update_ai_dns.sh.new" < "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_update_ai_dns.sh" && ssh "komehdaht@192.168.50.1" 'mv /tmp/update_ai_dns.sh.new /jffs/scripts/update_ai_dns.sh; chmod 755 /jffs/scripts/update_ai_dns.sh; sh -n /jffs/scripts/update_ai_dns.sh'

description: Upload and install patched update_ai_dns script


text:
**Running firewall checks**

I need to apply the runtime changes by starting the firewall and services. Should I also restart dnsmasq? I think updating the script might be necessary to ensure it triggers correctly. I’ll execute the commands and then inspect the status. The ip rule counts could still be in the thousands because our cleanup range might not delete everything. I’ll run the script and check the iptables chain as well as the cru.

command: ssh "komehdaht@192.168.50.1" '/jffs/scripts/firewall-start; /jffs/scripts/services-start; /jffs/scripts/wg_bypass_health.sh; echo "---CRU---"; cru l; echo "---RULES SHORT---"; ip -4 rule | sed -n "1,80p"; echo "---FWMARK RULE---"; ip -4 rule | awk "/fwmark 0x66/"; echo "---IPTABLES MANGLE---"; iptables -t mangle -S | sed -n "1,120p"; echo "---IPSET---"; ipset list WG_AI_BYPASS 2>/dev/null | sed -n "1,40p"; echo "---WG---"; wg show wgc1 | sed -n "1,80p"'

description: Apply scripts and verify policy routing and firewall state


text:
Выяснил важный технический факт: на вашем роутере нет поддержки `xt_set/ipset` для `iptables` (ошибка модуля), поэтому схема с `ipset + fwmark` здесь не взлетит. Переключаюсь на совместимый вариант: контролируемые `ip rule` по резолву доменов (без раздувания и с авто-рефрешем).

**Addressing iptables issues**

I’m encountering critical errors indicating that “ipset” has invalid arguments, and the “xt_set” isn't loaded, which means our current approach isn't working. I need to adapt the architecture, perhaps by using kernel modules or checking for compatibility issues. Modprobing “xt_set” might help if it’s not available. If “ipset” is not functioning properly, I’ll fall back to per-IP rules instead. I'll design a manageable script that refreshes entries regularly while cleaning up our broken iptables chain. Now, I need to fix this!
**Adapting firewall-start configurations**

I’m thinking of adjusting the firewall-start to avoid using “ipset” and instead focus on keeping the peer and geohide rules. I’ll clean up the old range and run the `/jffs/scripts/wg_domain_route.sh apply` script to generate new per-IP rules from the domain list, which will sort and apply them correctly. I need to remove the fwmark and mangle chain lines and ensure no duplicates remain in the PREROUTING/OUTPUT. It’s important to adjust how the script resolves and calls, maintaining the health check as well.
**Exploring dnsmasq and ipset options**

Since the ipset module isn't available, I'm wondering if the dnsmasq ipset entries might still work if the kernel has the ip_set loaded, even though iptables is missing the xt_set. The invalid ipset create suggests some issues, but using a workaround with per-IP rules via nslookup from the domains file seems solid. I can also handle the singlekey proxy domain through geohide DNS and fixed IP rules. Finally, I need to ensure the firewall-start script cleans up any existing duplicate rules to avoid issues.
**Scheduling and applying updates**

I'm thinking about the preference settings starting from 17100 and ensuring that the schedule includes a daily cron job for UpdateAIDNS, along with a cron for WGReseed every 30 minutes and WGBypassHealth every 5 minutes. I need to remember to run `wg_domain_route.sh apply` at startup to manage health. Instead of running firewall-start frequently, it might be better to just keep an eye on essential rule counts, checking for the existence of rule 17050 before deciding when to run firewall-start.
**Creating quick helper scripts**

I'm planning to create a quick helper script to check if there are any rules starting with 171; if none exist, I'll call `/jffs/scripts/firewall-start`. If the handshake is stale, I might want to restart and call firewall-start too. I need to adjust firewall-start to be quick, even if it resolves domains each run. While it may be okay to have huge entries every 30 minutes, I might want to re-evaluate the watchers and ensure we are re-adding the new WGReseed properly. It’s time to update the local temp scripts as well!

ApplyPatch

input:
*** Begin Patch
*** Update File: /home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_firewall-start.sh
@@
 PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
 WG_TABLE="wgc1"
-WG_SET="WG_AI_BYPASS"
-WG_MARK_HEX="0x66"
@@
-# Remove legacy per-IP rule spam in old pref range
-while ip rule del pref 17100 2>/dev/null; do :; done
-ip rule show | awk "
-  {
-    pref=\$1
-    sub(\":\", \"\", pref)
-    if (pref+0 >= 17101 && pref+0 <= 18999) print pref
-  }
-" | while read -r pref; do
-  [ -n "$pref" ] && ip rule del pref "$pref" 2>/dev/null || true
-done
-
-# Single stable policy rule: marked traffic -> WG table
-ip rule add pref 17100 fwmark "$WG_MARK_HEX" lookup "$WG_TABLE" 2>/dev/null || true
-
-# Ensure destination ipset exists for dnsmasq ipset=/... rules
-ipset create "$WG_SET" hash:ip family inet hashsize 4096 maxelem 262144 -exist
-
-# Build mangle chain once; mark packets whose dst in WG_AI_BYPASS
-iptables -t mangle -N WG_AI_BYPASS_RULES 2>/dev/null || true
-iptables -t mangle -F WG_AI_BYPASS_RULES
-iptables -t mangle -A WG_AI_BYPASS_RULES -m set --match-set "$WG_SET" dst -j MARK --set-mark "$WG_MARK_HEX"
-iptables -t mangle -A WG_AI_BYPASS_RULES -m mark --mark "$WG_MARK_HEX" -j CONNMARK --save-mark
-
+# Cleanup remnants of old fwmark chain (kernel lacks xt_set on this router)
 while iptables -t mangle -D PREROUTING -j WG_AI_BYPASS_RULES 2>/dev/null; do :; done
 while iptables -t mangle -D OUTPUT -j WG_AI_BYPASS_RULES 2>/dev/null; do :; done
-iptables -t mangle -A PREROUTING -j WG_AI_BYPASS_RULES
-iptables -t mangle -A OUTPUT -j WG_AI_BYPASS_RULES
+iptables -t mangle -F WG_AI_BYPASS_RULES 2>/dev/null || true
+iptables -t mangle -X WG_AI_BYPASS_RULES 2>/dev/null || true
+
+# Rebuild destination-based WG rules from current domain list
+[ -x /jffs/scripts/wg_domain_route.sh ] && /jffs/scripts/wg_domain_route.sh apply >/dev/null 2>&1 || true

 ip route flush cache
*** End Patch


input:
*** Begin Patch
*** Update File: /home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_services-start.sh
@@
 # AI DNS list updater (daily at 04:00)
 cru a UpdateAIDNS "0 4 * * * /jffs/scripts/update_ai_dns.sh"

-# Periodic self-heal for bypass policy/tunnel
-cru a WGBypassHealth "*/5 * * * * /jffs/scripts/wg_bypass_health.sh"
+# Periodic domain->IP route rebuild (catches DNS rotations)
+cru a WGReseed "*/30 * * * * /jffs/scripts/wg_domain_route.sh apply"
+
+# Periodic self-heal for tunnel/rules
+cru a WGBypassHealth "*/5 * * * * /jffs/scripts/wg_bypass_health.sh"

 # Run once at startup in background
 /jffs/scripts/update_ai_dns.sh >/dev/null 2>&1 &
+/jffs/scripts/wg_domain_route.sh apply >/dev/null 2>&1 &
 /jffs/scripts/wg_bypass_health.sh >/dev/null 2>&1 &
*** End Patch


input:
*** Begin Patch
*** Update File: /home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_wg_bypass_health.sh
@@
 log() {
   logger -t "$TAG" "$*"
 }

-# Ensure routing/marking rules are present
-[ -x /jffs/scripts/firewall-start ] && /jffs/scripts/firewall-start >/dev/null 2>&1
+# Ensure key bypass rules are present
+if ! ip rule show | awk '/lookup wgc1/ && $1 ~ /^17[0-9][0-9][0-9]:/ {found=1} END {exit !found}'; then
+  [ -x /jffs/scripts/firewall-start ] && /jffs/scripts/firewall-start >/dev/null 2>&1
+fi
@@
 if [ -n "$age" ] && [ "$age" -gt "$MAX_STALE_SEC" ]; then
   log "wgc1 stale handshake ${age}s, restarting client"
   service restart_wgc1 >/dev/null 2>&1
   sleep 8
   [ -x /jffs/scripts/firewall-start ] && /jffs/scripts/firewall-start >/dev/null 2>&1
 fi
*** End Patch


text:
**Updating firewalls and routing**

I’m thinking about updating the temp update_ai_dns file. It looks like maybe the firewall-start call should trigger the wg_domain_route apply, but I wonder if keeping firewall-start is necessary since it doesn’t seem to involve heavy iptables. It might be better to call wg_domain_route.sh directly to avoid resetting the others. I’ll make sure to patch everything neatly.

input:
*** Begin Patch
*** Update File: /home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_update_ai_dns.sh
@@
-if [ -x /jffs/scripts/firewall-start ]; then
-  /jffs/scripts/firewall-start >/dev/null 2>&1
+if [ -x /jffs/scripts/wg_domain_route.sh ]; then
+  /jffs/scripts/wg_domain_route.sh apply >/dev/null 2>&1
 fi
*** End Patch


Shell

command: ssh "komehdaht@192.168.50.1" "cat > /tmp/firewall-start.new" < "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_firewall-start.sh" && ssh "komehdaht@192.168.50.1" "cat > /tmp/services-start.new" < "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_services-start.sh" && ssh "komehdaht@192.168.50.1" "cat > /tmp/wg_bypass_health.sh.new" < "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_wg_bypass_health.sh" && ssh "komehdaht@192.168.50.1" "cat > /tmp/update_ai_dns.sh.new" < "/home/ruslanpi/Documents/repos/lmru--devops--argocd/.tmp_router_update_ai_dns.sh" && ssh "komehdaht@192.168.50.1" 'mv /tmp/firewall-start.new /jffs/scripts/firewall-start; mv /tmp/services-start.new /jffs/scripts/services-start; mv /tmp/wg_bypass_health.sh.new /jffs/scripts/wg_bypass_health.sh; mv /tmp/update_ai_dns.sh.new /jffs/scripts/update_ai_dns.sh; chmod 755 /jffs/scripts/firewall-start /jffs/scripts/services-start /jffs/scripts/wg_bypass_health.sh /jffs/scripts/update_ai_dns.sh; sh -n /jffs/scripts/firewall-start; sh -n /jffs/scripts/services-start; sh -n /jffs/scripts/wg_bypass_health.sh; sh -n /jffs/scripts/update_ai_dns.sh'

description: Upload revised non-ipset scripts and validate syntax


command: ssh "komehdaht@192.168.50.1" '/jffs/scripts/firewall-start; /jffs/scripts/services-start; /jffs/scripts/wg_bypass_health.sh; echo "---CRU---"; cru l; echo "---RULE COUNTS---"; ip -4 rule | awk "BEGIN{c=0} {pref=\$1; sub(\":\",\"\",pref); if (pref+0>=17100 && pref+0<=18999) c++} END{print c}"; echo "---RULE HEAD---"; ip -4 rule | sed -n "1,40p"; echo "---MANGLE CHAIN EXISTS---"; iptables -t mangle -S WG_AI_BYPASS_RULES 2>/dev/null || echo none; echo "---WG HANDSHAKE---"; wg show wgc1 | awk "/latest handshake:/"; echo "---TEST DOMAIN---"; nslookup chatgpt.com 127.0.0.1 2>/dev/null | awk "/^Address [0-9]+:/ {print \$3; exit}" > /tmp/test_ip.txt; TEST_IP=$(cat /tmp/test_ip.txt); echo ip=$TEST_IP; [ -n "$TEST_IP" ] && ip route get "$TEST_IP" || true'

description: Run revised scripts and validate policy routing behavior


text:
**Testing domain resolution**

I'm noticing that my domain parsing is off because the nslookup returned a resolver line. I need to improve my test to get the A record. When I tried `nslookup chatgpt.com 127.0.0.1 | awk '/^Address [0-9]+: / {print $3}'`, it seems to include 127.0.0.1, which might not really be what I want. I should use the information after "Name:". I'll test with a script similar to the existing resolver and run a quick route get for one rule first, maybe referencing 104.18.0.163.

description: Validate that bypass destination routes via wgc1


text:
**Cleaning up files and verifying peers**

I need to verify if the WGS peers are reachable from the router, possibly by pinging 10.6.0.2, though it may not be necessary. It might be a good idea to clean up local temporary files; it’s optional, but removing them from the repo would help avoid a messy situation if we created unnecessary files. Yes, I should use the Delete tool to remove four or five temporary files to keep things tidy.


command: ssh "komehdaht@192.168.50.1" 'echo "---CHATGPT HEAD---"; curl -I --max-time 12 https://chatgpt.com 2>&1 | sed -n "1,8p"; echo "---CLAUDE HEAD---"; curl -I --max-time 12 https://claude.ai 2>&1 | sed -n "1,8p"'

description: Smoke test blocked domains from router via HTTPS


text:
Супер, доступ через `komehdaht` сработал, я уже внес фиксы прямо на роутере и проверил состояние.

Что реально ломало обход:
- схема была нестабильной из-за накопления огромного числа `ip rule` (тысячи правил);
- не было `wan-event`, поэтому после WAN-событий правила не всегда восстанавливались;
- попытка перевода на `ipset+fwmark` не подходит для этого роутера: в ядре нет нужной поддержки (`xt_set`), поэтому этот путь я не оставил.

Что я сделал на роутере:
- переписал `/jffs/scripts/firewall-start`:
  - оставил фикс маршрутов для WG-клиентов (`10.6.0.2`, `10.6.0.3`);
  - оставил спец-правила для GeoHideDNS IP;
  - убрал legacy-мусор/остатки старой схемы;
  - теперь он пересобирает доменные обходные правила через `/jffs/scripts/wg_domain_route.sh apply`.
- переписал `/jffs/scripts/services-start`:
  - `UpdateAIDNS` ежедневно в 04:00;
  - `WGReseed` каждые 30 минут: `wg_domain_route.sh apply`;
  - `WGBypassHealth` каждые 5 минут.
- добавил `/jffs/scripts/wg_bypass_health.sh`:
  - проверяет, что обходные WG-правила живы;
  - если handshake `wgc1` протух (>300с) — делает `service restart_wgc1` и переустанавливает правила.
- добавил `/jffs/scripts/wan-event`:
  - на WAN up/connected запускает health/self-heal.
- обновил `/jffs/scripts/update_ai_dns.sh`:
  - после обновления списка доменов теперь делает `wg_domain_route.sh apply` (вместо watcher reseed).

Проверка после фикса:
- cron в порядке: есть `UpdateAIDNS`, `WGReseed`, `WGBypassHealth`;
- в `ip rule` теперь контролируемый набор (у меня получилось `179` правил в диапазоне обхода, а не тысячи);
- `wgc1` живой (handshake свежий);
- `ip route get` для обходного IP уходит в `table wgc1` и `dev wgc1`.

Что тебе сделать сейчас:
- на телефоне/ноутбуке переключить Wi‑Fi/WG (или просто reconnection), чтобы обновились сессии;
- на проблемном клиенте очистить DNS-кэш/перезапустить браузер;
- понаблюдать 1-2 часа, особенно после переподключения WAN/Wi‑Fi.

Если снова словишь отвал — я дальше добью до «железобетона»: добавлю расширенный лог-health (`logger` по ключевым стадиям) и при необходимости перестрою на двухконтурную схему с failover на второй VPS.

**Analyzing connectivity issues**

I encountered a 403 error from Cloudflare, possibly due to the user agent from the router, but connectivity is fine. I’m considering inspecting logs, though I’m not sure that's needed. I want to craft a final response in Russian, explaining what fixed the issue and the root cause, including that the ipset module was unavailable.

I think I should provide commands for users to verify from clients, such as flushing DNS. I'll also mention the backup file names as general `.bak.TIMESTAMP`, and provide a rollback command. I need to clarify that we changed the remote, not the repo, and aim for concise bullet points.